前言
在一次无意间逛某个网站时,被其首页的轮播效果深深地吸引了.通过浏览器的一顿调试,终于明白了实现原理,并最后自己手写了一份Demo
,最终效果如下:(源码在最后)
平面效果:
3D效果:
这款轮播图它将图片切成了几份,然后依次将切块用动画效果播放出来,最后拼接成了一张新图片.
在回忆前端技术栈的过程中,似乎是没有一种技术是可以直接对图片进行裁剪并分成几块的.我们平时应用的图片裁剪效果也只是使用canvas
来模拟生成.
通过本人的调试,结果发现这款轮播图采用的是一种比canvas
更简单的方式就达到了相同的裁剪效果.
接下来由浅入深,一步一步去揭露实现原理.
平面效果
先从平面效果说起,首先编写一段普通的html
轮播图,结构如下:
<div class="main" id="el">
<div class="item">
<img src="./img/1.jpg" />
</div>
<div class="item">
<img src="./img/2.jpg" />
</div>
<div class="item">
<img src="./img/3.jpg" />
</div>
</div>
el
是外层的容器,里面包含了三张轮播图片.
如果要制作一个动画效果,我们要遵循一个步骤.
- 生成或者获取要做动画的
dom
元素 - 对该
dom
元素添加动画效果
现在将平面效果的动画放慢,仔细观察其特征,如下图:
在原始的html
代码里是找不到这个图片切片的,那么就意味着要做动画的元素需要我们自己生成出来添加到页面文档中渲染.
假如设定图片会被切成5
份,那么我们生成的图片切片的结构大致如下:
<div class="main" id="el">
<div class="item">
<img src="./img/1.jpg" />
</div>
<div class="item">
<img src="./img/2.jpg" />
</div>
<div class="item">
<img src="./img/3.jpg" />
</div>
<!--做动画的dom元素-->
<div class="hook">
<div><img src="./img/2.jpg" /></div>
<div><img src="./img/2.jpg" /></div>
<div><img src="./img/2.jpg" /></div>
<div><img src="./img/2.jpg" /></div>
<div><img src="./img/2.jpg" /></div>
</div>
</div>
在原页面上通过js
生成一段类名为hook
的html片段
,设置定位方式为absolute
.
hook
里面有五个div
,分别显示2.jpg
对应的图片切块.现在要面临的问题是怎么样让这5
个div
分别按比例显示这张图片的某部分呢?
假如设置hook
外层容器的总宽度为1000px
,子元素5
个div
平均占200px
.通过以下设置可以让第二个div
只展现img
的第二个切块.
<div style="position:absolute;width:200px;left:200px;overflow:hidden">
<img src="./img/2.jpg" style="position:absolute;width:500%;left:-200px"/>
</div>
由于外层总宽度是1000px
,切成5
份每个div
占200px
(实际编码中200
要通过js
算出来),再向右移动200px
占领第二个切片位置.
img
的width
和left
的赋值是重点,它的具体数值也是要通过总宽度和切片数量计算出来.
width
设置成500%
,意味着图片的宽度等于1000px
和最外层容器相吻合,然后再往左移动200px
,由于父级div
设置了overflow:hidden
,设想一下效果便出来了.
以上说的这些过程都应该在js
里面拼接计算得到一串html字符串
,类似如下:
for(i=0;i<n;i++){ //循环i次,n(总共将图片切成n份),unit_width对应每一个切块的宽度
html += `
<div
style="position:absolute;
width:${unit_width}px;
top:-100%;
left:${ i * unit_width};
overflow:hidden">
<img src="${src}"
style="position:absolute;
width:${n*100}%;
left:${-i * unit_width}px"/>
</div>
`
}
5
个div
拼接好了再放入到<div class="hook"></div>
,并添加到页面文档渲染.最终生成这段dom
元素就是接下来要执行动画效果的元素.
由于5
个div
设置了绝对定位并将top
值设置成-100%
,如此它们一旦在页面上渲染出来位置就处于上方.
接下来的动画效果就非常好实现了,我们只需要在上面的循环中给每一个div
添加一个transition:top linear 0.25s
就可以了.
一旦这5
个div
渲染完成,我们就可以在js
里动态设置每个div
的top
值为0
.动画便会触发,页面上每个div
从顶部向下缓缓滑动.
等到所有动画过程全部完成,我们就将hook
这段用js
生成的dom
移除掉,并将应该显示的item
(包含静态图片的原始dom
)从隐藏设置为显示.至此整个动画过程就完成了.
3D效果
平面效果实现起来相对简单,相较而言3D
翻转的效果需要先对css
的3d
属性有个了解.
3d属性回顾
rotateX
:围绕X
轴旋转,联想单杠运动
.
rotateY
:围绕Y
轴旋转,联想钢管舞
.
rotateZ
:围绕Z
轴旋转,联想老式钟表盘
.
以上三个属性平时接触较多,不再赘述.下面着重介绍一下3d
属性.
.container{
perspective: 1200px;
perspective-origin: right center;
.wrapper {
transform-style: preserve-3d;
transform:translateZ(-100px);
}
}
外层容器container
包含一个子级wrapper
.子级是具体要做3d
动画的元素,所以它必须要设置一个属性transform-style: preserve-3d
.
只有设置了preserve-3d
,元素才能显现出3d
效果.
translateZ
和translateX
、translateY
这两个属性不一样.translateX
是在平面上左右移动,translateY
是在平面上下移动.
translateZ
是一个3d
属性,它不是在平面上做上下左右的位移,而是垂直于平面方向往里或往外做位移.
translateZ(-100px)
就表示元素往屏幕里边移动了100px
.根据近大远小的规则,元素最终呈现出来的视觉效果就是整体变小了.如果是正的100px
,就往屏幕外边移动100px
,视觉效果上元素被扩大了.
translateZ
只有在设置了preserve-3d
属性的元素上应用才会有效果,preserve-3d
能让所有3d
属性生效.
block
是要做3d
变换的dom
元素,因此要给它设置preserve-3d
和transform
变换.而父元素container
相当于一个舞台,而block
可以看成舞台表演的演员.
perspective
代表着观众离舞台的距离,可想而知perspective
值越小,代表着观众离舞台越近,那么舞台上的景象就会看的更清晰.
对应到真实场景,perspective
对应着用户的眼睛里dom
元素的距离,距离越大挨的越远,wrapper
就会越小内部越不清晰.相反perspective
越大,wrapper
也会显现的越大并且内部的细节也会越清楚.
perspective-origin
可以理解成用户是坐在观众席的左边位置还是中间位置还是右边位置来看舞台上的表演,视野位置的不同自然看到的效果也不一样.
现在回到正题,继续研究3d
翻转效果的轮播图.通过将动画时间调慢,观察其细节,如下图:
通过观察上图,很快意识到要做动画的元素是当前页面没有的,是需要我们在js
里面动态生成出来.
动画元素是一个立方体,它包含两个运动效果.一个是向左移动,另一个是向前翻转.向左移动很好办,设置成绝对定位动态变动left
值就可以了.而翻转通过设置RotateX(-90deg)
就做到了(相当于沿着X
轴翻转90
度).
该立方体的动画效果是不难实现的,难度在于如何生成这样的一个立方体.
绘制立方体
在当前的场景里,立方体只需要绘制四个面:上面、前面、左面和右面.上面存放的是下一张要轮播的图片,前面存放的是当前展现的图片,而左面和右面用黑色背景填充满让图片变的更加立体,html
结构如下:
<div class="wrapper">
<div class="left"></div>
<div class="right"></div>
<div class="front"><img src="1.jpg"></div>
<div class="up"><img src="2.jpg"></div>
</div>
将left
,right
,front
和up
都设置成绝对定位,宽和高都充满父元素,left
和top
设置为0
.
front
是正前方的面,它本来就在平面上显示,不用做任何处理.
left
要将中心点定位在左上角,沿y
轴往里渲染90
度就形成了侧面.
.left {
transform-origin: 0% 0%;
transform: rotateY(90deg);
background-color: #333;
}
right
需要先向右移动整个宽度再沿y轴旋转90
度就形成了右面.
html += `
... //js中dom字符串拼接
<div class="right" style="transform: translateX(${unit_width}px) rotateY(90deg);">
...
</div>
...
`
up
面先把中心点定在左下角,绕着X
轴旋转90
度再往上移动整个高度就形成了顶部.
.up{
transform-origin: 0% 100%;
}
html += `
... //js中dom字符串拼接
<div class="up" style="transform: rotateX(90deg) translateZ(${container_height}px);">
...
</div>
...
`
这四个面的dom
结构在js
里面拼接好后放入到wrapper
对应的父级div
里面.上面已经介绍过wrapper
里面是需要设置preserve-3d
属性的,wrapper
元素正是真正做动画的dom
元素.
wrapper
元素封装好后再丢进舞台元素container
里面,container
会设置3d
属性perspective
和perspective-origin
.
一个container
对应着一个立方体切片,所有被切割的立方体拼接而成的html
字符串放入到页面文档中渲染出来.如此做动画的dom
元素就生成好了.
添加翻转动画
执行动画的dom
元素已经生成好并渲染在了页面中,按照之前的分析,现在给每个立方体(wrapper
对应的div
)加一个rotateX(-90deg)
属性就可以让立方体做翻转了,结果如下:
wrapper
加上rotateX(-90deg)
属性的确是向前翻转了,但是最终停留的位置并没有贴近地面.
事故的原因是因为wrapper
是一个立方体,并非是简单的平面,它里面还包含了四个平面.现在对这个立方体做旋转,它的中心点的位置在哪里至关重要.
wrapper
的高度是100%
充满了整个父级.通过测试发现对立方体做属性变换,它其实是以正面(front
面)作为基准面的.立方体的中心点就位于front
面高度的中间处.如果front
面高度为600px
,那么就在300px
划一条X
轴,将整个立方体沿着这条轴做旋转.如果front
面高度为800px
,那么立方体就沿着400px
的轴线旋转.
现在再回到上面的案例,front
面的高度是设置为100%
充满父元素,那么立方体就会在中间划一条轴线向前翻转90
度,所以下面就会出现了镂空.我们不能让立方体翻转完停在半空中,要想办法把它贴回地面.
按照之前所说的,立方体是以front
面作为基准面的,翻转了90
度以后,front
面来到了底部.那么想让立方体向下移动,因为此时front
对着底部,只需要设置translateZ(contrainer_height/2)
就能使立方体向下移动一半的高度贴底.
这样设置完了虽然能确保翻转后的立方体贴底,但是它里面的图片发生了变形.
因为在立方体设置rotateX(-90deg)
朝前做翻转时其实是往前做了移动,根据近大远小的规则,图片视觉效果被放大了.为了解决这个问题,先将立方体使用translateZ(-contrainer_height/2)
往里面推一半的高度再让立方体朝前做翻转,这样图片拉伸的问题就解决了,代码如下:
while ((el = eles.shift())) {
//el对应着每个立方体的dom
//立方体先沿Z轴往后推一半高度,再朝前做翻转,翻转完后向下移动一半高度贴底
el.style.transform = `translateZ(${
-this.container_height / 2
}px) rotateX(-90deg) translateZ(${this.container_height / 2}px)`;
...
}
翻转效果做完了,我们会发现立方体正对着我们做翻转,我们无法看到立方体那个带有黑色背景的右面right
,这样会显的很没有立体感.
在前面介绍过一个属性perspective-origin:right center
.这就好比观众坐在观众席的右边,而舞台和观众席一样宽,舞台上面有个铁笼子它正对着观众的前方.此时观众的视野里只能看到铁笼的正面,如果将提笼往舞台左侧方向推动,那么此时坐在观众席右边的顾客不光能看到铁笼的正面,他还能看到铁笼的右面.
同样的道理为了让立方体做翻转时显的更加立体,可以使右面的黑色背景在动画中过程中显示出来.为了达到这一目的,就需要让立方体朝左移动,再加上perspective-origin
属性的支持,立方体翻转的立体效果就出来了.
如果想让立方体朝左移动,只需要获取立方体的dom
元素,对其left
动态赋值就可以了.
源码
完整代码
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!