最近在业务需求中,有360°看车的需求,即左右左右滑动鼠标实现正、侧、后方,多视角看车,增加意向用户对于车辆外观的了解,提高购买意向(毕竟比三张轮播图更生动不是)。
经过调研,发现市面上类似场景的实现都是36张图,每10°切换一张,达到360°的效果,36 = 360 /10
,同时也发现,目前市面上开源框架不适用于业务需求,主要还是可定制度太低,所以决定自己实现(好像也没其他办法了)。
下面将从几方面实现
如何展示
要达到360°看车的效果,需要将36张图片叠加在一起,布局就使用如下,父盒子 position: relative
,子元素 img
共36张,使用 position: absolute
层叠,滑动的时候通过切换图片的 显示/隐藏 完成效果。当然相关的触发事件是在父盒子上完成的。
这里对于图片的隐藏使用 visibility: hidden
,至于为什么不使用 display:none
? 是因为 display:none
的元素显示/隐藏时会触发回流、重绘,对于性能会有影响,而 visibility: hidden
只会触发重绘,性能方面后者屡胜一筹。
另外,记得给 img
加上draggable="false"
属性,否则图片被出视图外,就不美观了。当然在这里理论上是不会发生被拖动的情况的。
基本布局如下:
1 2 3 4 5 6
| <div> <img draggable="false"></img> <img></img> <img></img> ... 一共36张 </div>
|
如何实现
因为品牌官网是自适应的,需要对PC、PAD、mob都做适配,所以这里使用mouse
、touch
来实现具体的操作(滑动)逻辑。
鼠标相关事件
- mouseup
- mousemove
- mousedown
- mouseleave
触摸相关事件
- touchstart
- touchmove
- touchend
简单说一下思路:
开始移动:在鼠标按下或者开始触摸时
- 记录初始的
pageX
,
- 记录当前的图片索引
index
- 标记开始
clicked = true
移动中:在鼠标或者触摸开始移动时
- 计算当前
pageX
与上一次或者初始的pageX
的差值
- 差值大于0,图片索引
index
自增1
- 差值小于0,图片索引
index
自减1
- 自增/自减后,对于当前图片移除
visibility: hidden
属性,上一张/下一张图片增加 visibility: hidden
属性
index
大于图片总数时,重置为0,index
小于1时,重置为图片总数,以此来完成循环的效果
移动完成:在鼠标抬起或者停止触摸时, 标记 clicked = false
具体代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| onMousedown(type, e) { let pageX = '' if (type === "touch") { pageX = e.changedTouches[0].pageX }else{ pageX = e.pageX } currPos = pageX clicked = true }
onMouseMove: throttle(function(type, e) { if(clicked){ let pageX = ''; if (type === "touch") { pageX = e.changedTouches[0].pageX }else{ pageX = e.pageX } if (currPos - pageX >= 0) { currImg++; if (currImg > imageTotal) { currImg = 0; } } else { currImg--; if (currImg < 1) { currImg = imageTotal; } } currPos = pageX; vrImgs[lastImg].classList.add('notseen'); vrImgs[currImg].classList.remove('notseen'); lastImg = currImg; } },0)
onMouseup(e) { clicked = false },
|
当把代码完成并实现时,你会发现稍微拖动几像素,图片就是疯狂切换,于是我们对敏感度做一些优化。
使用一个界限值,超过或小于这个值才触发图片的显示/隐藏,当然这个界限可以视业务需求而定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| onMouseMove: throttle(function(type, e) { if(clicked){ let pageX = ''; if (type === "touch") { pageX = e.changedTouches[0].pageX }else{ pageX = e.pageX } widthStep = 4 if (Math.abs(currPos - pageX) >= widthStep) { if (currPos - pageX >= widthStep) { currImg++; if (currImg > imageTotal) { currImg = 0; } } else { currImg--; if (currImg < 1) { currImg = imageTotal; } } currPos = pageX; vrImgs[lastImg].classList.add('notseen'); vrImgs[currImg].classList.remove('notseen'); lastImg = currImg; } } },0),
|
到此为止,360°看车就基本完成了。
如何优化
但是,等等。36张图,如果用户在图片还没有全部加载完的情况下拖动,岂不是不尴尬😅,空白一片,神马都没有。
那就监听图片加载,加个loading或者进度条吧,进度条更直观。在这里我们利用image的onLoad来实现
onload 事件在图片加载完成后立即执行。
1 2 3 4 5
| let count = 0; image.onLoad = function() { count ++; }
|
获取所有需要加载的图片示例,累计 count
== len
则表示加载完成
此处使用 Promise
进行包装,来实现图片加载完的监听,当然也可以使用 watch
1 2 3 4 5 6 7 8 9
| let count = 0; const img = document.getElementsByClassName('png') const len = img.length for(let i = 1 ; i < len ; i++){ img[i].onload = function(){ count ++ } }
|
将每个 onLoad
手动转换为 Promise
,多个 Promise
放入一个数组,并利用 Promise.all()
保证所有的图片加载完成后执行后续逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function imgComplate() { const _self = this this.imgLoad = true const img = document.getElementsByClassName('png') const len = img.length const promiseAll = []; for(let i = 1 ; i < len ; i++){ promiseAll[i] = new Promise((resolve, reject)=>{ img[i].onload = function(){ _self.loadedCount ++ const progress = ((_self.loadedCount / 35) * 100).toFixed(0) _self.loadWidth = (progress > 100 ? 100 : progress) + '%' resolve() } }) } Promise.all(promiseAll).then((img)=>{ _self.imgLoad = false }) }
|
大功告成。