360°看车实现方案

最近在业务需求中,有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" 属性,否则图片被出视图外,就不美观了。当然在这里理论上是不会发生被拖动的情况的。

基本布局如下:

COPY
1
2
3
4
5
6
<div>   
<img draggable="false"></img>
<img></img>
<img></img>
... 一共36张
</div>

如何实现

因为品牌官网是自适应的,需要对PC、PAD、mob都做适配,所以这里使用mousetouch 来实现具体的操作(滑动)逻辑。

鼠标相关事件

  • mouseup
  • mousemove
  • mousedown
  • mouseleave

触摸相关事件

  • touchstart
  • touchmove
  • touchend

简单说一下思路:

  1. 开始移动:在鼠标按下或者开始触摸时

    • 记录初始的 pageX
    • 记录当前的图片索引 index
    • 标记开始 clicked = true
  2. 移动中:在鼠标或者触摸开始移动时

    • 计算当前 pageX 与上一次或者初始的pageX 的差值
    • 差值大于0,图片索引 index 自增1
    • 差值小于0,图片索引 index 自减1
    • 自增/自减后,对于当前图片移除 visibility: hidden 属性,上一张/下一张图片增加 visibility: hidden 属性
    • index 大于图片总数时,重置为0,index 小于1时,重置为图片总数,以此来完成循环的效果
  3. 移动完成:在鼠标抬起或者停止触摸时, 标记 clicked = false

具体代码实现如下:

COPY
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
},

当把代码完成并实现时,你会发现稍微拖动几像素,图片就是疯狂切换,于是我们对敏感度做一些优化。
使用一个界限值,超过或小于这个值才触发图片的显示/隐藏,当然这个界限可以视业务需求而定。

COPY
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 事件在图片加载完成后立即执行。

COPY
1
2
3
4
5
let count = 0;
image.onLoad = function() {
// 每次加载完 count自增1
count ++;
}

获取所有需要加载的图片示例,累计 count == len则表示加载完成
此处使用 Promise 进行包装,来实现图片加载完的监听,当然也可以使用 watch

COPY
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() 保证所有的图片加载完成后执行后续逻辑

COPY
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 保证所有的图片加载完成后执行后续逻辑
Promise.all(promiseAll).then((img)=>{
// 图片加载完成
_self.imgLoad = false
})
}

大功告成。

作者: 果汁
文章链接: https://guozhigq.github.io/post/7a597f7.html
版权声明: All posts on this blog are licensed under the CC BY-NC-SA 4.0 license unless otherwise stated. Please cite 果汁来一杯 !