马黑黑 发表于 2024-6-12 12:00

JS原生animate动画函数应用实例:文本转场效果

<style>
.pa p { font: normal 18px/24px sans-serif; margin: 12px 0; }
#outBox { margin: 20px auto; width: 700px; height: 200px; border: 1px solid gray; overflow: hidden; position: relative; }
#txtBox { position: absolute; top: 20px; right: 0px; font: normal 40px sans-serif; text-shadow: 2px 2px 2px gray; white-space: nowrap; user-select: none; cursor: pointer; transition: 1s; }
#txtBox:hover { transform: scale(1.2); filter: drop-shadow(10px 10px 20px green); }
.mum { position: relative; margin: 12px 0; padding: 10px; font: normal 16px/20px Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; color: black; background: rgba(240, 240, 240,.95); box-shadow: 2px 2px 4px gray; border: thick groove lightblue; border-radius: 6px; }
.mum ::selection { background-color: rgba(0,100,100,.35); }
.mum div { margin: 0; padding: 0; }
.mum cl-cd { display: block; position: relative; margin: 0 0 0 50px; padding: 0 0 0 10px; white-space: pre-wrap; overflow-wrap: break-word; border-left: 1px solid silver; }
.mum cl-cd::before { position: absolute; content: attr(data-idx); width: 50px; color: gray; text-align: right; transform: translate(-70px); }
.tRed { color: red; }
.tBlue { color: blue; }
.tGreen { color: green; }
.tDarkRed { color: darkred; }
.tMagenta { color: magenta; }
</style>

<div class="pa">
        <p>先上效果:</p>
        <div id="outBox">
                <div id="txtBox"></div>
        </div>
        <p>整体效果是,诗歌按循序逐句轮换出场。每一句:身披彩衣从右边入场,到了舞台中央做两次幅度不同的垂直拉伸训练,然后继续往左走猫步,直至消失。所有诗句亮相完毕,更换衣服重头再来,如此往复。鼠标指针移至诗句运动暂停,移开指针表演继续。</p>
        <p>下面我们来剖析实现过程:</p>
        <p>首先看看场景HTML结构;</p>
        <div class='mum'>
<cl-cd data-idx="1">&lt;<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"outBox"</span>&gt;</cl-cd>
<cl-cd data-idx="2">&nbsp; &nbsp; &lt;<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"txtBox"</span>&gt;&lt;<span class="tDarkRed">/div</span>&gt;</cl-cd>
<cl-cd data-idx="3">&lt;<span class="tDarkRed">/div</span>&gt;</cl-cd>
        </div>
        <p>外层盒子 id="outBox" 是舞台,它需要相应的CSS样式来布景,简单规范一下尺寸、位置、定位方式,特别地,禁止内容外溢,以便让演员在后台的不雅观的动作例如擦汗、摸摸鼻子甚至挠痒痒什么的不被观众看到:</p>
        <div class='mum'>
<cl-cd data-idx="1">#outBox {</cl-cd>
<cl-cd data-idx="2">&nbsp; &nbsp; <span class="tBlue">margin:</span> 20px auto;</cl-cd>
<cl-cd data-idx="3">&nbsp; &nbsp; <span class="tBlue">width:</span> 700px;</cl-cd>
<cl-cd data-idx="4">&nbsp; &nbsp; <span class="tBlue">height:</span> 200px;</cl-cd>
<cl-cd data-idx="5">&nbsp; &nbsp; <span class="tBlue">border:</span> 1px solid gray;</cl-cd>
<cl-cd data-idx="6">&nbsp; &nbsp; <span class="tBlue">overflow:</span> hidden;</cl-cd>
<cl-cd data-idx="7">&nbsp; &nbsp; <span class="tBlue">position:</span> relative;</cl-cd>
<cl-cd data-idx="8">}</cl-cd>
        </div>
        <p>里层盒子 id="txtBox" 是演员,也需要配套的CSS样式支持,得精致装修:</p>
        <div class='mum'>
