CSS+JS:识字始翻书——animation定格@keyframes动画初探
本帖最后由 马黑黑 于 2022-4-26 19:14 编辑关键词:forwards, 定格关键帧动画, @keyframes, JS, css
迄今为止,我们使用关键帧动画的方式都是循环(infinite)或循环+往复(infinite alternate)的,从未考虑和尝试过动画的定格。其实动画定格拥有足够多的应用场景,例如翻书就需要定格关键帧动画:一页书翻开后就让已完成的动作停住,否则我们就必须具备一目十行的阅读能力——显然这是多数人办不到的。本帖就以翻书为例,初步领略一下关键帧动画的定格设置。
我们先设计一个简单粗糙的书本样式。先看CSS部分:
.book {
margin: auto;
margin-top: 100px;
width: 700px;
height: 440px;
perspective: 1000px;
cursor: pointer;
border: 1px solid;
padding: 10px;
position: relative;
}
.page {
background: #ccc;
padding: 20px;
width: 310px;
height: 400px;
left: 50%;
border-left: 1px solid #eee;
transform-origin: left;
transform-style: preserve-3d;
position: absolute;
}
.book选择器其实是个容器,给书页提供存在与页翻页的场所,注意它设置了景深——翻书不能是2d的。为了便于观察,我们还设置了边框。
.page选择器就是书页了,注意设置了3d转换模式、转换基点,还有尺寸、定位等属性的设置都是根据.book选择器以及需要而定。
CSS代码还需要两个关键帧动画:
@keyframes rot1{
from { transform: rotateY(0deg); }
to { transform: rotateY(-180deg); }
}
@keyframes rot2{
from { transform: rotateY(-180deg); }
to { transform: rotateY(0deg); }
}
第一个是往后面翻页,第二个是往回翻页,注意二者 from 和 to 角度的变化。
深度简化的HTML代码如下:
<div class="book">
<div class="page" style="z-index:2">1</div>
<div class="page" style="z-index:1">2</div>
</div>
哎呀,我们这本书怎么才两页?管它呢,咱任性一回行不?再说了,书可不一定是越厚越好,在大力倡导节能减排的时代,我们要写薄而美的书。
到了这里,我们还没看到关键帧动画定格的影子。嗯,这是核心所在,我们这就讨论它。我们已经知道,关键帧动画的调用是元素通过animation属性实现的,比如我们已经熟知的下面的语句:
animation: rot1 2s linear infinite alternate;
使用上面语句的元素将调用名为 rot1 的关键帧动画(@keyframes rot1,我们此前设置的动画之一),即从 0deg 到 -180deg 沿Y轴旋转,2秒钟一个运行周期,匀速运行,重复运行,往复(亦即原路返回)运行。显然,这不是我们想要的翻书状态,所以,我们把它改为:
animation: rot1 0.5s linear forwards;
这将翻好书页后停住不动,定格在 rotateY(0deg) 到 rotateY(-180deg) 变换过程中的最后一帧(书页的翻转恰好是-180度,从0deg到-180deg有多少针是CSS和计算机的事情,我们所关心的仅是首帧和最后一帧)。forwards 本意是向前,在此表示向前定格(在最后一帧)。
OK,这是我们需要的,但如果在CSS代码流里某一个元素选择器中调用,则失去了交互性,它运行一次,停住。我们的期望是我们通过鼠标的交互操作来完成翻开书页的动作,这就需要用到JS了:
let page = document.querySelectorAll(".page"); //①首先……
let idx = 0; //②其次……
//③最后……
document.querySelector(".book").onclick = function(){
if(idx == 0){
page.style.animation = "rot1 .5s linear forwards";
idx = 1;
} else {
page.style.animation = "rot2 .5s linear forwards";
idx = 0;
}
}
①首先,获得书页数组,page变量通过 document.querySelectorAll() 内置函数得到所有书页的操作句柄(HTML里我们设置了两个页面),以数组形式存在,稍后我们将对第一页即 page 进行翻过去再翻回来的操作。
②其次,我们声明了一个 idx 变量并给它赋值,这是将书页翻过去和翻回来的操作标识(依据),为0时翻过去,其他任何值时翻回来。
③最后,我们给书本的鼠标点击动作编写一个函数,该函数判断 idx 翻书行为标识值来决定调用 rot1 还是 rot2 关键帧动画,过程中还要修改 idx 的值以便下让一次的鼠标点击操作能有正确的结果。
animation的调用其实还是通过CSS实现,JS仅仅是控制什么时候让哪个元素运行什么动画、以什么方式运行动画,交互由此达成。并不是说CSS没有任何交互能力(想一想hover),但交互绝非CSS的特长,JS才是干这个的好手。
OK,大功告成,现在我们可以对第一页书页(其实应该是封面,缺少美工只好让它充当书页吧)进行交互性质的翻过来翻过去的操作了。
PS:从代码位置上看,forwards 虽然替代了 infinite|alternate,二者的地位却不是一样的:infinite|alternate设定动画执行的行为方式或特征(循环|往复);forwards 隶属于 animation-fill-mode 范畴,是动画的“填充”样式,它的“同僚”还有 none(默认)、backwards 和 both,它们实际上用于设定动画执行之前和(或)之后的状态,我们在此暂时不去对它们进行深入探讨。
效果在下面演示。
<style>
.book {
margin: auto;
margin-top: 100px;
width: 700px;
height: 440px;
perspective: 1000px;
cursor: pointer;
border: 1px solid;
padding: 10px;
position: relative;
}
.page {
background: #ccc;
padding: 20px;
width: 310px;
height: 400px;
left: 50%;
border-left: 1px solid #eee;
transform-origin: left;
transform-style: preserve-3d;
position: absolute;
}
@keyframes rot1{
from { transform: rotateY(0deg); }
to { transform: rotateY(-180deg); }
}
@keyframes rot2{
from { transform: rotateY(-180deg); }
to { transform: rotateY(0deg); }
}
</style>
<div class="book">
<div class="page" style="z-index:2">1</div>
<div class="page" style="z-index:1">2</div>
</div>
<script>
let page = document.querySelectorAll(".page");
let idx = 0;
document.querySelector(".book").onclick = function(){
if(idx == 0){
page.style.animation = "rot1 .5s linear forwards";
idx = 1;
} else {
page.style.animation = "rot2 .5s linear forwards";
idx = 0;
}
}
</script> 二楼完整代码:
<style>
.book {
margin: auto;
margin-top: 100px;
width: 700px;
height: 440px;
perspective: 1000px;
cursor: pointer;
border: 1px solid;
padding: 10px;
position: relative;
}
.page {
background: #ccc;
padding: 20px;
width: 310px;
height: 400px;
left: 50%;
border-left: 1px solid #eee;
transform-origin: left;
transform-style: preserve-3d;
position: absolute;
}
@keyframes rot1{
from { transform: rotateY(0deg); }
to { transform: rotateY(-180deg); }
}
@keyframes rot2{
from { transform: rotateY(-180deg); }
to { transform: rotateY(0deg); }
}
</style>
<div class="book">
<div class="page" style="z-index:2">1</div>
<div class="page" style="z-index:1">2</div>
</div>
<script>
let page = document.querySelectorAll(".page");
let idx = 0;
document.querySelector(".book").onclick = function(){
if(idx == 0){
page.style.animation = "rot1 .5s linear forwards";
idx = 1;
} else {
page.style.animation = "rot2 .5s linear forwards";
idx = 0;
}
}
</script>
这个,好像html里可以一直写下去的吧,这样就有更多页了{:4_173:} 哦,貌似不行,js只对第一页进行翻转的,多写多少页都没用的,看我笨的{:4_173:} 这个有趣,黑黑真棒{:4_187:} 本帖最后由 马黑黑 于 2022-4-26 21:12 编辑
红影 发表于 2022-4-26 20:43
这个,好像html里可以一直写下去的吧,这样就有更多页了
那样就很复杂了,为了演示forwards,它不能太复杂,否则会眼花缭乱。 红影 发表于 2022-4-26 20:47
哦,貌似不行,js只对第一页进行翻转的,多写多少页都没用的,看我笨的
不是不行,这里面涉及到很复杂的一系列的算法问题,诸如书页子元素之间的z-index如何设置、显示的内容如何倒转过来(否则翻页后有一面的反镜像的)、翻页到边界如何判断、如何处理,等等等等。本帖示例仅针对第一页进行翻页演示,这样可以简化一些问题,从而突出主题。这是提前说明了的。 红影 发表于 2022-4-26 20:49
这个有趣,黑黑真棒
记得你发过一个基于jQuery库的翻页帖子,它很复杂的,纯JS做就会更复杂。这里只是开端。 马黑黑 发表于 2022-4-26 21:08
那样就很复杂了,为了演示forwards,它不能太复杂,否则会眼花缭乱。
嗯嗯,知道了,这个是演示forwards的,简单明了就好{:4_187:} 马黑黑 发表于 2022-4-26 21:11
不是不行,这里面涉及到很复杂的一系列的算法问题,诸如书页子元素之间的z-index如何设置、显示的内容如 ...
嗯,看到那个“1”倒转了,要弄多的翻页,要考虑的太多了。 马黑黑 发表于 2022-4-26 21:13
记得你发过一个基于jQuery库的翻页帖子,它很复杂的,纯JS做就会更复杂。这里只是开端。
那个我只是套用的,那个需要上传呢{:4_173:} 红影 发表于 2022-4-26 23:35
那个我只是套用的,那个需要上传呢
主要是论坛所用的JS库多种,其中一个限制了jQuery的使用,不然可以直接弄的 红影 发表于 2022-4-26 23:35
嗯,看到那个“1”倒转了,要弄多的翻页,要考虑的太多了。
嗯,不过这些问题都可以解决 红影 发表于 2022-4-26 23:34
嗯嗯,知道了,这个是演示forwards的,简单明了就好
这个书要搞定,纯JS+CSS的翻页效果很多人弄,都不太理想 马黑黑 发表于 2022-4-26 23:43
主要是论坛所用的JS库多种,其中一个限制了jQuery的使用,不然可以直接弄的
在这种情况下作改进真不容易{:4_187:} 马黑黑 发表于 2022-4-26 23:43
嗯,不过这些问题都可以解决
黑黑真棒{:4_187:} 马黑黑 发表于 2022-4-26 23:44
这个书要搞定,纯JS+CSS的翻页效果很多人弄,都不太理想
能做出来就很不容易呢{:4_187:} 红影 发表于 2022-4-27 12:32
能做出来就很不容易呢
肯定做得出来 红影 发表于 2022-4-27 12:31
黑黑真棒
一般般吧