图片切片的轮播效果

前言

在一次无意间逛某个网站时,被其首页的轮播效果深深地吸引了.通过浏览器的一顿调试,终于明白了实现原理,并最后自己手写了一份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生成一段类名为hookhtml片段,设置定位方式为absolute.

hook里面有五个div,分别显示2.jpg对应的图片切块.现在要面临的问题是怎么样让这5div分别按比例显示这张图片的某部分呢?

假如设置hook外层容器的总宽度为1000px,子元素5div平均占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份每个div200px(实际编码中200要通过js算出来),再向右移动200px占领第二个切片位置.

imgwidthleft的赋值是重点,它的具体数值也是要通过总宽度和切片数量计算出来.

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>
     `     
  }

5div拼接好了再放入到<div class="hook"></div>,并添加到页面文档渲染.最终生成这段dom元素就是接下来要执行动画效果的元素.

由于5div设置了绝对定位并将top值设置成-100%,如此它们一旦在页面上渲染出来位置就处于上方.

接下来的动画效果就非常好实现了,我们只需要在上面的循环中给每一个div添加一个transition:top linear 0.25s就可以了.

一旦这5div渲染完成,我们就可以在js里动态设置每个divtop值为0.动画便会触发,页面上每个div从顶部向下缓缓滑动.

等到所有动画过程全部完成,我们就将hook这段用js生成的dom移除掉,并将应该显示的item(包含静态图片的原始dom)从隐藏设置为显示.至此整个动画过程就完成了.

3D效果

平面效果实现起来相对简单,相较而言3D翻转的效果需要先对css3d属性有个了解.

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效果.

translateZtranslateXtranslateY这两个属性不一样.translateX是在平面上左右移动,translateY是在平面上下移动.

translateZ是一个3d属性,它不是在平面上做上下左右的位移,而是垂直于平面方向往里或往外做位移.

translateZ(-100px)就表示元素往屏幕里边移动了100px.根据近大远小的规则,元素最终呈现出来的视觉效果就是整体变小了.如果是正的100px,就往屏幕外边移动100px,视觉效果上元素被扩大了.

translateZ只有在设置了preserve-3d属性的元素上应用才会有效果,preserve-3d能让所有3d属性生效.

block是要做3d变换的dom元素,因此要给它设置preserve-3dtransform变换.而父元素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,frontup都设置成绝对定位,宽和高都充满父元素,lefttop设置为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属性perspectiveperspective-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动态赋值就可以了.

源码

完整代码

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值