<cl-cd data-idx="1">#txtBox {</cl-cd>
<cl-cd data-idx="2">&nbsp; &nbsp; <span class="tBlue">position:</span> absolute;</cl-cd>
<cl-cd data-idx="3">&nbsp; &nbsp; <span class="tBlue">top:</span> 20px;</cl-cd>
<cl-cd data-idx="4">&nbsp; &nbsp; <span class="tBlue">right:</span> 0px;</cl-cd>
<cl-cd data-idx="5">&nbsp; &nbsp; <span class="tBlue">font:</span> normal 40px sans-serif;</cl-cd>
<cl-cd data-idx="6">&nbsp; &nbsp; <span class="tBlue">text-shadow:</span> 2px 2px 2px gray;</cl-cd>
<cl-cd data-idx="7">&nbsp; &nbsp; <span class="tBlue">white-space:</span> nowrap; <span class="tGreen">/* 禁止自动折行 */</span></cl-cd>
<cl-cd data-idx="8">&nbsp; &nbsp; <span class="tBlue">user-select:</span> none; <span class="tGreen">/* 禁止选中文本 */</span></cl-cd>
<cl-cd data-idx="9">&nbsp; &nbsp; <span class="tBlue">cursor:</span> pointer;</cl-cd>
<cl-cd data-idx="10">&nbsp; &nbsp; <span class="tBlue">transition:</span> 1s;</cl-cd>
<cl-cd data-idx="11">}</cl-cd>
<cl-cd data-idx="12"><span class="tGreen">/* 指针滑过效果 */</span></cl-cd>
<cl-cd data-idx="13">#<span class="tBlue">txtBox:</span>hover {</cl-cd>
<cl-cd data-idx="14">&nbsp; &nbsp; <span class="tBlue">transform:</span> scale(1.2);</cl-cd>
<cl-cd data-idx="15">&nbsp; &nbsp; <span class="tBlue">filter:</span> drop-shadow(10px 10px 20px green);</cl-cd>
<cl-cd data-idx="16">}</cl-cd>
        </div>
        <p>开始进入JS实现代码了。文本拆分处理总要做一做,顺带声明几个必要的变量:</p>
        <div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">var</span> txtAr = <span class="tMagenta">'锄禾日当午,汗滴禾下土。谁知盘中餐?粒粒皆辛苦。'</span>.split(/[,。?]/);</cl-cd>
<cl-cd data-idx="2"><span class="tBlue">var</span> ani, idx = 0;</cl-cd>
        </div>
        <p>拆分文本的目的是为了获得一个输出文本数组,直接构建数组也可以,上面的做法更为方便,随意一段有标点符号的文本均可使用 split 方法通过创建一个简单的正则表达式即将标点符号归拢起来就可以拥有数组元素为纯文字的数组。数组使用变量 txtAr 存储。其他全局变量,ani 是动画操作句柄,idx 是文本数组索引。</p>
        <p>接下来是本文主题相关的核心内容——创建一个JS函数,实现文本转场效果。函数要处理的细节不少,重点是演员动作的设计与控制,需要用到 JS Animate 函数来完成:</p>
        <div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">var</span> showText = () =&gt; {</cl-cd>
