hello啊,every body ,大家好,之前我们(其实只有我自己啦,为什么用我们呢?可能是为了装作我有一个团队吧,也可能只是为了顺口🌝 )发布了
hevue-img-preview 2.0
版本,增加了多图预览的功能,收到了广大用户的好评(其实并没有用户),我们的产品经理(其实就是我)又贴心的增加了部分功能,推出了2.5版本,下面大家来跟我一起看看都有哪些新功能以及如何实现的吧 O(∩_∩)O
本次更新内容
- 版本号:2.5.2
- 增加键盘控制功能,控制退出预览,翻页、旋转缩放等
- 增加图片加载中,加载失败的显示效果
- 顺便修复了z-index显示层级不够高的问题
- 未安装的同学直接安装就好,已安装的同学可以带上版本号更新
npm i hevue-img-preview@2.5.2 --save
- 详细介绍文章:https://juejin.im/post/5ee6377b51882542ea2b50ec
- GitHub地址:https://github.com/heyongsheng/hevue-img-preview
增加键盘事件
需求及理由
由于本款组件是面向pc端的一款图片预览组件,所以我们的用户自然都是pc用户,那么其中必然有一部分用户是用的笔记本,那么这一部分中的一部分人可能习惯用触控板加键盘的方式进行操作,通常pc的屏幕都是比较大的,所以让用户用手指在触控板上移动好几下从上一张的按钮移动到下一张的按钮,在移动到关闭的按钮上是很费劲的(就算用鼠标也有那么一丢丢不方便),所以我们贴心的推出了键盘控制功能,左手键盘点点,就能实现组件的几乎所有功能,真香!下面就来看看我们伟大的工程师(还是我)是如何实现的吧
代码实现
提示:如果不关心代码的同学只看思路就行了,忽略掉代码部分,纵使你可能不需要此组件,也对功能不感兴趣,我也希望你能耐心往下看看,也许你能从中学到部分思路呢
注意
本文所有的代码修改都是在2.0版本的代码基础之上进行修改,如果有同学感兴趣的话可以到我的GitHub上下载历史版本源码进行对照阅读
让用户选择是否使用键盘事件
首先呢,我们要足够的为用户考虑,就算我们加了这个功能,也要看用户需不需要,再加上我们这里是添加的键盘事件,有可能会覆盖掉用户的默认键盘事件或者导致重复触发,所以我们必须让用户选择是否启用此功能,我们可以把这个开关作为一个配置项,让用户调用的时候传入进来。
props: {
...
keyboard: { // 是否启用键盘事件
type: Boolean
default: false // 默认不启用
}
}
// 然后在组件被触发的时候判断是否启用
if (this.keyboard) {
document.addEventListener('keydown', fn) // fn为键盘事件名
}
// 注意如果我们启用了键盘事件要在关闭组件时注销掉
if (this.keyboard) {
document.removeEventListener('keydown', fn) // fn为键盘事件名
}
做一个简易的防抖函数
注意,由于作者才疏学浅,编写代码时候还不知道节流,只知道防抖,所以不知道以下所属情况其实符合节流的场景,但是实在懒得改了,这一段内容大家参考参考就好,等我悟透了防抖和节流再专门写一篇文章讲讲。
大家看到了我们上面添加的是 keydown
事件,也就是如果用户不松手是可以一直触发这个事件的,由于我们给图片的缩放和旋转都添加了动画效果,执行时间为0.3s,所以当键盘事件连续两次触发间隔小于0.3s时,画面就会出现抖动现象,所以我们这里要制作一个简易的防抖
/*
* 此段代码为下面的示例,请先阅读下文
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
let timer = null //借助闭包
return function() {
if(timer){
clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
timer = setTimeOut(fn,delay)
}else{
timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
}
}
}
传统的防抖函数是如果用户在规定的时间内连续触发事件,那么只有用户最后一次触法事件会生效,例如我们规定时间是0.5s,用户触发了一次事件(例如点击事件),又在0.5s内又触发了一次该事件,那么第一次事件就不会执行,如果用户在触发第二次事件后的0.5s内又触发了相同的事件,那么这个事件还不会执行,直到用户最后一次触发事件并且在0.5s内没有触发相同事件时,才会执行此事件(具体代码可以参考上面的代码,如果想详细了解可以去搜索一下)。但是我们这里显然不能这么做,如果用户连续点击,那么这个事件永远都不会执行,我们需要让用户触发的事件先生效,然后在规定的时间内再触发次时间不生效,等到规定的时间过去再让此事件可被触发。
// 我们现在data里定义一个变量,控制我们的事件是否可被触发,此处不考虑封装防抖函数,后面有机会可以单独讲一下防抖
canRun: true // 默认为true,事件第一次触发可被触发
// 然后我们在 methods 里定义这个防抖函数
keyHandleDebounce (e) {
if (this.canRun) {
// 如果this.canRun为true证明当前可以执行函数
this.keyHandle(e) // keyHandle 为键盘触发事件,下面会讲
this.canRun = false // 执行函数后一段时间内不可再次执行
setTimeout(() => {
this.canRun = true // 等到了我们设定的时间之后,把this.canRun改为true,可以再次执行函数
}, 300) // 此处我们把规定间隔事件固定为0.3s,也就是动画执行的时间
}
},
// 这时候我们可以在添加是移除键盘事件的地方,把触发键盘事件改为触发这个防抖事件
document.addEventListener('keydown', this.keyHandleDebounce)
document.removeEventListener('keydown', this.keyHandleDebounce)
这样我们就达到了防抖又可连续触发键盘事件的目的
监听键盘事件
监听键盘事件就没啥好说的了,监听用户按下的对应按钮,然后触发对应事件就好了,需要注意的是上一张和下一张只有在多图预览时才有效,这里我们没有用 ↑
↓
←
→
键来操作放大、缩小、上一张、下一张,而是采用了 w
s
a
d
键来操作相应功能,这是为什么呢,当然是尽量把功能键都放在左手边啊,要不功能键左边右边分散开来多不方便啊,这样也方便用户左右操作,右手在触控板上控制图片移动嘛(其实是因为上下键会导致页面滚动,而作者没有找到很好的办法),为啥没有控制图片移动的?当然是因为用户右手已经空出来了,无论是用触控板还是鼠标移动都很方便嘛!而且用键盘也不好控制移动的距离,体验不怎么好的
keyHandle (e) {
var e = window.event || e
var key = e.keyCode || e.which || e.charCode
switch (key) {
case 27: // esc
this.close()
break
case 65: // a键-上一张
if (this.multiple) {
this.toogleImg(false)
}
break
case 68: // d键-下一张
if (this.multiple) {
this.toogleImg(true)
}
break
case 87: // w键-放大
this.scaleFunc(0.15)
break
case 83: // s键-缩小
this.scaleFunc(-0.15)
break
case 81: // q键-逆时针旋转
this.rotateFunc(-90)
break
case 69: // e键-顺时针旋转
this.rotateFunc(90)
break
case 82: // r键-复位键
this.initImg()
break
default:
break
}
}
增加图片加载等待效果
因为有些图片来自于网络或者服务器加载,由于图片体积或者网速较慢的原因可能会加载图片较慢,为了良好的用户体验,我们需要给用户展示一个正在加载的效果,如果由于各种原因(网络不好,图片地址无效等)导致图片加载失败的话,我们也得给用户一个反馈的效果
首先这里修复一个bug,之前我们的图片切换等都是直接修改 prop
里传入的url值,当然这样是不规范的,我们在子组件里不能直接怼prop里的值进行修改,虽然不知道为啥没报错。。。刚好我们这里要实现图片加载等待的效果,这个不规范的操作也顺手给解决了。
// 首先我们在data里定义一个变量imgurl
imgurl: ''
// 然后我们把我们当前展示图片的地址换成这个(此插件的img元素只有一个,多图预览就是通过修改这个图片url实现的)
<img
:src="imgurl"
ref="heImView"
class="he-img-view"
:style="'transform: scale('+imgScale+') rotate('+imgRotate+'deg);margin-top:'+imgTop+'px;margin-left:'+imgLeft+'px;' + maxWH"
@mousedown="addMove"
/>
// 由于我们要加图片加载等待效果,所以我们把所有对url的修改都封装成一个函数changeUrl
// 这里我们要现在data里加一个变量imgState表示图片状态以展示不同的反馈图片,1为加载中,2为加载成功,3为加载失败
methods: {
changeUrl (url){
this.imgState = 1
let img = new Image()
img.src = url // 创建一个图片对象并把需要展示的图片赋值过去
img.onload = () => {
this.imgState = 2 // 图片加载成功 页面显示图片
this.imgurl = url
}
img.onerror = () => {
this.imgState = 3 // 图片加载失败 显示加载失败
}
}
}
// 之后我们要在本组件被调用的时候把用户的url当做参数调用我们上面封装的修改图片地址的函数
if (this.multiple) {
// imgList 即多图预览时展示的图片数组
if (Array.isArray(this.imgList) && this.imgList.length > 0) {
// nowImgIndex 为默认展示的图片在图片数组中的下标,如果用户不传默认就是0(即第一张)
this.imgIndex = Number(this.nowImgIndex) || 0
// this.url = this.imgList[this.imgIndex] // 之前直接对url进行修改,这里改为调用函数
this.changeUrl(this.imgList[this.imgIndex])
} else {
console.error('imgList 为空或格式不正确')
}
} else {
// 如果不是多图显示直接获取用户传入的url就可以了
this.changeUrl(this.url)
}
切换上一张下一张的地方也要改为调用函数的方法(能截图我就不贴代码了哈)
至此我们加载中,加载完成,加载失败的效果就已经完成咯
其实这里还有一个问题,就是如果我们连续切换两次图片,可能会存在两张图片同时加载的情况,那么就会产生两张图片先后加载完成的问题,这时候每一张图片加载完成或失败就会修改图片加载状态,甚至会造成显示图片错误的情况,这里我想过防抖或节流的方法都无法解决,所以只能换一种思路,如果存在切换图片,那肯定就是多图预览,那我们需要显示的就是用户最后点击的那一次切换的图片,所以我们只需要在修改图片地址的时候,把当前图片的下标传过去,再加载完成的时候,判断当前加载完成的图片的下标是否是当前需要显示的图片的下标就可以了。
// this.changeUrl(this.imgList[this.imgIndex])
this.changeUrl(this.imgList[this.imgIndex], this.imgIndex)
// 然后改造一下changeUrl
changeUrl (url, index) {
this.imgState = 1
let img = new Image()
img.src = url
img.onload = () => {
if (index != undefined && index == this.nowImgIndex) {
this.imgState = 2
this.imgurl = url
} else if (index == undefined) {
this.imgState = 2
this.imgurl = url
}
}
img.onerror = () => {
if (index != undefined && index == this.nowImgIndex) {
this.imgState = 3
} else if (index == undefined) {
this.imgState = 3
}
}
},
大功告成,这里测试结果就不展示了,反正就是成功了。
文章中有很多不成熟的地方,还需要各位大佬多多指教,多谢多谢
QQ:1378431028
微信: heyongsheng1996