分析
跑马灯在前端是很常见的一种功能。听着高大上,其实就是让一行文字朝一个方向移动。让dom元素移动的方式在css
中又无外乎两种:transition
& animation
。
transition
实现一个文字从容器右侧滚动到容器左侧的跑马灯。(这里的例子是未知容器长度
和未知文字长度
时的处理方案。如果已知容器宽度则只使用translateX
移动即可)。
他的缺点就在于需要动态为元素p
添加和取消控制他的class
,因此一般还是采用他的升级版animation
。
scss
.tra-marquee {
width: 100%;
height: 50px;
border: solid 1px #000;
line-height: 50px;
font-size: 16px;
box-sizing: border-box;
position: relative;
overflow: hidden;
// 起始点:使文字在容器右侧
> p {
position: absolute;
right: 0; // 文字右侧与容器右侧距离为0,使文字右侧与容器右侧对齐
top: 0;
transform: translateX(100%); // 使文字向右移动文字长度的距离,达到文字左侧紧贴容器右侧
white-space: nowrap;
// 终点:使文字在容器左侧
&.move {
right: 100%; // 使文字左侧与容器左侧对齐
transform: translateX(0%); // 使文字右侧与容器左侧对齐
transition: all 12s linear;
}
}
}
animation
优点:作为animation的升级版可以更好的控制动画的延时、停止后的位置、运动快慢、是否重复等。就不需要再动态控制样式了。
scss
.ani-marquee {
width: 100%;
height: 50px;
border: solid 1px #000;
line-height: 50px;
font-size: 16px;
box-sizing: border-box;
position: relative;
overflow: hidden;
> p {
position: absolute;
top: 0;
white-space: nowrap;
animation: Marquee 12s linear infinite;
}
}
@keyframes Marquee {
0% {
right: 0;
transform: translateX(100%);
}
100% {
right: 100%;
transform: translateX(0%);
}
}
特殊场景
上面通过css
实现的都是一些简单场景。但有的时候我们可能需要再跑马灯中加入各种逻辑判断,又或者使用类似于跑马灯的场景但又不完全是。
这类场景的一大特点就是单纯的css
不能够完全满足需求,需要再javaScript
文件中来动态控制跑马灯。而使用javaScript
控制跑马灯在原理上与animation
相同。也是通过将整个动画拆分为不同的帧动画
,然后再将他们拼合起来。
不同的是animation
中帧动画是通过keyframes
关键字实现的,而javaScript
中实现帧动画是通过浏览器的刷新帧来实现的。而在浏览器每一刷新帧所触发的钩子函数,就是浏览器专门为动画所暴露的Api:requestAnimationFrame
具体使用方法可以看上方MDN链接中示例
部分的介绍。如有不足也可参考下方我自己写的另一种特殊示例。
抖音直播间标题文字过长超出范围时自动滚动
这个需求看上去是跑马灯的效果,但是有一定的差异。因为跑马灯是整个文字从尾部滚动到头部,而这个是从文字过长被容器隐藏的地方一直滚动到文字末尾,将整个文字都展示出来。然后再循环滚动。
这时我们通过css
就无法拿到滚动的距离(文字宽度 - 容器宽度),也无法用css
捕捉到动画停止的时机(滚动到文字尾部停止)。这时就需要采用requestAnimationFrame
实现。
获取容器宽度和文字长度
首先需要获取到容器宽度
和文字长度
,通过ref
配合offsetWidth
即可实现。需要注意的是,默认文档流内文字长度是等于容器宽度的。需要将文字脱离文档流才可获取到其长度。 这里我采用的方法是定位。
scss
.request-marquee {
width: 200px;
height: 50px;
border: solid 1px #000;
line-height: 50px;
font-size: 16px;
box-sizing: border-box;
margin: 50px 0 0 50px;
position: relative;
overflow: hidden;
> p {
white-space: nowrap;
position: absolute; // 脱离文档流
}
}
requestAnimationFrame实现
tsx
import React, { FC, useEffect, useRef } from "react";
import "./index.scss";
const RequestAnimationFrame: FC = () => {
const parentElement = useRef<HTMLDivElement>(null);
const childElement = useRef<HTMLParagraphElement>(null);
const timer = useRef<NodeJS.Timeout>(); // 清除定时器
const start = useRef<number>(); // 动画开始时间
const delayBeforeAnimate = 3000; // 动画开始时的延迟时间
const delayAfterNextAnimate = 3000; // 动画结束后距离下一次动画开始的延迟时间
useEffect(() => {
const parentWidth = parentElement.current!.offsetWidth;
const childWidth = childElement.current!.offsetWidth;
// 如果文字长度大于容器长度就开启滚动
if (childWidth - parentWidth > 0) {
timer.current = setTimeout(() => {
requestAnimationFrame(startAnimate);
}, delayBeforeAnimate);
}
}, []);
/**
* 开始动画
* @param timestamp 当前时间
*/
function startAnimate(timestamp: number) {
if (start.current === undefined) {
start.current = timestamp; // 开始滚动的时间戳
}
const parentWidth = parentElement.current!.offsetWidth;
const childWidth = childElement.current!.offsetWidth;
const speed = 0.03; // 每一帧滚动的速度
const elapsed = timestamp - start.current; // 距离开始滚动所过去的时间
const distance = speed * elapsed; // 速度乘以时间得到对应帧滚动的距离
childElement.current!.style.transform = `translateX(-${exactDistance}px)`; // 设置文字在当前帧滚动的距离
const maxDistance = childWidth - parentWidth; // 应该滚动的距离
const exactDistance = Math.min(distance, maxDistance); // 更精准的对已经滚动的距离和应该滚动的距离作比较
if (exactDistance === maxDistance) {
// 表示已经滚动距离大于应该滚动距离,所以下一帧要重置动画
timer.current = undefined;
resetAnimate();
} else {
// 表示已经滚动距离小于应该滚动距离,所以下一帧继续动画
requestAnimationFrame(startAnimate);
}
}
/**
* 重置动画
*/
function resetAnimate() {
timer.current = undefined;
timer.current = setTimeout(() => {
timer.current = undefined;
// 重置文本滚动样式
childElement.current!.style.transition = "none";
childElement.current!.style.transform = "none";
// 重置滚动开始时间
start.current = undefined;
timer.current = setTimeout(() => {
requestAnimationFrame(startAnimate);
}, delayBeforeAnimate);
}, delayAfterNextAnimate);
}
return (
<div className="request-marquee" ref={parentElement}>
<p ref={childElement}>我是一条测试直播间名字过长的文本~~~</p>
</div>
);
};
export default RequestAnimationFrame;
以上就是前端实现跑马灯的全部内容啦!如果看完文章有所收获的话,烦请动动小手点个赞哦~
您的支持是对我最大的鼓励❤️❤️❤️