轮播图的简单实现3D版

轮播图的简单实现3D版

参考的例子为网易云音乐APP。

从网易云的例子上看,3D的轮播图有一定的层次感。

也就是左右两张图距离用户是比较远的,而中间这张图离用户是比较近的。

远近可以理解成css中的z-index

我们可以先把大体的HTML结构给写出来(这里以三张图为例子)。

1
2
3
4
5
6
7
<div class="carousel">
<div class="carousel__item__container">
<div class="carousel__item"></div>
<div class="carousel__item"></div>
<div class="carousel__item"></div>
</div>
</div>

结构基本和之前的一样,不过样式可能会有所改变,为了使得可视窗口显示三张图片,需要对之前的样式进行修改。

之前对应每张图片的宽高不变,是500x350

1
2
3
4
5
6
7
8
9
.carousel__item {
position: absolute;
width: 500px;
height: 350px;
top: 0;
left: 0;
/* 设置过渡动画 */
transition: all .5s;
}

整个.carousel盒子明显不能为500x350,不然无法显示出三张图片。

我们可以指定为500+200x2宽度,也就是左右各腾出200px来放对应的图片。

1
2
3
4
5
6
7
.carousel {
position: relative;
width: 900px;
height: 350px;
/* 下面这个让整个盒子居中,方便看 */
margin: 30px auto;
}

为了展示的清楚点,填充图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="carousel">
<div class="carousel__item__container">
<div class="carousel__item">
<img src="../images/img1.jpg" />
</div>
<div class="carousel__item">
<img src="../images/img2.jpg" />
</div>
<div class="carousel__item">
<img src="../images/img3.jpg" />
</div>
</div>
</div>

设置下图片的样式:

1
2
3
4
5
.carousel__item > img {
display: block;
width: 100%;
height: 100%;
}

现在的效果如下:

由于三张图片绝对定位都是top: 0; left: 0,所以都在左上角,并且显示的是最后一张。

现在我们要先把图片挪到对应的位置。

对于第一张图,它应该是在中间的,所以添加transform: translate3d(200px, 0, 0)

位置是正确了,但是被最后一张图遮住了,给它添加个z-index让它离屏幕"近一点"。

第二张图,它应该是在中间的右边的,因为当我们点击(往右的)下一张时,右边这张就会放到中间的位置。

给它加上transform: translate3d(400px, 0, 0)

到这里,大体的效果基本出来了,可以看到我们没有对第三张图做任何transform变化。

很容易从逻辑上理解,它作为最后一张就应该在第一张(中间这张)的左边,这样当我们点击(往左的)上一张时,左边这张就会放到中间的位置。

为了更有层次感,我们把左右两张缩放,变小点,这里使用的是scale

感觉很对,接下来就是要实现滑动的逻辑了。

PS:本文只简单地实现往右下一张的逻辑。

由于3d轮播有一定的特殊性,所以这里不考虑三张图片一下的情况,也就是处理最少3张的情况。

首先要分析当我们想要下一张时,需要如何调整对应图片的样式(只针对3张的情况)。

第一张(中间这张)应该是往右边,原来右边的应该往中间,原来左边的应该往右边,也就是变成如下这样。

那么我们的逻辑就可以开始写了。

我们先添加一个button来模拟下一次的情况。

1
2
3
<div style="display: flex;align-items: center;justify-content: center;">
<button id="btn">下一张</button>
</div>

然后在buttonclick事件上写逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const eles = document.getElementsByClassName("carousel__item");
let curIdx = 0;
let left = eles.length - 1;
let right = curIdx + 1;

const btn = document.getElementById("btn");
btn.onclick = function () {
// 中间的往左边
eles[curIdx].style.setProperty("transform", "translate3d(0,0,0) scale(0.8)");
eles[curIdx].style.setProperty("z-index", "1");

// 原来左边的放右间
eles[left].style.setProperty("transform", "translate3d(400px,0,0) scale(0.8)");
eles[left].style.setProperty("z-index", "1");

// 原来右边的往中间
eles[right].style.setProperty("transform", "translate3d(200px,0,0) scale(1)");
eles[right].style.setProperty("z-index", "3");

// 更新索引
left = curIdx;
curIdx = right;
right = (curIdx + 1) % eles.length;
};

