You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

XML请求加载后CSS Marquee动画失效问题求助

解决XHR加载HTML后GSAP跑马灯动画失效问题

问题根源

  1. 原GSAP动画代码在页面初始window.onload阶段执行,但此时通过XHR请求的跑马灯DOM还未插入页面,代码找不到目标元素,动画根本没初始化。
  2. 当跑马灯内容通过innerHTML插入容器后,没有触发动画初始化逻辑,新插入的DOM元素从未被绑定GSAP动画。

修复方案

把GSAP动画逻辑封装成独立初始化函数,在XHR成功插入DOM后立即调用该函数,确保动画绑定到新插入的元素上。

修改后的完整代码

1. 更新后的marquee.js

function includeMarquee() {
    var xhr = new XMLHttpRequest();
    var marqueeFilePath = 'marquee.html';

    xhr.open('GET', marqueeFilePath, true);
    xhr.onreadystatechange = function () {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            var marqueeContent = xhr.responseText;
            var marqueeContainer = document.getElementById('marquee-container');
            if (marqueeContainer) {
                marqueeContainer.innerHTML = marqueeContent;
                // 插入DOM后立即初始化动画
                initMarqueeAnimation();
            }
        }
    };

    xhr.send();
}

function initMarqueeAnimation() {
    // 遍历所有跑马灯行,初始化每个行的动画
    const lines = document.querySelectorAll(".stb_line_single");
    lines.forEach((line, i) => {
        const links = line.querySelectorAll("a");
        const tl = horizontalLoop(links, {
            repeat: -1, // 改成-1实现无限循环,原1.7只循环1.7次就停了
            speed: 1 + i / 5,
            reversed: i ? true : false,
            paddingRight: parseFloat(gsap.getProperty(links[0], "marginRight", "px"))
        });
        links.forEach(link => {
            link.addEventListener("mouseenter", () => gsap.to(tl, {timeScale: 0, overwrite: true}));
            link.addEventListener("mouseleave", () => gsap.to(tl, {timeScale: i ? -1 : 1, overwrite: true}));
        });
    });
}

// horizontalLoop工具函数保持不变
function horizontalLoop(items, config) {
    items = gsap.utils.toArray(items);
    config = config || {};
    let tl = gsap.timeline({repeat: config.repeat, paused: config.paused, defaults: {ease: "none"}, onReverseComplete: () => tl.totalTime(tl.rawTime() + tl.duration() * 100)}),
        length = items.length,
        startX = items[0].offsetLeft,
        times = [],
        widths = [],
        xPercents = [],
        curIndex = 0,
        pixelsPerSecond = (config.speed || 1) * 100,
        snap = config.snap === false ? v => v : gsap.utils.snap(config.snap || 1),
        totalWidth, curX, distanceToStart, distanceToLoop, item, i;
    gsap.set(items, {
        xPercent: (i, el) => {
            let w = widths[i] = parseFloat(gsap.getProperty(el, "width", "px"));
            xPercents[i] = snap(parseFloat(gsap.getProperty(el, "x", "px")) / w * 100 + gsap.getProperty(el, "xPercent"));
            return xPercents[i];
        }
    });
    gsap.set(items, {x: 0});
    totalWidth = items[length-1].offsetLeft + xPercents[length-1] / 100 * widths[length-1] - startX + items[length-1].offsetWidth * gsap.getProperty(items[length-1], "scaleX") + (parseFloat(config.paddingRight) || 0);
    for (i = 0; i < length; i++) {
        item = items[i];
        curX = xPercents[i] / 100 * widths[i];
        distanceToStart = item.offsetLeft + curX - startX;
        distanceToLoop = distanceToStart + widths[i] * gsap.getProperty(item, "scaleX");
        tl.to(item, {xPercent: snap((curX - distanceToLoop) / widths[i] * 100), duration: distanceToLoop / pixelsPerSecond}, 0)
          .fromTo(item, {xPercent: snap((curX - distanceToLoop + totalWidth) / widths[i] * 100)}, {xPercent: xPercents[i], duration: (curX - distanceToLoop + totalWidth - curX) / pixelsPerSecond, immediateRender: false}, distanceToLoop / pixelsPerSecond)
          .add("label" + i, distanceToStart / pixelsPerSecond);
        times[i] = distanceToStart / pixelsPerSecond;
    }
    function toIndex(index, vars) {
        vars = vars || {};
        (Math.abs(index - curIndex) > length / 2) && (index += index > curIndex ? -length : length);
        let newIndex = gsap.utils.wrap(0, length, index),
            time = times[newIndex];
        if (time > tl.time() !== index > curIndex) {
            vars.modifiers = {time: gsap.utils.wrap(0, tl.duration())};
            time += tl.duration() * (index > curIndex ? 1 : -1);
        }
        curIndex = newIndex;
        vars.overwrite = true;
        return tl.tweenTo(time, vars);
    }
    tl.next = vars => toIndex(curIndex+1, vars);
    tl.previous = vars => toIndex(curIndex-1, vars);
    tl.current = () => curIndex;
    tl.toIndex = (index, vars) => toIndex(index, vars);
    tl.times = times;
    if (config.reversed) {
        tl.vars.onReverseComplete();
        tl.reverse();
    }
    return tl;
}

window.onload = function () {
    includeMarquee();
};

2. 其他文件保持不变

  • CSS样式(原代码中的CSS部分)
  • marquee.html(原跑马灯HTML结构)
  • 页面中的容器和脚本引用:
<div id="marquee-container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script> <!-- 确保GSAP库先加载 -->
<script src="/src/js/include-marquee.js"></script>

额外说明

  • 把原动画代码中的repeat:1.7改成了repeat:-1,实现真正的无限循环,避免动画跑1.7次后停止。
  • 修复了原动画代码中未定义的line和i变量问题,通过遍历所有.stb_line_single元素初始化每个跑马灯行的动画。
  • 必须确保GSAP库在自定义脚本之前加载,否则会报错gsap is not defined。

内容的提问来源于stack exchange,提问作者ambaamba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 02:24:50