书本无限翻页动画

3,843 阅读4分钟

前言

寒假到来又是读书的好季节,所以做了一本小册来补充知识效果如下:

实现

制作一页

我们需要将两张图合成一张有正反两面。这里需要将反面沿着 y 轴反转一下就可以正确的显示了。使用 transform:scale(-1,1) 原本左侧这张图是正面看时的视角,右侧图是反面看的视角

想法

  1. 将两张图合成一张时通过定位将图重叠在一起。
  2. 反转整张图时可以看到一页的两面
<div class='merge'>
          <img src="https://static-zh.wxb.com.cn/karazhan/content/article/2020/1/16f8334cdef.jpg" />
          <img src="https://static-zh.wxb.com.cn/karazhan/content/article/2020/1/16f82fce679.jpg" />
</div>  

实际效果
发现旋转时无论转了多少角度都只能看见图二,因为图二的层级永远比图一高。

解决办法
使用 3d 视角来实现层级的改变
1.首先将父级设置为3d视角 2. 然后将图一的 Z 方向移动 1px ,z方向代表和用户的距离,本来图一图二的z方向是相同的,但是图二的层级高,所以看到的是图二,现在将图一向前移动1px,自然看到的是图一了。

.merge{
  position: relative;
  transform-style: preserve-3d ;
  transform-origin: left center;
}
img{
  width:200px;
  height: 300px;
  position: absolute;
  top:0;
  left: 0;
  background-color: #fff;
  &:nth-child(1){
    transform: translateZ(1px);
  }
}

为什么不使用 z-index
使用z-index 来改变层级是,由于两张图片还是在一个层级上,所以无论图片怎么反转,总有一张图片会始终覆盖另外一张图片。所以我们还得切换它们的z-index的层级,很麻烦。而 translateZ 是先后导致的用户看到的层级关系,所以反转的时候后面的图片就会被翻转到前面。

单页demo完整代码

制作多页

多页翻转时会遇到层级问题,还是会出现只会显示最后一组图片,因为它的层级最高。

解决方式
首先要清楚右侧的第一张图片(即将翻页的那张图)必须显示在最上面,而左侧的图片(已翻转的图片)显示最后一张图片就达到我们的想要的效果了。

  1. 多张页面的时候,一般我们使用遍历的方式,只需要把即将翻转的页面的 z-index 提到最高就实现了右侧的翻转的图片层级问题。而左侧的本身最后一张的层级会比之前高,所以我们不需要设置任何东西。
// 通过 isSelected 来控制层级问题
<div className="page-wrap">
        {
          list.map((item,index)=>{
            return <Merge {...item} isSelected={selectedIndex===index} rotateY={selectedIndex<=index ?0:rotateY}/>
          })
        }
      </div>
      
// merge 组件
render(){
    let {rotateY,left,right,isSelected} = this.props
    return <div className='merge' style={{transform:`rotateY(${rotateY}deg)`,zIndex:isSelected?99:0}}> 
          <img src={left} />
          <img className="image" src={right} />
     </div>   
  }

发现问题
当子项直接为图片是需要给图片设置 backgroundborder 才能是 3d 改变层级的效果生效。而图标外层包一层div并不会出现该问题。我将div设置为 inline / inline-block 也不会出现该问题。目前还没搞清楚啥原因?

多页demo完整代码

进阶无限翻页效果

上文中实现了📖翻页效果,试想下一本书如果有几百页,那么我们需要创建一个页的dom,能不能尝试用最少的dom结构完成这些操作。 尝试用3页来模拟整本书的翻阅效果。

想法

  1. 通过下面这种图来分析翻书整个过程我把它分为 左中右三个部分
  2. 当中间页翻转到左侧时将左侧页回置到右侧,同时右侧翻转到中间页 这样实现了三张图片到循环翻转效果。

待解决的问题

  1. 翻转效果是通过 transform:rotateY(deg) 来控制,通过控制整个角度来实现翻页效果
  2. 从中间件页翻转到左侧是需要过渡效果的,从左侧页翻转到右侧是不需要过渡效果
  3. 层级问题,将中间页到层级设置为三张最高的,之所以选择三张图片来模拟整本书的效果,也是因为三张图片在翻转的时候层级问题解决比较简单些。

方案

  1. 还是将每页抽离成组件基本代码如下
function Single (props){
    let {position,left,right} = props
    let rotateY = 0
    let zIndex = 0
    let duraction = 0
    // 从中间页翻转到左侧
    let isLeft = position === 'left'
    let isMiddle = position === 'middle'
    let isRight = position === 'right'
    if(isLeft){
        rotateY = 180
    }
    if(isMiddle){
        zIndex = 99
    }
    if(isLeft||isMiddle){
        duraction = 1
    }
    
    return <div className='merge' style={{transform:`rotateY(${rotateY}deg)`,zIndex,transitionDuration:duraction +'s'}}> 
          <img src={left} />
          <img className="image" src={right} />
     </div>   
}
  1. 控制书页的位置,初始化数据如下,left / middle / right 分别代表 左侧页面/中间页面/右侧页面
constructor(){
  super()
  this.state = {
    // 所有页面列表
    list:[],
    // 实际展示页面的列表
    displayList:[],
    // 管理位置列表
    positionList:['middle','right','right'],
  }
}
  1. 当我们点击下一页时,需要改变 positionList 中的位置的值 middle->left , left->right , right->middle
// 找到需要middle页的index
    let rightIndex = this.state.positionList.findIndex(item=>item==='right')
    // 找到 left 页的index
    let leftIndex = this.state.positionList.findIndex(item=>item==='left')
    // 找到 middle 页的index
    let middleIndex = this.state.positionList.findIndex(item=>item==='middle')
    let path = ''
    // 将left页进行翻转
    if(leftIndex!==-1){
      this.state.positionList[leftIndex] = 'right'
    }
    // 将中间一页翻转到left
    if(middleIndex!==-1){
      this.state.positionList[middleIndex] = 'left'
    }
    // rgiht 中第一张进行翻转
    this.state.positionList[rightIndex] = 'middle'

测试
按照我们预想的逻辑后测试发现,左侧那页还没等中间页翻转到,已经跑到右侧页面了。我们需要延迟左侧翻转到动作。
但是翻转是由 rotateY 来控制,是父级 props 传递过来的,子组件不能控制。想了一个办法就是在左侧的位置定位一张图片,该图片的地址和左侧图片的地址保持一致,每次点击下一页都动态修改。
这里我们只是相同的三张图片进行无限翻转,我们需要在翻转的时候加入新的图片。

加入新页面
为了便于分析用 0/1/2 分别代表 左侧/中间/右侧 页面。那么初始状态下我们设置的是 122 也就是 一张中间图两张设置为右侧的图片,来分析下何时需要加入新图
122 不需要替换图片 012 不需要替换图片 201 需要替换图片,因为相当于最开始的第一页又回到开始,我们需要将第一页到数据更新就产生了新到页面。

无限翻页完整demo

最后

通过手动实现翻页📖的效果,又可以开心的学习,对css了解更多一些。一开始想做这个效果思路有点乱,发现在开发前用文档记录下自己的思路,一个个解决进一步梳理比较有效果。下图是在实现过程中梳理的想法,有助于自己一步步解决问题。 关于动画效果如果有好的实现方式欢迎在评论区留下意见。