CSS关键帧动画文本效果:逐行出场
思路:用p标签装载每行文本,令其先待在div父容器右侧,再用CSS关键帧动画依次将p标签平移到默认时的位置,最后一句出场后间隔一定时间复位重来,整体动画循环往复并可暂停、继续。
实现:
一、CSS和HTML
父元素是一个div标签,绝对定位,宽度需要设置一个合适的值;子元素是段落标签p,相对定位以便无需操心布局且transform能对之进行控制;关键帧动画非常简单,平移到 0 处,就是回到默认时的原位置。p标签暂不出现在HTML代码中,它们将由JS动态生成:
<style>
#txtbox {
position: absolute;
left: 50px;
top: 50px;
width: 280px;
min-height: 60px;
padding: 10px 20px;
box-sizing: border-box;
overflow: hidden;
}
#txtbox p {
margin: 8px 0;
position: relative;
font: normal 26px/30px sans-serif;
color: navy;
text-shadow: 1px 1px 1px gray;
animation: 2s forwards;
}
@keyframes move { to { transform: translate(0); } }
</style>
<div id="txtbox"></div>
基于上面的设计,@keyframes 属性制作的动画 transform: translate(0) 是不管用的,平移的尺寸按理应不能是 0,所以是不是 #txtbox p 选择器漏了一句?是的,漏了 transform: translate(280px); 属性设置,还有,animation 属性也不完整,漏了动画名称,这些,将由JS来完善和管理。
二、JS
首先需要几个全局变量:
var text = `西岳崚嶒竦处尊,诸峰罗立似儿孙。安得仙人九节杖,拄到玉女洗头盆。车箱入谷无归路,箭栝通天有一门。稍待秋风凉冷后,高寻白帝问真源。`;
var paras = [];
var reg = /[,。\n]/;
var ww = txtbox.offsetWidth;
text 是待输出文本,反引号内的文本内容可以分行写;paras 是段落签数组,用来保存p标签元素;reg 是正则表达式,注意红色部分,中括号表示其里面的内容只要出现其中的一个便能匹配,这里设计了三个:① 汉语逗号,② 汉语句号,③换行符 \n,可以根据需要添加、删减或重新构造自己的正则;ww 是父div元素的宽度。
接着处理一下文本,将text按正则描述的规则拆分成若干单位、放入临时数组 ar 中并且,① 对 ar 数组过滤空文本数组元素,② 封装每一个数组元素内容为p标签、令其移动到父div元素外围右侧,③ 将封装好的p标签追加到父div元素,④ 将p标签一一存放到全局数组变量 paras 中。这个功能用一个立即执行函数完成:
(function () {
var ar = text.split(reg).filter(item => item !=='');
ar.forEach((p,k) => {
var para = document.createElement('p');
para.innerHTML = p;
para.style.cssText += `transform: translate(${ww}px);`;
txtbox.appendChild(para);
paras.push(para);
});
})();
现在,paras 数组变量已经准备就绪,可以遍历它做一些实际性工作了:
paras[0].style.animationName = 'move';
paras.forEach((p,k) => {
p.onanimationend = () => {
paras[(k+1) % paras.length].style.animationName = 'move';
if(k === paras.length - 1) setTimeout( () => parasReset(), 10000);
};
});
上面,我们先是给第一个p标签 paras[0] 指派关键帧动画名。接着,用 array.forEach() 方法遍历了数组 paras,批量定制了p元素的 onanimationend 事件(动画结束事件):当前 p 标签的该事件触发时,给下标为 (k+1) % paras.length 的p标签设置动画名称。使用遍历索引加1取数组长度的余数获得数组元素下标是个巧妙的方法:遍历具有迭代特性,每次的 k 变量指向当前数组元素,它的下一个数组元素自然是 k+1,但当循环到最后,k+1 的值会等于数组长度值(数组元素总量值)从而非法(数组元素下标从0开始起算),而 k+1 取数组长度的余数,在前面均等于 k+1 本身,而当 k+1 等于数组长度时会得到 0,也就是回到第一个数组元素即给第一个数组元素指定动画。然后,要使得动画循环运行,我们需要加入一个处理机制(代码第 6 行):最后一个p标签关键帧动画结束时,使用定时器 setTimeout 间隔预定时间后调用该处理机制的函数 parasReset(),函数长如下酱紫:
var parasReset = () => {
paras.forEach(p => {
p.style.animationName = '';
p.style.transform = 'translate(${ww}px)';
setTimeout( () => { paras[0].style.animationName = 'move'; }, 500);
});
};
函数 parasReset() 遍历存储p标签的数组 paras,清空所有p标签的动画(必须,否则无法继续执行后面的同名动画)并将之统统移动到父div盒子的右侧外围,最后,再用一个 setTimeout 定时器在N毫秒之后给第一个p标签指定动画名称(这里的定时器必须使用,因为JS是同步运行机制,CSS动画名交替则要求,如果赋予的是同名动画,则不能取消之后立马重新赋予,需要一个时间差)。
至此,文本动画已经可以运行了,就差一个控制动画播放暂停的机制,我们用父div元素的窗体为载体,点击它时动画会在播放与暂停之间来回切换:
txtbox.onclick = () => {
var state = window.getComputedStyle(paras[0]).getPropertyValue('animation-play-state');
paras.forEach(p => p.style.animationPlayState = state === 'running' ? 'paused' : 'running');
};
最后,将上述的代码整合一下:
<style>
#txtbox {
position: absolute;
left: 50px;
top: 50px;
width: 280px;
min-height: 60px;
padding: 10px 20px;
box-sizing: border-box;
overflow: hidden;
}
#txtbox p {
margin: 8px 0;
position: relative;
font: normal 26px/30px sans-serif;
color: navy;
text-shadow: 1px 1px 1px gray;
animation: 2s forwards;
}
@keyframes move { to { transform: translate(0); } }
</style>
<div id="txtbox"></div>
<script>
var text = `西岳崚嶒竦处尊,诸峰罗立似儿孙。安得仙人九节杖,拄到玉女洗头盆。车箱入谷无归路,箭栝通天有一门。稍待秋风凉冷后,高寻白帝问真源。`;
var paras = [];
var reg = /[,。\n]/;
var ww = txtbox.offsetWidth;
//自执行函数 :处理文本+p标签封装+追加+存储
(function () {
var ar = text.split(reg).filter(item => item !=='');
ar.forEach((p,k) => {
var para = document.createElement('p');
para.innerHTML = p;
para.style.cssText += `transform: translate(${ww}px);`;
txtbox.appendChild(para);
paras.push(para);
});
})();
//p标签复位函数
var parasReset = () => {
paras.forEach(p => {
p.style.animationName = '';
p.style.transform = 'translate(${ww}px)';
setTimeout( () => { paras[0].style.animationName = 'move'; }, 500);
});
};
//第一个p标签启用动画
paras[0].style.animationName = 'move';
//遍历p标签 :动态生成所有标签的动画结束事件
paras.forEach((p,k) => {
p.onanimationend = () => {
paras[(k+1) % paras.length].style.animationName = 'move';
if(k === paras.length - 1) setTimeout( () => parasReset(), 10000);
};
});
//父div元素点击事件 :切换动画播放暂停
txtbox.onclick = () => {
var state = window.getComputedStyle(paras[0]).getPropertyValue('animation-play-state');
paras.forEach(p => p.style.animationPlayState = state === 'running' ? 'paused' : 'running');
};
</script>
【提示】p标签在动态生成时使用 innerHTML 赋予其文本,这意味着 text 变量的赋值文本可以附带简单的HTML标签,诸如给特定关键词加粗体、前景色等修饰性标签。此外,可以通过CSS伪类选择器修饰对应的p标签,比如 :first-of-type、last-of-type 等。