这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

本篇主要讨论以下两种翻书动画的实现:

第一种是整页翻转的效果:

这种整页翻转的效果主要是做rotateY的动画,并结合一些CSS的3d属性实现。

第二种折线翻转的效果,如下图所示:

主要是通过计算页面翻折过来的位置。

这两种原理上都不是很复杂,需要各个细节配合好,形成一个连贯的翻书动画。

我们先重点说一下第一种翻页效果的实现。

1. 基本布局

这种的实现相对比较简单,我们先把DOM结构准备好,如下代码所示:

<ul class="pages">
<!--一个li.paper包含了正反两页-->
<li class="paper" data-left>
<!--一个.page就是一页内容-->
<div class="page page-1-back">
<img src="1.jpg" alt>
</div>
<div class="page page-1">
<img src="2.jpg" alt>
</div>
</li>
<li class="paper" data-right>
<div class="page page-2">
<img src="3.jpg" alt>
</div>
<div class="page page-2-back">
<img src="4.jpg" alt>
</div>
</li>
<!--其它页内容省略-->
</ul>

一个li.paper就表示一张纸,包含了正反两页,data-left属性表示它是在左边的,而data-right表示是在右侧,通过absolute定位把它们放到相应的位置。所以如果是下一页,应该让data-right做左翻的动画,相反上一页则让data-left做右翻的动画。

.page-1是当前显示在左边的那一页,.page-2表示当前右边的那一页,而.page-1-back和.page-2-back则分别表示在.paeg-1和.page-2后面的那一页。它们置于背后是水平翻转的,这一点应该不难想象,所以需要借助transform: scale水平翻转一下:

.page-1-back,
.page-2-back {
transform: scale(-1, 1);
}

并且.page-1的z-index要比在后面的.page-1-back要高:

.page-1,
.page-2 {
z-index: 1;
}

通过这样排版之后,就得到了以下的布局:

接下来让右边的那一页翻过来。

2. 翻书动画

就是做.paper的rotateY动画,很简单,如下代码所示:

@keyframes flip-to-left {
from {
transform: rotateY(0);
}
to {
transform: rotateY(-180deg);
}
}
.paper[data-right] {
transform-origin: left center;
animation: flip-to-left 2s ease-in-out;
}

需要设置变换中心为左边中间的位置,效果如下:

我们发现有几个问题,第1个问题是翻过去的后面的那个paper没有显示出来,因为一开始把没显示出来的paper都隐藏了,所以需要把后面那个paper显示出来:

.paper {
display: none;
position: absolute;
/* 默认放在右边 */
right: 0;
}
.paper[data-left],
.paper[data-right] {
display: block;
z-index: 1;
}
.paper[data-left] {
right: auto;
left: 0;
}
/* 把相邻的paper显示出来 */
.paper[data-right] + .paper {
display: block;
}

这样翻过来之后就能显示后面的那个paper了,如下图所示:

第二个问题是:为什么.page-2-back没显示出来,仍然显示的是.page-2,猜测是因为.page-2的z-index比较高,把.page-2-back盖住了,所以即使整体rotate属性变了,它也是被盖住的状态。

所以第一个方法可以在翻转一半的时候就把z-index的高低关系互换一下,让page-2-back比page-2更高,但是这个方法不太好控制,因为动画的变化不是linear的,即使是linear的这个方法也不灵活,容易出现闪动的情况。

第二个方法是调整它们俩的translateZ关系,让page-2的translateZ值比page-2-back高1px就可以了,而不是直接设置z-index关系。为了让translateZ能生效,需要设置它们容器的transform-style为preserve-3d,如下代码所示:

.paper {
transform-style: preserve-3d;
}
.page-1,
.page-2 {
transform: translateZ(1px);
}

这个可以让子元素从扁平空间(flat)变成一个3维空间,translateZ就能发挥作用了,效果如下所示:

这样基本的效果就出来了,但是总感觉哪里不太对,就是这个翻转有点平,没有景深的效果。说到景深会想起另外一个CSS属性transform-perspective,我们不妨给它加一个perspective看看效果如何:

@keyframes flip-to-left {
from {
transform: perspective(1000px) rotateY(0);
}
to {
transform: perspective(1000px) rotateY(-180deg);
}
}

