文本“走马灯”看似简单,实际也不复杂

前言

在日常项目中,需要实现文本【横向】或【纵向】滚动,

  1. 无逢滚动(从左边界消失的同时立即从右边界出来)------ 【贪吃蛇效果】
  2. 距离一定的位置进行滚动(文本从容器消失,等待结尾文本距离容器多少距离,才开始从右侧出来)
  3. 头尾相接 ------ 内容超出容器,但是尾部结束时,头部相隔一定距离同时出现在容器;

一、无逢滚动 --- 已知内容宽度不会超过容器宽度 (纯CSS实现)
会增加额外相同的dom元素

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style lang="scss">
        .wrapper {
            box-sizing: border-box;
            position: absolute;
            top: 50%;
            left: 30%;
            transform: translate(-20%, -50%);

            width: 600px;
            height: 60px;
            overflow: hidden;
            border: 4px solid red;
            border-radius: 5px;
        }

        .marquee-wrapper {
            display: flex;
            align-items: center;
            width: 200%;
            height: 100%;
            transform: translateX(0);
            /*animation: move 6s infinite linear;*/
            animation-delay: 5s;
            /*text-shadow: 1325px 0;*/
        }

        .marquee-text {
            width: 100%;
            font-size: 20px;
            white-space: nowrap;
            /*background-color: red;*/
        }

        .marquee-text:nth-child(2) {
            color: red;
        }
        
        @keyframes move {
            0% {
                transform: translateX(0);
            }

            100% {
                transform: translateX(-50%);
            }
        }
</style>
</head>
<body>
    <div class="wrapper">
        <div class="marquee-wrapper">
            <div class="marquee-text">
                这是第一行横向滚动的提示文字信息
            </div>
            <div class="marquee-text">
                这是第一行横向滚动的提示文字信息
            </div>
        </div>
    </div>
    </div>
</body>
</html>

二、距离一定的位置进行滚动 --- 内容宽度不定 (JS + CSS实现)
不会增加额外相同的dom元素

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style lang="scss">
        :root {
            --start-position: 0;
        }
        .marquee-wrapper {
            box-sizing: border-box;
            position: absolute;
            top: 50%;
            left: 30%;
            transform: translate(-20%, -50%);
            display: flex;
            align-items: center;
            width: 600px;
            height: 60px;
            overflow: hidden;
            border: 4px solid red;
            border-radius: 5px;
        }

        /*.marquee-wrapper {*/
        /*    display: flex;*/
        /*    align-items: center;*/
        /*    width: auto;*/
        /*    height: 100%;*/
        /*}*/

        .marquee-text {
            width: auto;
            font-size: 20px;
            white-space: nowrap;
        }

        .marquee-text.moveFirst {
            animation: moveFirst linear forwards;
            animation-delay: 3s;
        }

        .marquee-text.moveInfinite {
            animation: moveInfinite linear infinite;
        }


        @keyframes moveFirst {
            0% {
                transform: translateX(0);
            }

            100% {
                transform: translateX(-100%);
            }
        }

        @keyframes moveInfinite {
            0% {
                transform: translateX(var(--start-position));
            }

            100% {
                transform: translateX(-100%);
            }
        }
</style>
</head>
<body>
    <div class="marquee-wrapper">
        <div class="marquee-text">
            这是第一行横向滚动的提示文字信息1这是第一行横向滚动的提示文字信息2。
        </div>
    </div>

    <script>
        const container = document.querySelector('.marquee-wrapper');
        const marqueeText = document.querySelector('.marquee-text');
        const containerWidth = container.offsetWidth
        const marqueeTextWidth = marqueeText.offsetWidth;
        const speed = 100; // 滚动速度,单位 px/s
        const firstAnimationDuration = marqueeTextWidth / speed; // 第一次动画持续时间
        const animationDuration = (marqueeTextWidth + containerWidth) / speed; // 动画持续时间

        if (marqueeTextWidth > containerWidth) {
            marqueeText.classList.add('moveFirst');
            marqueeText.style.animationDuration = `${firstAnimationDuration}s`;

            // 注意⚠️:第一次动画执行不能是infinite,否则不会监听到动画结束
            marqueeText.addEventListener('animationend', () => {
                marqueeText.classList.remove('moveFirst');
                marqueeText.classList.add('moveInfinite');
                // 设置第二次从什么位置开始执行动画
                document.documentElement.style.setProperty('--start-position', containerWidth + 'px');
                // marqueeText.style.setProperty('--start-position', containerWidth + 'px');
                marqueeText.style.animationDuration = `${animationDuration}s`;
            })
        }
    </script>
