实现
制作一页
我们需要将两张图合成一张有正反两面。这里需要将反面沿着 y 轴反转一下就可以正确的显示了。使用 transform:scale(-1,1)
原本左侧这张图是正面看时的视角,右侧图是反面看的视角
想法
- 将两张图合成一张时通过定位将图重叠在一起。
- 反转整张图时可以看到一页的两面
<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 是先后导致的用户看到的层级关系,所以反转的时候后面的图片就会被翻转到前面。
制作多页
多页翻转时会遇到层级问题,还是会出现只会显示最后一组图片,因为它的层级最高。
解决方式
首先要清楚右侧的第一张图片(即将翻页的那张图)必须显示在最上面,而左侧的图片(已翻转的图片)显示最后一张图片就达到我们的想要的效果了。
- 多张页面的时候,一般我们使用遍历的方式,只需要把即将翻转的页面的 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>
}
发现问题
当子项直接为图片是需要给图片设置 background
或 border
才能是 3d 改变层级的效果生效。而图标外层包一层div并不会出现该问题。我将div设置为 inline / inline-block 也不会出现该问题。目前还没搞清楚啥原因?
进阶无限翻页效果
上文中实现了 翻页效果,试想下一本书如果有几百页,那么我们需要创建一个页的dom,能不能尝试用最少的dom结构完成这些操作。 尝试用3页来模拟整本书的翻阅效果。
想法
- 通过下面这种图来分析翻书整个过程我把它分为 左中右三个部分
- 当中间页翻转到左侧时将左侧页回置到右侧,同时右侧翻转到中间页 这样实现了三张图片到循环翻转效果。
待解决的问题
- 翻转效果是通过
transform:rotateY(deg)
来控制,通过控制整个角度来实现翻页效果 - 从中间件页翻转到左侧是需要过渡效果的,从左侧页翻转到右侧是不需要过渡效果
- 层级问题,将中间页到层级设置为三张最高的,之所以选择三张图片来模拟整本书的效果,也是因为三张图片在翻转的时候层级问题解决比较简单些。
方案
- 还是将每页抽离成组件基本代码如下
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>
}
- 控制书页的位置,初始化数据如下,left / middle / right 分别代表 左侧页面/中间页面/右侧页面
constructor(){
super()
this.state = {
// 所有页面列表
list:[],
// 实际展示页面的列表
displayList:[],
// 管理位置列表
positionList:['middle','right','right'],
}
}
- 当我们点击下一页时,需要改变 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 需要替换图片,因为相当于最开始的第一页又回到开始,我们需要将第一页到数据更新就产生了新到页面。
最后
通过手动实现翻页 的效果,又可以开心的学习,对css了解更多一些。一开始想做这个效果思路有点乱,发现在开发前用文档记录下自己的思路,一个个解决进一步梳理比较有效果。下图是在实现过程中梳理的想法,有助于自己一步步解决问题。
从最零基础开始的的HTML+CSS+JavaScript。jQuery,Ajax,node,angular框架等到移动端HTML5的项目实战【视频+工具+系统路线图】都有整理,在线解析,学习指导,点:【WEB前端学习圈⑤】