效果如下图所示:

这样看起来感觉就立体多了。perspective可以理解为摄像机的位置,如果值越大摄像机就推得越远。不同值对比如下:

这样翻书的动画基本就完成了,从左向右翻也是同样道理。接下来的问题是怎么形成连续翻书的动画。

3. 连续翻书

可以给动画加一个forwards属性,让动画保持在最后结束的那个状态:

.paper[data-right] {
transform-origin: left center;
animation: flip-to-left 2s ease-in-out forwards;
}

停住之后,上面那些类的关系需要重新更新一下,例如翻过来之后原本的.page-2-back会变成.page-1。

比较科学的方法是使用element.animate做动画,因为它有一个onfinish的回调告诉我们动画结束了,动画由于这个API的兼容性不是很好,要么找个polyfill,要么还是用上面CSS的方法然后借助setTimeout。polyfill的库比较大,这里还是用setTimeout模拟动画结束,使用setTimeout的风险是可能会不太准。

代码逻辑比较简单,就是找到对应的dom结点设置对应的类或者属性,就是代码比较繁琐一点,如下所示:

let currentPage = 1;
let $ = document.querySelector.bind(document);
$('#next-page').addEventListener('click', goToNextPage);
const flipAnimateTime = 1000;
function goToNextPage () {
// 触发CSS动画
$('.paper[data-right]').setAttribute('data-begin-animate', true);
setTimeout(() => {
// data-right变成data-left
let $rightPaper = $('.paper[data-right]'),
$leftPaper = $('.paper[data-left]');
$rightPaper.removeAttribute('data-right');
$rightPaper.setAttribute('data-left', true);
// data-left没有了
$leftPaper.removeAttribute('data-left');
$leftPaper.querySelector('.page-1').classList.remove('page-1');
$leftPaper.querySelector('.page-1-back').classList.remove('page-1-back');
// 重新设置类的关系
$leftPaper = $rightPaper;
$rightPaper = $leftPaper.nextElementSibling;
$leftPaper.querySelector('.page').classList.add('page-1-back');
$leftPaper.querySelector('.page + .page').classList.add('page-1');
// 如果还有下一页
if ($rightPaper) {
$rightPaper.setAttribute('data-right', true);
$rightPaper.querySelector('.page').classList.add('page-2');
$rightPaper.querySelector('.page + .page').classList.add('page-2-back');
}
currentPage++;
}, flipAnimateTime);
}

效果如下图所示:

向左翻页也是类似。

这里有个问题,如果用户点下一页点得很快那应该怎么办?如果他点得很快的话前面的翻页还没有结束,会导致setTimeout里的代码还没有执行,那么整个模型就乱了。有两个解决方法,第一种是在翻页过程中禁掉下一页的操作,但是似乎不太友好,第二种是把翻页的过程当作一个task任务,一旦点了下一页或者上一页,就push一个task进来,每个task按顺序同步执行,如果task数组长度大于1那么就缩短动画的时间,让它翻得快一点。相似的处理我已经在《实现内部组件轮播切换效果》讨论过,这里不再重复。

4. 适配的问题

你可能会担心动画结束后修改了dom结构,导致CSS属性变了会闪一下,例如原本的page-2-back是水平翻转的,但是在JS里面设置了之后它就变成非水平翻转了,虽然展示的效果是一样的,但是会不会闪一下呢?只要改之前和改之后浏览器进行layout计算的结果一模一样它就不会闪的,就像上面的例子,但是一旦位移差了1px,就会有闪动。

在实际的例子,你可能需要中间有1px的书缝的阴影,所以左右页的宽度就不是刚好50%,而是要减去1px,所以如果你的transform-origin还是left center的话翻过去之后就会往右移了1px,当动画结束重置状态,1px的偏移就会被修正,这个时候就会小闪一下。而当你把transform-origin改成-1px center之后,又会导致翻过去之后往左移了1px。所以最好别把中间的阴影单独弄出来,可以改成在每一个page里面用before/after画,每个page还是要占50%,这样就没问题。

