最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do I make an Infinite marquee with JS? - Stack Overflow

programmeradmin1浏览0评论

I'm trying to make an Infinite marquee that speeds up on scroll, / you can see the effect on this website, the text says "Not your usual SEO agency" and when you scroll it speeds up.

Here's what I've tried but it does not work. It does not loop properly without overlapping (keep your eye on the left side of the page, you'll notice the text briefly overlaps and then translates left to create a gap) and I am unsure on how to fix it:

Here's the code (TEXT ONLY VISIBLE ON "FULL PAGE" view):

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(":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>

I'm trying to make an Infinite marquee that speeds up on scroll, https://altsdigital./ you can see the effect on this website, the text says "Not your usual SEO agency" and when you scroll it speeds up.

Here's what I've tried but it does not work. It does not loop properly without overlapping (keep your eye on the left side of the page, you'll notice the text briefly overlaps and then translates left to create a gap) and I am unsure on how to fix it:

Here's the code (TEXT ONLY VISIBLE ON "FULL PAGE" view):

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./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>

Share Improve this question edited Mar 13, 2022 at 2:25 awawawaw asked Feb 17, 2022 at 22:02 awawawawawawawaw 1954 silver badges15 bronze badges 7
  • looks sped up to me - can't read the text at all – Bravo Commented Feb 17, 2022 at 22:04
  • @Bravo the problem is that the text is overlapping on the loop. and then briefly translating left. – awawawaw Commented Feb 17, 2022 at 22:05
  • 1 oh, right, that's subtle but yes - perhaps make a note in the question that the issue is only visible on full page snippet :p – Bravo Commented Feb 17, 2022 at 22:13
  • 1 Your example does not change animation direction depending on scroll direction ;) – Roko C. Buljan Commented Feb 17, 2022 at 22:21
  • 1 @RokoC.Buljan I know, sorry let me remove that extra code. I was planning for it to do that later. But what causes the brief translation to the left and overlap of the text? I am unsure about that. – awawawaw Commented Feb 17, 2022 at 22:23
 |  Show 2 more ments

1 Answer 1

Reset to default 8

Your items are overlapping because you're not allowing any lerping diffing when the items should switch positions.

The current value should never equal the target value. If the values match, than the current value needs to catch up the target — giving that erratic movement and wrong calculations, additionally aggravated for the two sibling elements which should be perfectly in sync to give that immediate snap-back, perceived as a fluid continuous motion.

Solution

  • Instead of animating two (or more) children independently,
    animate only the parent .loop-container.
  • The container should be as wide as one child element exactly.
  • "Push" one child element to the far left using position: absolute; left: -100%
  • To allow the target value to be always greater than the current value:
    when the target value is greater than 100 — set current to the negative difference of the two values, and target to 0

Demo time:

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.direction = -1; // -1 (to-left), 1 (to-right) 
    
    // Init
    this.el.style.cssText = `position: relative; display: inline-flex; white-space: nowrap;`;
    this.el.children[1].style.cssText = `position: absolute; left: ${100 * -this.direction}%;`;
    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;
    }

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

  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;
  top: 50vh;
  overflow: hidden;
  font: 900 9vw/1 sans-serif;
  min-height: 100vh;
}
<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:

When animating, (unless you want an element static / immovable) you should never put an elements transformations inside an if/else logic. The element should always receive the updated transformations. Put inside the conditional logic only the values that you actually want to modify (as I did in the example above).

发布评论

评论列表(0)

  1. 暂无评论