<cl-cd data-idx="2">&nbsp; &nbsp; txtBox.style.color = `#${<span class="tRed">Math</span>.random().toString(16).substring(2,8)}`;</cl-cd>
<cl-cd data-idx="3">&nbsp; &nbsp; txtBox.innerText = txtAr;</cl-cd>
<cl-cd data-idx="4">&nbsp; &nbsp; <span class="tBlue">var</span> outwidth = outBox.offsetWidth, txtwidth = txtBox.offsetWidth;</cl-cd>
<cl-cd data-idx="5">&nbsp; &nbsp; <span class="tBlue">var</span> cx = outwidth / 2 - txtwidth / 2;</cl-cd>
<cl-cd data-idx="6">&nbsp; &nbsp; <span class="tBlue">var</span> fly = [</cl-cd>
<cl-cd data-idx="7">&nbsp; &nbsp; &nbsp; &nbsp; {<span class="tBlue">right:</span> `-${txtwidth}px`, <span class="tBlue">top:</span> `20px`},</cl-cd>
<cl-cd data-idx="8">&nbsp; &nbsp; &nbsp; &nbsp; {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span> `20px`, <span class="tBlue">offset:</span> 0.25},</cl-cd>
<cl-cd data-idx="9">&nbsp; &nbsp; &nbsp; &nbsp; {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span>`20px`},</cl-cd>
<cl-cd data-idx="10">&nbsp; &nbsp; &nbsp; &nbsp; {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span> `80px`},</cl-cd>
<cl-cd data-idx="11">&nbsp; &nbsp; &nbsp; &nbsp; {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span> `20px`},</cl-cd>
<cl-cd data-idx="12">&nbsp; &nbsp; &nbsp; &nbsp; {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span> `120px`},</cl-cd>
<cl-cd data-idx="13">&nbsp; &nbsp; &nbsp; &nbsp; {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span> `20px`, <span class="tBlue">offset:</span> 0.75},</cl-cd>
<cl-cd data-idx="14">&nbsp; &nbsp; &nbsp; &nbsp; {<span class="tBlue">right:</span> `${outwidth}px`}</cl-cd>
<cl-cd data-idx="15">&nbsp; &nbsp; ];</cl-cd>
<cl-cd data-idx="16">&nbsp; &nbsp; <span class="tBlue">var</span> attr = { <span class="tBlue">duration:</span> 8000, <span class="tBlue">iterations:</span> 1 };</cl-cd>
<cl-cd data-idx="17">&nbsp; &nbsp; ani = txtBox.animate(fly,attr);</cl-cd>
<cl-cd data-idx="18">&nbsp; &nbsp; ani.onfinish = () =&gt; {</cl-cd>
<cl-cd data-idx="19">&nbsp; &nbsp; &nbsp; &nbsp; idx = (idx + 1) % (txtAr.length - 1);</cl-cd>
<cl-cd data-idx="20">&nbsp; &nbsp; &nbsp; &nbsp; showText();</cl-cd>
<cl-cd data-idx="21">&nbsp; &nbsp; };</cl-cd>
<cl-cd data-idx="22">};</cl-cd>
        </div>
        <p>函数名为 showText,具体代码:</p>
        <blockquote>
                <span class="tRed">第2行</span> :设置文本颜色为随机颜色,也就是每一次运行函数文本的颜色都基本不同于上一次;<br>
                <span class="tRed">第3行</span> :依据当前索引输出文本;<br>
                <span class="tRed">第4、5行</span> :分别获取父子元素的宽度(outwidth、txtwidth)、文本在场景中央的左边距(cx);<br>
                <span class="tRed">第6~15行</span> :设计 animate(keyframes, options) 函数参数1即 keyframes,描述8各点元素的 right、top 位置。其中第二个点和倒数第二个点拥有 offset 属性,它相当于CSS关键帧动画 @keyframes 描述语句中的百分比,这里意思是动画运行到此处占据动画时间线的百分比。可以不设置 offset 属性,若此,所有的运动节点均摊运动周期时长;<br>
                <span class="tRed">第16行</span> :设计 animate(keyframes, options) 函数参数2即 options,周期时长8秒、运行1次;<br>
                <span class="tRed">第17行</span> :运行 animate 动画并将操作句柄存入前面声明的全局变量 ani;<br>
                <span class="tRed">第18~21行</span> :动画 ani 的结束事件,一旦结束,就改变文本索引号,并立马调用函数自身以期动画循环运行。
        </blockquote>
        <p>函数写完,需要启动一下,showText(),即可正常运行动画。再加入一些必要的交互控制,比如鼠标指针移入、移出能暂停、继续动画,动画就相对完整了,具体见以下本文示例的完整源码:</p>
        <div class='mum'>