</body>
</html>

三、头尾相接 ------ 元素与元素间有一定的间距

  1. CSS + JS实现
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style lang="scss">
        :root {
            --width: 0;
        }
        .marquee-wrapper {
            box-sizing: border-box;
            position: absolute;
            top: 50%;
            left: 30%;
            transform: translate(-20%, -50%);
            display: flex;
            align-items: center;
            width: 600px;
            height: 60px;
            overflow: hidden;
            border: 4px solid red;
            border-radius: 5px;
        }

        .marquee-text-wrapper {
            display: flex;
            align-items: center;
            height: 100%;
        }

        .marquee-text {
            width: auto;
            font-size: 20px;
            white-space: nowrap;
            /*color: red;*/
        }

        .marquee-text2 {
            display: none;
            margin-left: 200px;
            /*color: blue;*/
        }

        .marquee-text-wrapper.moveFirst {
            animation: moveFirst linear forwards;
            animation-delay: 3s;
        }

        .marquee-text-wrapper.moveInfinite {
            animation: moveInfinite linear infinite;
        }


        @keyframes moveFirst {
            0% {
                transform: translateX(0);
            }

            100% {
                transform: translateX(var(--width));
            }
        }

        @keyframes moveInfinite {
            0% {
                transform: translateX(0);
            }

            100% {
                transform: translateX(var(--width));
            }
        }
</style>
</head>
<body>
    <div class="marquee-wrapper">
        <div class="marquee-text-wrapper">
            <div class="marquee-text">
                这是第一行横向滚动的提示文字信息1这是第一行横向滚动的提示文字信息2。
            </div>
            <div class="marquee-text marquee-text2">
                这是第一行横向滚动的提示文字信息1这是第一行横向滚动的提示文字信息2。
            </div>
        </div>
    </div>

    <script>
        const container = document.querySelector('.marquee-wrapper');
        const marqueeTextWrapper = document.querySelector('.marquee-text-wrapper');
        const marqueeText = document.querySelector('.marquee-text');

        const containerWidth = container.offsetWidth
        const marqueeTextWidth = marqueeText.offsetWidth;
        let marqueeTextWrapperWidth = marqueeTextWidth


        console.log(marqueeTextWidth > containerWidth)

        if (marqueeTextWidth > containerWidth) {
            document.querySelector('.marquee-text2').style.display = 'block';
            // 重新计算滚动内容的宽度
            marqueeTextWrapperWidth = marqueeTextWidth + 200;
            const speed = 100; // 滚动速度,单位 px/s
            const firstAnimationDuration = marqueeTextWrapperWidth / speed; // 第一次动画持续时间

            document.documentElement.style.setProperty('--width', `-${marqueeTextWrapperWidth}px`);
            marqueeTextWrapper.classList.add('moveFirst');
            marqueeTextWrapper.style.animationDuration = `${firstAnimationDuration}s`;

            // 注意⚠️:第一次动画执行不能是infinite,否则不会监听到动画结束
            marqueeTextWrapper.addEventListener('animationend', () => {
                marqueeTextWrapper.classList.remove('moveFirst');
                marqueeTextWrapper.classList.add('moveInfinite');
                marqueeTextWrapper.style.transform = 'translateX(0)';

            })
        }
    </script>
