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