<cl-cd data-idx="1">&lt;<span class="tDarkRed">style</span>&gt;</cl-cd>
<cl-cd data-idx="2">#outBox { <span class="tBlue">margin:</span> auto; <span class="tBlue">width:</span> 760px; <span class="tBlue">height:</span> 200px; <span class="tBlue">border:</span> 1px solid gray; <span class="tBlue">overflow:</span> hidden; <span class="tBlue">position:</span> relative; }</cl-cd>
<cl-cd data-idx="3">#txtBox { <span class="tBlue">position:</span> absolute; <span class="tBlue">top:</span> 20px; <span class="tBlue">right:</span> 0; <span class="tBlue">font:</span> normal 40px sans-serif; <span class="tBlue">text-shadow:</span> 2px 2px 2px gray; <span class="tBlue">white-space:</span> nowrap; <span class="tBlue">user-select:</span> none; <span class="tBlue">cursor:</span> pointer; <span class="tBlue">transition:</span> 1s; }</cl-cd>
<cl-cd data-idx="4">#<span class="tBlue">txtBox:</span>hover { <span class="tBlue">transform:</span> scale(1.2); <span class="tBlue">filter:</span> drop-shadow(10px 10px 20px green); }</cl-cd>
<cl-cd data-idx="5">&lt;<span class="tDarkRed">/style</span>&gt;</cl-cd>
<cl-cd data-idx="6"> </cl-cd>
<cl-cd data-idx="7">&lt;<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"outBox"</span>&gt;</cl-cd>
<cl-cd data-idx="8">    &lt;<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"txtBox"</span>&gt;&lt;<span class="tDarkRed">/div</span>&gt;</cl-cd>
<cl-cd data-idx="9">&lt;<span class="tDarkRed">/div</span>&gt;</cl-cd>
<cl-cd data-idx="10"> </cl-cd>
<cl-cd data-idx="11">&lt;<span class="tDarkRed">script</span>&gt;</cl-cd>
<cl-cd data-idx="12"><span class="tBlue">var</span> txtAr = <span class="tMagenta">'锄禾日当午,汗滴禾下土。谁知盘中餐?粒粒皆辛苦。'</span>.split(/[,。?]/);</cl-cd>
<cl-cd data-idx="13"><span class="tBlue">var</span> ani, idx = 0;</cl-cd>
<cl-cd data-idx="14"><span class="tBlue">var</span> showText = () =&gt; {</cl-cd>
<cl-cd data-idx="15">    txtBox.style.color = `#${<span class="tRed">Math</span>.random().toString(16).substring(2,8)}`;</cl-cd>
<cl-cd data-idx="16">    txtBox.innerText = txtAr;</cl-cd>
<cl-cd data-idx="17">    <span class="tBlue">var</span> outwidth = outBox.offsetWidth, txtwidth = txtBox.offsetWidth;</cl-cd>
<cl-cd data-idx="18">    <span class="tBlue">var</span> cx = outwidth / 2 - txtwidth / 2;</cl-cd>
<cl-cd data-idx="19">    <span class="tBlue">var</span> fly = [</cl-cd>
<cl-cd data-idx="20">      {<span class="tBlue">right:</span> `-${txtwidth}px`, <span class="tBlue">top:</span> `20px`},</cl-cd>
<cl-cd data-idx="21">      {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span> `20px`, <span class="tBlue">offset:</span> 0.25},</cl-cd>
<cl-cd data-idx="22">      {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span>`20px`},</cl-cd>
<cl-cd data-idx="23">      {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span> `80px`},</cl-cd>
<cl-cd data-idx="24">      {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span> `20px`},</cl-cd>
<cl-cd data-idx="25">      {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span> `120px`},</cl-cd>
<cl-cd data-idx="26">      {<span class="tBlue">right:</span> `${cx}px`, <span class="tBlue">top:</span> `20px`, <span class="tBlue">offset:</span> 0.75},</cl-cd>
<cl-cd data-idx="27">      {<span class="tBlue">right:</span> `${outwidth}px`}</cl-cd>
<cl-cd data-idx="28">    ];</cl-cd>
<cl-cd data-idx="29">    <span class="tBlue">var</span> attr = { <span class="tBlue">duration:</span> 8000, <span class="tBlue">iterations:</span> 1 };</cl-cd>
<cl-cd data-idx="30">    ani = txtBox.animate(fly,attr);</cl-cd>
<cl-cd data-idx="31">    ani.onfinish = () =&gt; {</cl-cd>
<cl-cd data-idx="32">      idx = (idx + 1) % (txtAr.length - 1);</cl-cd>
<cl-cd data-idx="33">      showText();</cl-cd>
<cl-cd data-idx="34">    }</cl-cd>
<cl-cd data-idx="35">};</cl-cd>
<cl-cd data-idx="36">showText();</cl-cd>
<cl-cd data-idx="37">txtBox.onmouseover = () =&gt; ani.pause();</cl-cd>
<cl-cd data-idx="38">txtBox.onmouseout = () =&gt; ani.play();</cl-cd>
<cl-cd data-idx="39">&lt;<span class="tDarkRed">/script</span>&gt;</cl-cd>
</div>
        <p>以上代码不止应用于文本转场功能,有能力的可以对之改写,以实现表现力更为强大、细节更加丰富的其他用途。</p>