然后我们就可以看到效果了:

看起来似乎不错,但是其实是有bug的,如果我们有四张图片的话。

很不对劲好吧…

为什么会出现这种情况呢?

可以从动图里面看到右边其实每次都存在一张不动图片,导致动画很奇怪。

并且图片的层次存在问题,对于第二张图之后的图片,应该是离用户越来越远的。

所以我们需要先改进动画的逻辑,先不管层次的问题。

由于左右每次应该都是只有一张的,所以不能把全部的图片都放到左边或者右边。

那么应该放哪里呢?答案是可以放中间。

可以把不显示的图片都放到中间,对于下一张,每次从中间取出一张图放到右边,原来左边的图放到中间

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
const eles = document.getElementsByClassName("carousel__item");
let curIdx = 0;
let left = eles.length - 1;
let right = curIdx + 1;

const btn = document.getElementById("btn");
btn.onclick = function () {
// 中间的往左边
eles[curIdx].style.setProperty("transform", "translate3d(0,0,0) scale(0.8)");
eles[curIdx].style.setProperty("z-index", "1");

// 原来左边的放中间
eles[left].style.setProperty("transform", "translate3d(200px,0,0) scale(0.8)");
eles[left].style.setProperty("z-index", "1");

// 原来右边的往中间
eles[right].style.setProperty("transform", "translate3d(200px,0,0) scale(1)");
eles[right].style.setProperty("z-index", "3");

// 更新索引
left = curIdx;
curIdx = right;
right = (curIdx + 1) % eles.length;

// 取现在对应的下一张,放到右边
eles[right].style.setProperty("transform", "translate3d(400px,0,0) scale(0.8)");
eles[right].style.setProperty("z-index", "1");
};

效果如下:

看起来没什么问题,但还是有一点小瑕疵,可能动图抽帧看不出来。

我们对除了中间的那张的z-index设置为3,其他都设置为1

当我们从第一张往右的时候,左边会闪一下(左边过渡到中间的一个动画),但这里应该是中间往左边的动画一直显示才对。

原因就是相同z-index情况下,后写的节点比先写的节点离屏幕"更近"。

所以我们还需要解决下层次的问题。

  • 中间这张看得见的往左边,z-index设置为1
  • 原来左边的这张z-index设置为0,放到中间藏起来;
  • 原来右边的这张z-index设置为3,也就是当前的展示的图片;
  • 从中间不可见的图片中取一张往右,z-index设置为0
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
const eles = document.getElementsByClassName("carousel__item");
let curIdx = 0;
let left = eles.length - 1;
let right = curIdx + 1;

const btn = document.getElementById("btn");
btn.onclick = function () {
// 中间的往左边
// 往左边为1
eles[curIdx].style.setProperty("transform", "translate3d(0,0,0) scale(0.8)");
eles[curIdx].style.setProperty("z-index", "1");

// 原来左边的放中间
// 中间的看不见的图片都是0
eles[left].style.setProperty("transform", "translate3d(200px,0,0) scale(0.8)");
eles[left].style.setProperty("z-index", "0");

// 原来右边的往中间
// 中间看的见的这张就是3
eles[right].style.setProperty("transform", "translate3d(200px,0,0) scale(1)");
eles[right].style.setProperty("z-index", "3");

// 更新索引
left = curIdx;
curIdx = right;
right = (curIdx + 1) % eles.length;

// 取现在对应的下一张,放到右边
// 中间不可见往右边为0
eles[right].style.setProperty("transform", "translate3d(400px,0,0) scale(0.8)");
eles[right].style.setProperty("z-index", "0");
};

那么基本上逻辑就差不多了,放一个自己的demo。