Apply marquee effect to text
by Jordan Sayner
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/ScrollTrigger.min.js"></script>
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<section class="home-hero_section"><img src="https://assets.website-files.com/63b6f4497a8b7545922c200a/63b79f31efdaa8435776b63b_894164_All8Flavors_Cans_TropicalBerry_020821.png" alt="" class="home-hero_img"><div tr-marquee-scrolldirection="true" tr-marquee-element="component" class="marquee-text_component"><div class="marquee-text_wrapper"><div tr-marquee-element="panel" class="marquee-text_panel" style="translate: none; rotate: none; scale: none; transform: translate(-39.7212%, 0%) translate3d(0px, 0px, 0px);"><div class="marquee-text_text">BOOSTING IMMUNITY</div><img src="https://assets.website-files.com/63b6f4497a8b7545922c200a/63b79e762ba2a51e3b81f866_bolt.svg" alt="" class="marquee-text_icon"></div><div tr-marquee-element="panel" class="marquee-text_panel" style="translate: none; rotate: none; scale: none; transform: translate(-39.7212%, 0%) translate3d(0px, 0px, 0px);"><div class="marquee-text_text">BOOSTING IMMUNITY</div><img src="https://assets.website-files.com/63b6f4497a8b7545922c200a/63b79e762ba2a51e3b81f866_bolt.svg" alt="" class="marquee-text_icon"></div></div></div><div tr-marquee-reverse="true" tr-marquee-element="component" tr-marquee-scrolldirection="true" class="marquee-text_component is-2"><div class="marquee-text_wrapper is-2"><div tr-marquee-element="panel" class="marquee-text_panel" style="translate: none; rotate: none; scale: none; transform: translate(48.1281%, 0%) translate3d(0px, 0px, 0px);"><div class="marquee-text_text">INCREASING ENDURANCE</div><img src="https://assets.website-files.com/63b6f4497a8b7545922c200a/63b79e762ba2a51e3b81f866_bolt.svg" alt="" class="marquee-text_icon"></div><div tr-marquee-element="panel"...
CSS
.home-hero_section {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex
;
min-height: 100vh;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
background-image: -webkit-gradient(linear, left top, left bottom, from(#caf587), color-stop(81%, transparent));
background-image: linear-gradient(180deg, #caf587, transparent 81%);
}
.home-hero_img {
position: absolute;
z-index: 2;
display: block;
width: 25%;
max-width: 48vh;
-webkit-transform: rotate(-9deg);
-ms-transform: rotate(-9deg);
transform: rotate(-9deg);
pointer-events: none;
}
.marquee-text_component {
width: 100%;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
font-size: 1.2vw;
}
.marquee-text_wrapper {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
overflow: hidden;
width: 100%;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}
.marquee-text_panel {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-flex:...
JavaScript
document.querySelectorAll("[tr-marquee-element='component']").forEach((component, index) => {
const panels = component.querySelectorAll("[tr-marquee-element='panel']");
const speed = 100;
const baseSpeed = panels[0].offsetWidth;
const duration = baseSpeed / speed;
const normalMoveDist = index % 2 === 0 ? -100 : 100;
const timeline = gsap.timeline({ repeat: -1, defaults: { ease: "none" } });
timeline.fromTo(panels, { xPercent: 0 }, { xPercent: normalMoveDist, duration });
timeline.timeScale(1);
let resetSpeedTimeout;
ScrollTrigger.create({
trigger: "body",
start: "top top",
end: "bottom bottom",
onUpdate: self => {
clearTimeout(resetSpeedTimeout);
const velocity = gsap.utils.clamp(-10, 10, self.getVelocity() * 0.006);
const speedFactor = 1 + Math.abs(velocity);
timeline.timeScale(speedFactor);
// After 150ms of no scroll update, reset speed back to normal (1) smoothly
resetSpeedTimeout = setTimeout(() => {
gsap.to(timeline, { timeScale: 1, duration: 0.5, ease: "power2.out" });
}, 150);
}
});
});