</div>

<script>
var txtAr = '锄禾日当午,汗滴禾下土。谁知盘中餐?粒粒皆辛苦。'.split(/[,。?]/);
var ani, idx = 0;
var showText = () => {
        txtBox.style.color = `#${Math.random().toString(16).substring(2,8)}`;
        txtBox.innerText = txtAr;
        var outwidth = outBox.offsetWidth, txtwidth = txtBox.offsetWidth;
        var cx = outwidth / 2 - txtwidth / 2;
        var fly = [
                {right: `-${txtwidth}px`, top: `20px`},
                {right: `${cx}px`, top: `20px`, offset: 0.25},
                {right: `${cx}px`, top:`20px`},
                {right: `${cx}px`, top: `80px`},
                {right: `${cx}px`, top: `20px`},
                {right: `${cx}px`, top: `120px`},
                {right: `${cx}px`, top: `20px`, offset: 0.75},
                {right: `${outwidth}px`}
        ];
        var attr = { duration: 8000, iterations: 1 };
        ani = txtBox.animate(fly,attr);
        ani.onfinish = () => {
                idx = (idx + 1) % (txtAr.length - 1);
                showText();
        }
};
showText();
txtBox.onmouseover = () => ani.pause();
txtBox.onmouseout = () => ani.play();
</script>

起个网名好难 发表于 2024-6-12 12:38

本帖最后由 起个网名好难 于 2024-6-12 15:41 编辑

iterations 缺省就是1, 可省略;

若仅剩duration, attr 也可以不用直接写数值。
当然作为模板还是不要少为好。

南无月 发表于 2024-6-12 12:58

叹为观止~~老师总能想出一些看起来很妙的场景,效果太好了,这个转场非常流畅。。。鼠标触碰暂停且光晕笼罩,细节处理相当完美 。。。{:4_199:}

南无月 发表于 2024-6-12 13:06

随意一段有标点符号的文本均可使用 split 方法通过创建一个简单的正则表达式即将标点符号归拢起来就可以拥有数组元素为纯文字的数组。
var txtAr = '锄禾日当午,汗滴禾下土。谁知盘中餐?粒粒皆辛苦。'.split(/[,。?]/);
{:4_199:}
这个太有用了。很奇妙的样子

马黑黑 发表于 2024-6-12 13:13

起个网名好难 发表于 2024-6-12 12:38
iterations 缺省就是1, 可省略;

若仅剩durationduration, attr 也可以不用直接写数值。