</body>
</html>
  1. JS实现 ------ 使用**window.requestAnimationFrame()** 方法
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style lang="scss">
        :root {
            --width: 0;
        }
        .marquee-wrapper {
            box-sizing: border-box;
            position: absolute;
            top: 50%;
            left: 30%;
            transform: translate(-20%, -50%);
            display: flex;
            align-items: center;
            width: 600px;
            height: 60px;
            overflow: hidden;
            border: 4px solid red;
            border-radius: 5px;
        }

        .marquee-text-wrapper {
            display: flex;
            align-items: center;
            height: 100%;
        }

        .marquee-text {
            width: auto;
            font-size: 20px;
            white-space: nowrap;
        }

        .marquee-text2 {
            display: none;
            margin-left: 200px;
            /*color: red;*/
        }
    </style>
</head>
<body>
<button onclick="handleStart()">开始</button>
<button onclick="handleEnd()">结束</button>

<div class="marquee-wrapper">
    <div class="marquee-text-wrapper">
        <div class="marquee-text">
            这是第一行横向滚动的提示文字信息1这是第一行横向滚动的提示文字信息2。
        </div>
        <div class="marquee-text marquee-text2">
            这是第一行横向滚动的提示文字信息1这是第一行横向滚动的提示文字信息2。
        </div>
    </div>
</div>


    <script>
        const marqueeWrapper = document.querySelector('.marquee-wrapper');
        const marqueeTextWrapper = document.querySelector('.marquee-text-wrapper');
        const marqueeText = document.querySelector('.marquee-text');


        const marqueeWrapperWidth = marqueeWrapper.offsetWidth;
        const marqueeTextWidth = marqueeText.offsetWidth;

        let requestAnimationId = null;
        let startTimeStamp = null;

        // 判断是否溢出
        if (marqueeTextWidth > marqueeWrapperWidth) {
            document.querySelector('.marquee-text2').style.display = 'block';


            // 指定开始位置 指定从容器右侧开始滚动出现
            const startPosition = 600
            // 重新计算滚动内容的宽度 (200: 第二个元素与第一个元素的 marginLeft 距离)
            const offsetWidth = marqueeTextWidth + 200;

            // 创建动画函数
            animationObj = createAnimationFn(marqueeTextWrapper, startPosition, offsetWidth);
            // 开始执行执行动画
            requestAnimationId = animationObj.start();
        } else {
            marqueeTextWrapper.style.transform = `translateX(0)`;
        }

        /*
        * 创建滚动动画
        * el : 要滚动的元素
        * startPosition: 从指定位置开始滚动
        * offsetWidth: 滚动元素的总宽度
        * */
        function createAnimationFn(el, startPosition, offsetWidth) {
            // 60HZ: 1s/60    (1000 / 60) * 0.06 = 16.666666666666668 * 0.06 = 1
            // 这个速度是 1s移动 60px
            const speed = 0.06;
            let cancel = false;

            const func = (timeStamp) => {
                if (cancel) return

                let moveX = 0
                if (startTimeStamp) {
                    moveX = Math.round((timeStamp - startTimeStamp) * speed)
                } else {
                    startTimeStamp = timeStamp
                }
                let x = Math.floor(startPosition - moveX)

                if (x <= -offsetWidth) {
                    startTimeStamp = null;
                    startPosition = 0;
                    x = 0;
                    el.style.transform = `translateX(${x}px)`;
                    if (!cancel) {
                        requestAnimationId = window.requestAnimationFrame(func)
                    }
                    return
                }

                if (el && x > -offsetWidth) {
                    el.style.transform = `translateX(${x}px)`
                    if (!cancel) {
                        requestAnimationId = window.requestAnimationFrame(func)
                    }
                }
            }

            return {
                start: () => {
                    cancel = false
                    return window.requestAnimationFrame(func)
                },
                // 可以再加一些暂停函数等
                end: (requestId) => {
                    cancel = true
                    window.cancelAnimationFrame(requestId)
                },
            }
        }

        function handleStart() {
            if (!animationObj) return
            startTimeStamp = null;
            requestAnimationId = animationObj.start()
        }
        function handleEnd() {
            if (!animationObj) return
            animationObj.end(requestAnimationId)
        }
    </script>
</body>
</html>

window.requestAnimationFrame()

总结:JS 和 CSS实现动画效果的优缺点
相关推荐
崔庆才丨静觅10 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅12 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅12 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊12 小时前
jwt介绍
前端
爱敲代码的小鱼12 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax