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
1 Answer
Reset to default 8Your 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 thecurrent
value:
when thetarget
value is greater than100
— setcurrent
to the negative difference of the two values, andtarget
to0
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 </div>
<div class="item">Infinite Horizontal Looping Text </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).