嗯,这样的话,连 attr 变量都可以不要了

马黑黑 发表于 2024-6-12 13:18

南无月 发表于 2024-6-12 13:06
随意一段有标点符号的文本均可使用 split 方法通过创建一个简单的正则表达式即将标点符号归拢起来就可以拥 ...
这个,/[,。?]/,它就是正则表达式。这是最简单的正则表达式,用中括号将三个标点符号或更多的其他任意合法的字符罗列出来,它的意思是:

或者 ,,或者 。,或者 ?,只要存在这三个符号的任意一个,就成。

然后,符号 // 是表示我这是个正则表达式,按这个规则去做匹配。

而 split 是拆分字串,它需要一个拆分规则,如果有规则,就按规则拆分字符串,如果没有规则,则规则是无,无也是规则,拆分出来的是一个一个的字节。

马黑黑 发表于 2024-6-12 13:20

南无月 发表于 2024-6-12 12:58
叹为观止~~老师总能想出一些看起来很妙的场景,效果太好了,这个转场非常流畅。。。鼠标触碰暂停且光晕笼罩 ...

完美谈不上的,就是貌似有点好看

南无月 发表于 2024-6-12 17:43

马黑黑 发表于 2024-6-12 13:20
完美谈不上的,就是貌似有点好看

有点好看肯定不止。。。
我觉得像模特走台似,赏心悦目,非常优秀。。{:4_170:}
而且可以暂停,这个更好玩

南无月 发表于 2024-6-12 17:48

马黑黑 发表于 2024-6-12 13:18
这个,/[,。?]/,它就是正则表达式。这是最简单的正则表达式,用中括号将三个标点符号或更多的其他任意 ...

老师这个说得太清楚了。。
原来三个符号是并列存在的,碰到谁都要遵规则。
拆完后,一队一队的出现进行表演,
非常有序,有组织有纪律。。{:4_170:}

这个正则表达经常听你说,名字起得也好听。。正气凛然

马黑黑 发表于 2024-6-12 18:24

南无月 发表于 2024-6-12 17:43
有点好看肯定不止。。。
我觉得像模特走台似,赏心悦目,非常优秀。。
而且可以暂停,这个更 ...

{:4_203:}

南无月 发表于 2024-6-12 20:35

马黑黑 发表于 2024-6-12 18:24

{:4_170:}别介呀,这阵子老不接话我看着挺稀罕。

马黑黑 发表于 2024-6-12 20:40

南无月 发表于 2024-6-12 20:35
别介呀,这阵子老不接话我看着挺稀罕。

胡言乱语的说

南无月 发表于 2024-6-12 20:45

马黑黑 发表于 2024-6-12 20:40
胡言乱语的说

灌水么,胡言也正常。。。
常有理有点反常,别是有原因那就得找找了{:4_170:}
我找找

马黑黑 发表于 2024-6-12 20:52

南无月 发表于 2024-6-12 20:45
灌水么,胡言也正常。。。
常有理有点反常,别是有原因那就得找找了
我找找

找东东累人

南无月 发表于 2024-6-12 20:58

马黑黑 发表于 2024-6-12 20:52
找东东累人

{:4_170:}咦,也就是说可以忽略不计?

马黑黑 发表于 2024-6-12 21:36

南无月 发表于 2024-6-12 20:58
咦,也就是说可以忽略不计?

自然可以

南无月 发表于 2024-6-13 17:47

马黑黑 发表于 2024-6-12 21:36
自然可以

{:4_181:}听老师的没错

马黑黑 发表于 2024-6-13 17:48

南无月 发表于 2024-6-13 17:47
听老师的没错

错了改改

南无月 发表于 2024-6-13 20:08

马黑黑 发表于 2024-6-13 17:48
错了改改

越改越错了咋办

马黑黑 发表于 2024-6-13 20:17

南无月 发表于 2024-6-13 20:08
越改越错了咋办
将错就错
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: JS原生animate动画函数应用实例:文本转场效果