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

前言

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

  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实现动画效果的优缺点
相关推荐
hackeroink23 分钟前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者2 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-2 小时前
验证码机制
前端·后端
燃先生._.3 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖4 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
唯之为之4 小时前
巧用mask属性创建一个纯CSS图标库
css·svg
m0_748235244 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240255 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar5 小时前
纯前端实现更新检测
开发语言·前端·javascript