另外一个要考虑的问题是,使用了transform: scale + translateZ可能会导致模糊,一个直接的例子可以见这个codepen,就是因为用了translateZ或者will-change: transform等触发了GPU渲染导致模糊了,这个过程可能是浏览器把当前图层截一张图给GPU计算,GPU把这张静态图缩放就会模糊。而当我们把translateZ等有promotion提升作用的属性去掉之后,在缩放的过程会模糊,但是最终状态是清晰的。如下图所示:

5. 变成一个插件

当把上面的问题都解决了之后,可以把它变成一个插件,用的人只要引入,然后初始化一下就搞定了,不用关心这些类怎么变之类的问题。

并且,由于一个paper容器有两个page是正反面的关系,一旦中间突然插入了一页就会导致page的正反面关系发生变化,所以这个结构不是很灵活,最好是动态生成,也就是说使用插件的人,把所有的page并列排就好了,然后在插件里面再重新组织下DOM结构,把在正反面的两个page放到一个paper里面。

接着讨论下第二种翻书效果的实现。

6. 折线翻书效果的实现

这个有一个现成的插件turn.js,使用起来非常简单,我们简单讨论一下它的内部实现。

这个东西乍看一下,似乎有曲面的效果:

但实际上是没有的,这个曲线效果是它添加的阴影和渐变产生的视觉效果,当我们把background-image的渐变去掉之后对比一下就能看出来了:

没有渐变的伪装之后一下子就平了。它就变成了一个折纸的模型——给定一张纸和一个折过去的点,计算一下折过去的旋转角度和位移。它的源代码是在fold函数里面计算的:

它里面有各种余弦正弦的计算和角度的判断,具体实现还是比较复杂的,没有深入去研究,代码可见turn.js.

还有一个问题是它是怎么实现三角形裁剪展示的效果?它是在上层又盖了一个div:

7. 小结

本文讨论了两种翻书效果的实现,重点讨论了一下比较简单的整体翻页的方式,这种方式主要是做rotateY动画,同时打开perspective让其具有景深效果,并且用preserve-3d结合translateZ制造上下层级关系,这种方式可能会存在闪动和模糊的问题,为了让翻过去不会闪动关键的地方是保证每一个page占宽50%,模糊的问题是因为用了scale加上GPU提升导致的,所以只能通过不写3d属性保证清晰。

第二种的效果模型相对比较复杂,简单分析了一下它的原理和实现方式。主要是计算折纸过来的角度和位移,上层再盖一个div隐藏不露出来的部分。然后再加上阴影和渐变制造一种曲面的效果。这种翻书的效果还是挺好玩的。

本文转载于:

