马黑黑 发表于 2022-4-26 19:02

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,它们实际上用于设定动画执行之前和(或)之后的状态,我们在此暂时不去对它们进行深入探讨。

效果在下面演示。

马黑黑 发表于 2022-4-26 19:04

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

马黑黑 发表于 2022-4-26 19:05

二楼完整代码:
<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>

红影 发表于 2022-4-26 20:43

这个,好像html里可以一直写下去的吧,这样就有更多页了{:4_173:}

红影 发表于 2022-4-26 20:47

哦,貌似不行,js只对第一页进行翻转的,多写多少页都没用的,看我笨的{:4_173:}

红影 发表于 2022-4-26 20:49

这个有趣,黑黑真棒{:4_187:}

马黑黑 发表于 2022-4-26 21:08

本帖最后由 马黑黑 于 2022-4-26 21:12 编辑

红影 发表于 2022-4-26 20:43
这个,好像html里可以一直写下去的吧,这样就有更多页了
那样就很复杂了,为了演示forwards,它不能太复杂,否则会眼花缭乱。

马黑黑 发表于 2022-4-26 21:11

红影 发表于 2022-4-26 20:47
哦,貌似不行,js只对第一页进行翻转的,多写多少页都没用的,看我笨的

不是不行,这里面涉及到很复杂的一系列的算法问题,诸如书页子元素之间的z-index如何设置、显示的内容如何倒转过来(否则翻页后有一面的反镜像的)、翻页到边界如何判断、如何处理,等等等等。本帖示例仅针对第一页进行翻页演示,这样可以简化一些问题,从而突出主题。这是提前说明了的。

马黑黑 发表于 2022-4-26 21:13

红影 发表于 2022-4-26 20:49
这个有趣,黑黑真棒

记得你发过一个基于jQuery库的翻页帖子,它很复杂的,纯JS做就会更复杂。这里只是开端。

红影 发表于 2022-4-26 23:34

马黑黑 发表于 2022-4-26 21:08
那样就很复杂了,为了演示forwards,它不能太复杂,否则会眼花缭乱。

嗯嗯,知道了,这个是演示forwards的,简单明了就好{:4_187:}

红影 发表于 2022-4-26 23:35

马黑黑 发表于 2022-4-26 21:11
不是不行,这里面涉及到很复杂的一系列的算法问题,诸如书页子元素之间的z-index如何设置、显示的内容如 ...

嗯,看到那个“1”倒转了,要弄多的翻页,要考虑的太多了。

红影 发表于 2022-4-26 23:35

马黑黑 发表于 2022-4-26 21:13
记得你发过一个基于jQuery库的翻页帖子,它很复杂的,纯JS做就会更复杂。这里只是开端。

那个我只是套用的,那个需要上传呢{:4_173:}

马黑黑 发表于 2022-4-26 23:43

红影 发表于 2022-4-26 23:35
那个我只是套用的,那个需要上传呢

主要是论坛所用的JS库多种,其中一个限制了jQuery的使用,不然可以直接弄的

马黑黑 发表于 2022-4-26 23:43

红影 发表于 2022-4-26 23:35
嗯,看到那个“1”倒转了,要弄多的翻页,要考虑的太多了。

嗯,不过这些问题都可以解决

马黑黑 发表于 2022-4-26 23:44

红影 发表于 2022-4-26 23:34
嗯嗯,知道了,这个是演示forwards的,简单明了就好

这个书要搞定,纯JS+CSS的翻页效果很多人弄,都不太理想

红影 发表于 2022-4-27 12:31

马黑黑 发表于 2022-4-26 23:43
主要是论坛所用的JS库多种,其中一个限制了jQuery的使用,不然可以直接弄的

在这种情况下作改进真不容易{:4_187:}

红影 发表于 2022-4-27 12:31

马黑黑 发表于 2022-4-26 23:43
嗯,不过这些问题都可以解决

黑黑真棒{:4_187:}

红影 发表于 2022-4-27 12:32

马黑黑 发表于 2022-4-26 23:44
这个书要搞定,纯JS+CSS的翻页效果很多人弄,都不太理想

能做出来就很不容易呢{:4_187:}

马黑黑 发表于 2022-4-27 12:49

红影 发表于 2022-4-27 12:32
能做出来就很不容易呢

肯定做得出来

马黑黑 发表于 2022-4-27 12:49

红影 发表于 2022-4-27 12:31
黑黑真棒

一般般吧
页: [1] 2 3 4
查看完整版本: CSS+JS:识字始翻书——animation定格@keyframes动画初探