如何用JS制作无限跑马灯?

How do I make an Infinite marquee with JS?

我正在尝试制作一个可以加快滚动速度的 Infinite marquee,https://altsdigital.com/ 你可以在这个网站上看到效果,文字说“不是你通常的 SEO 机构”,当你滚动它时速度向上。

以下是我尝试过的方法,但它不起作用。它没有重叠就不能正确循环(请注意页面的左侧,您会注意到文本短暂重叠然后向左平移以产生间隙)我不确定如何修复它:

这是代码(文本仅在“全页”视图中可见):

const lerp = (current, target, factor) => {
    let holder = current * (1 - factor) + target * factor;
    holder = parseFloat(holder).toFixed(3);
    return holder;
};

class LoopingText {
    constructor(DOMElements) {
        this.DOMElements = DOMElements;
        this.lerpingData = {
            counterOne: { current: 0, target: 0 },
            counterTwo: { current: 100, target: 100 },
        };
        this.interpolationFactor = 0.1;
        this.speed = 0.2;
        this.render();
        this.onScroll();
    }

    onScroll() {
        window.addEventListener("scroll", () => {
            this.lerpingData["counterOne"].target += this.speed * 5;
            this.lerpingData["counterTwo"].target += this.speed * 5;
        });
    }

    lerp() {
        for (const counter in this.lerpingData) {
            this.lerpingData[counter].current = lerp(
                this.lerpingData[counter].current,
                this.lerpingData[counter].target,
                this.interpolationFactor
            );
        }

        this.lerpingData["counterOne"].target += this.speed;
        this.lerpingData["counterTwo"].target += this.speed;

        if (this.lerpingData["counterOne"].target < 100) {
            this.DOMElements[0].style.transform = `translate(${this.lerpingData["counterOne"].current}%, 0%)`;
        } else {
            this.lerpingData["counterOne"].current = -100;
            this.lerpingData["counterOne"].target = -100;
        }

        if (this.lerpingData["counterTwo"].target < 100) {
            this.DOMElements[1].style.transform = `translate(${this.lerpingData["counterTwo"].current}%, 0%)`;
        } else {
            this.lerpingData["counterTwo"].current = -100;
            this.lerpingData["counterTwo"].target = -100;
        }
    }

    render() {
        this.lerp();

        window.requestAnimationFrame(() => this.render());
    }
}

let textArray = document.getElementsByClassName("item");
new LoopingText(textArray);
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: "Poppins";
}

.hero-section {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100vh;
    overflow: hidden;
    position: relative;
    width: 100%;
}

.loop-container {
    position: relative;
    width: 100%;
    display: flex;
    /* padding-right: 24px; */
}

.item {
    position: absolute;
    font-size: 15rem;
    white-space: nowrap;
    margin: 0;
}

span {
    transition: all 0.2s;
    cursor: default;
}

.hover:hover {
    color: gray;
    transition: all 0.2s;
}
<body>
    <section class="hero-section">
        <div class="loop-container">
            <div class="item">Infinite Horizontal Looping Text</div>
            <div class="item">Infinite Horizontal Looping Text</div>
        </div>
    </section>
    <section class="hero-section">
    </section>
</body>

你的项目是重叠的,因为你不允许在项目应该交换位置时进行任何勒平差异

current 值应该永远不会 等于target 值。如果值匹配,则当前值需要 赶上 target — 给出不稳定的运动和错误的计算,另外加剧了两个应该完美的兄弟元素同步给予即时 snap-back,被视为流畅的连续运动。

解决方案

  • 不是独立地为两个(或更多)子级设置动画,
    只为父级设置动画.loop-container
  • 容器应与 一个子元素 一样
  • 使用position: absolute; left: -100%
  • 将一个子元素“推”到最左边
  • 允许 target 值始终 大于 current 值:
    target 值大于 100 — 将 current 设置为两个值的 负差 ,将 target 设置为 0

演示时间:

const lerp = (current, target, factor) => current * (1 - factor) + target * factor;

class LoopingText {
  constructor(el) {
    this.el = el;
    this.lerp = {current: 0, target: 0};
    this.interpolationFactor = 0.1;
    this.speed = 0.2;
    this.events();
    this.render();
  }

  events() {
    window.addEventListener("scroll", () => this.lerp.target += this.speed * 5);
  }

  animate() {
    this.lerp.target += this.speed;
    this.lerp.current = lerp(this.lerp.current, this.lerp.target, this.interpolationFactor);

    if (this.lerp.target > 100) {
      this.lerp.current -= this.lerp.target;
      this.lerp.target = 0;
    }

    this.el.style.transform = `translateX(${this.lerp.current}%)`;
  }

  render() {
    this.animate();
    window.requestAnimationFrame(() => this.render());
  }
}

document.querySelectorAll(".loop-container").forEach(el => new LoopingText(el));
/* QuickReset */ * { margin: 0; box-sizing: border-box; }

body { min-height: 400vh;  /* force some scrollbars */ }

.hero-section {
  position: relative;
  overflow: hidden;
  display: flex;
  min-height: 100vh;
}

.loop-container {
  margin: auto 0;
  white-space: nowrap;
  font: 900 9vw/1 sans-serif;
}

.item:first-child {
  position: absolute;
  left: -100%;
  top: 0;
}
<section class="hero-section">
  <div class="loop-container">
    <div class="item">Infinite Horizontal Looping Text&nbsp;</div>
    <div class="item">Infinite Horizontal Looping Text&nbsp;</div>
  </div>
</section>

PS:

制作动画时,(除非你想要一个静态/不可移动的元素)你不应该将元素转换放在 if/else 逻辑中。该元素应始终接收更新的转换。仅将您实际想要修改的值放入条件逻辑中(就像我在上面的示例中所做的那样)。