https://juejin.cn/post/6844903665216520206

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--怎么实现一个3d翻书效果的更多相关文章

  1. HTML5 3D翻书效果(双面效应)

    最后使用HTML5翻书效果达到测试,比较简单,它的升级版是 最后一个问题: 1)后,原来的页面连环画将成为一面镜子 2)无法实现双面翻书. 3)明显感觉页面似有近遮挡标志. 这次的升级版本号实现过程比 ...

  2. 纯css实现翻书效果

    前言 最近研究了一下css3的3D效果,写了几个demo,写篇博客总结一下实现的经过.PS:如果对transform-origin/perspective/transform-style这些概念还不了 ...

  3. 15 个最佳 jQuery 翻书效果插件

    本文为你带来15个非常实用的.实现类似翻书效果的jQuery插件,你可以很容易地整合到你的web应用中,提升用户体验. 1.  BookBlock BookBlock可以将任何内容(如图像.文本)创建 ...

  4. turn.js实现翻书效果

    JS插件网 http://www.ijquery.cn/?p=173 描述:Turn.js 是一个轻量级的 (15kb) jQuery/html5 插件用来创建类似书本和杂志翻页效果,支持触摸屏设备. ...

  5. 基于CSS3新属性Animation及transform实现类似翻书效果

    注:本实例JS部分均以原生JS编写,不善用原生JS的,可用jQuery等对三方框架改写 先上效果图:(样式有点丑,可以忽略一下下,效果出来了就好,后期加到其他项目中方便更改0.0) 类似翻书效果,原本 ...

  6. Turn.js 实现翻书效果的学习与总结

    最近CTO给我分配了一个移动端H5开发的任务,主要功能是需要实现翻书效果,我听过主要需求后,当时是呀!!!接下来自己尝试使用fullPage.js和Swiper来实现翻书效果,结果效果都不是非常的理想 ...

  7. [知了堂学习笔记]_css3特效第一篇--旋转的背景&翻书效果

    一.html遮盖层与css3的旋转动画 >效果图(加载可能会慢一点儿,请稍等...): >实现思路:在一个大的div中装入一个底层img和顶层的div(里面的内容按照以上图片呈现的样式布局 ...

  8. Andorid 翻书效果

    本文内容 项目结构 环境 演示 参考资料 翻书效果,主要采用绘制贝塞尔曲线的方法.本文有三个演示: 简单翻书效果.翻下一页后,当前页不会消失. 翻书时的贝塞尔曲线.演示翻书时,贝塞尔曲线的路径和要素. ...

  9. UIScrollView翻书效果

    代码地址如下:http://www.demodashi.com/demo/12695.html 前言:看到凤凰新闻 头条栏目的编辑推荐新闻是这个效果,觉得不错,就想着实现一下,以下就是我的实现过程. ...

  10. css3特效第一篇--旋转的背景&翻书效果

    一.html遮盖层与css3的旋转动画 >效果图(加载可能会慢一点儿,请稍等...): >实现思路:在一个大的div中装入一个底层img和顶层的div(里面的内容按照以上图片呈现的样式布局 ...

随机推荐

  1. 看似简单的input框输入竟然异常卡顿,记一个日常性能问题的排查思路

    壹 ❀ 引 我们公司产品主要提供企业项目管理服务,那么自然有配套的desk工单管理系统,用于搜集客户bug以及相关问题反馈.有一天我在测试功能时碰巧发现了一个bug,所以就想着提一个工单记录下方便日后 ...

  2. NC23501 小A的回文串

    题目链接 题目 题目描述 小A非常喜欢回文串,当然我们都知道回文串这种情况是非常特殊的.所以小A只想知道给定的一个字符串的最大回文子串是多少,但是小A对这个结果并不是非常满意.现在小A可以对这个字符串 ...

  3. python web连接mysql数据库

    一定要commit,否则数据库不会发生改变!!! 1.使用python写入内容到数据库 import pymysql # 记得下载并引入pymysql # 1.连接mysql,db:声明数据库 con ...

  4. Spring源码之springMVC

    目录 web.xml 程序入口 servlet 初始化 运行阶段 销毁阶段 DispatcherServlet 初始化 DispatcherServlet 的逻辑处理 web.xml 它的作用是配置初 ...

  5. 使用 Hugging Face 微调 Gemma 模型

    我们最近宣布了,来自 Google Deepmind 开放权重的语言模型 Gemma现已通过 Hugging Face 面向更广泛的开源社区开放.该模型提供了两个规模的版本:20 亿和 70 亿参数, ...

  6. C++ GDAL用CreateCopy()新建栅格并修改波段的个数

      本文介绍基于C++语言GDAL库,为CreateCopy()函数创建的栅格图像添加更多波段的方法.   在C++语言的GDAL库中,我们可以基于CreateCopy()函数与Create()函数创 ...

  7. MySQL Boolean类型的坑

    MySQL中,Boolean只是 tinyint(1) 的别名,也就是说,MySQL中并没有真正的bool类型. 而SQLAlchemy生成SQL的时候并没有检测到 这一点,这就导致一个问题,当使用 ...

  8. protobuf简单示例

    user.proto syntax = "proto3"; package demo; option go_package = "./pb"; //指定go_o ...

  9. 【WiFi开发全攻略】WIFI基础知识大全

    [WiFi开发全攻略]WIFI基础知识大全 1. Wi-Fi起源 现在我们大家对Wi-Fi肯定都不陌生,无论是笔记本,手机,智能电视,都离不开Wi-Fi.目前我们一般用的都是Wi-Fi5,Wi-Fi6 ...

  10. Cocos Creator 2.x升级至Cocos Creator 3.x

    1.导入类时,批量导入 2.导入 override...关键字时,批量导入 3.this.node.scale = 0.6-->this.node.setScale(0.6, 0.6); 4.n ...