Marquee Vertical Item Progress
Marquee
Loop
GSAP
JS
CMS
Preview Links
Custom JS
Enable custom code in preview or view on published site.
Enable custom code?
Last Updated: Dec 4, 2025
- gap and padding-bottom on the marquee-3_list must match for the list to loop seamlessly
- The second marquee-3_panel has an attribute of aria-hidden="true" so duplicated content isn't read by screen readers
- When images are inside a marquee, they should be set to eager load to avoid flicker
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".marquee-3_component").forEach((component) => {
if (component.hasAttribute("data-marquee-3")) return;
component.setAttribute("data-marquee-3", "");
gsap.context(() => {
let marquee = gsap.timeline({ repeat: -1 });
marquee.fromTo(".marquee-3_track", { yPercent: 0 }, { yPercent: -50, duration: 10, ease: "none" });
}, component);
component.querySelectorAll(".marquee-3_item").forEach((item) => {
gsap.context(() => {
let tl = gsap.timeline({
paused: true,
defaults: { ease: "none" }
});
tl.fromTo(item, { x: "8em" }, { x: "0em", ease: "power1.out" });
tl.fromTo(item, { opacity: 0.2 }, { opacity: 1, ease: "none" }, "<");
tl.to(item, { x: "8em", ease: "power1.in" });
tl.to(item, { opacity: 0.2, ease: "none" }, "<");
function animate() {
const rect = item.getBoundingClientRect();
const compRect = component.getBoundingClientRect();
let progress = Math.max(0, Math.min(1, (compRect.bottom - rect.top) / (rect.height + compRect.height)));
tl.progress(progress);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
}, item);
});
});
});
</script>