GSAP Guide

Every GSAP code used on this template is here. How to edit them and find them is explain on this page. In every code block on this page, we added additionnal explanation to help you understand everything.

(Site settings) Footer Code - Text Word animation
This GSAP setup enables smooth scroll-triggered text animations using SplitText and ScrollTrigger. Words animate into view as you scroll, with support for smooth scrolling via Lenis. All scripts are optimized for performance, prevent animation flickering, and include font-loading handling. Use custom attributes: data-word-reveal="true" on any *RICH TEXT element you want to animate word-by-word, and data-prevent-flicker="true" to avoid flicker before animation starts.
1<!-- GSAP Plugin Loader & Error Fallback -->
2
3<script>
4  // Waits for DOM to be ready
5  document.addEventListener("DOMContentLoaded", () => {
6    // Adds a .gsap-not-found class to <html> if GSAP is missing (useful for fallbacks)
7    if (typeof window.gsap === "undefined") document.documentElement.classList.add("gsap-not-found");
8
9    // Registers the required plugins (SplitText and ScrollTrigger)
10    gsap.registerPlugin(ScrollTrigger, SplitText);
11  });
12</script>
13
14<!-- GSAP Text Animation: Reveals words on scroll -->
15<script>
16  document.addEventListener("DOMContentLoaded", () => {
17    // Selects every element with the custom attribute
18    document.querySelectorAll("[data-word-reveal='true']").forEach((text) => {
19
20      // Splits the child elements into words and characters, and adds masking classes
21      const split = SplitText.create(text.children, {
22        type: "words, chars",          // Split into words and characters
23        mask: "words",                 // Only apply masking to words
24        wordsClass: "word",            // Add .word class
25        charsClass: "char",            // Add .char class
26      });
27
28      // Creates a timeline linked to scroll position
29      const tl = gsap.timeline({
30        scrollTrigger: {
31          trigger: text,              // Start animation when this element enters view
32          start: "top bottom",        // Animation begins when top of element hits bottom of viewport
33          end: "top 90%",             // Ends when top of element reaches 90% of viewport
34          toggleActions: "none play none reset", // Plays only once and resets on scroll back up
35        },
36      });
37
38      // Animates words upward into view
39      tl.from(split.words, {
40        yPercent: 110,                // Move each word up from 110%
41        delay: 0,                     // No delay before starting
42        duration: 0.5,                // Each word animates for 0.5s
43        stagger: { amount: 0.1 },     // Stagger all words over 0.1s
44      });
45
46      // Ensures visibility only after animation is applied
47      gsap.set(text, { visibility: "visible" });
48    });
49  });
50</script>
(Site settings) Footer Code - Smooth Scroll
This GSAP integration enables ultra-smooth scrolling using the Lenis library, fully synchronized with ScrollTrigger animations. It ensures a seamless scroll experience across devices, with built-in support for momentum, easing, and touch responsiveness. The setup uses GSAP’s ticker for validation compliance and performance, keeping scroll-based animations in perfect sync. To activate, no custom attributes are needed—Lenis handles all scrolling natively once initialized.
1<!-- Lenis Smooth Scroll -->
2
3<script src="https://unpkg.com/lenis@1.3.1/dist/lenis.min.js"></script>
4
5<script>
6document.addEventListener("DOMContentLoaded", () => {
7  // Register necessary plugins (required for validation)
8  gsap.registerPlugin(ScrollTrigger);
9
10  // Initialize Lenis
11  const lenis = new Lenis({
12    duration: 1.2,
13    easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), // expo.out
14    smooth: true,
15    smoothTouch: false,
16    touchMultiplier: 2
17  });
18
19  // Sync ScrollTrigger with Lenis
20  lenis.on("scroll", ScrollTrigger.update);
21
22  // Use GSAP's ticker to drive Lenis — this is required to pass GSAP validation
23  gsap.ticker.add((time) => {
24    lenis.raf(time * 1000);
25  });
26
27  // Optional: Fire custom event to trigger delayed animations
28  window.dispatchEvent(new CustomEvent("GSAPReady", {
29    detail: { lenis }
30  }));
31});
32</script>
(About) Hero images animation
This GSAP embed creates a 360-degree spinning carousel for hero images using 3D transforms. It’s fully interactive: users can drag horizontally to rotate the carousel, and it keeps spinning when idle. It adjusts dynamically on resize, uses Draggable and InertiaPlugin, and flips image content when it’s on the back side to keep everything facing forward. Add the attribute data-animate="inertia" to your carousel wrapper to activate this animation.
1<!-- GSAP 3D Hero Image Carousel (Auto-spin + Draggable) -->
2<script>
3{
4document.addEventListener("DOMContentLoaded", () => {
5  gsap.registerPlugin(Draggable, InertiaPlugin);
6    
7    // Get the wrapper element with attribute [data-animate="inertia"]
8    const wrapper = document.querySelector("[data-animate='inertia']");
9    
10    // Get the container and its child items
11    const items = [...wrapper.querySelector("[data-inertia='item']").children];
12
13    // === CONFIGURATION ===
14    let dragDistancePerRotation = 3000;             // Drag distance to complete one full rotation
15    let itemWidth = items[0].offsetWidth;           // Width of one item
16    let itemCount = items.length;                   // Total number of rotating items
17    let radius = (itemWidth / (1.2 * Math.sin(Math.PI / itemCount))) * 1; // Dynamic circular radius
18    const perspective = 5000;                       // Strength of 3D depth
19    const proxy = document.createElement("div");    // Invisible layer for dragging
20    const progressWrap = gsap.utils.wrap(0, 1);     // Ensures progress stays between 0-1
21    let startProgress = 0;                          // Stores rotation start point
22
23    // Apply 3D perspective to the wrapper
24    gsap.set(wrapper, {
25      perspective: perspective,
26      transformStyle: "preserve-3d",
27    });
28
29    // Set 3D rendering on container
30    const container = wrapper.querySelector("[data-inertia='item']");
31    gsap.set(container, {
32      transformStyle: "preserve-3d",
33    });
34
35    // Auto-spin animation
36    const spin = gsap.fromTo(
37      items,
38      {
39        rotationY: (i) => (i * 360) / items.length, // Evenly space items in a circle
40        z: -radius,
41      },
42      {
43        rotationY: "-=360",                        // Infinite spin to the left
44        duration: 80,                              // Very slow loop
45        ease: "none",
46        repeat: -1,
47        transformOrigin: "50% 50% " + -radius + "px",
48        z: -radius,
49      }
50    );
51
52    // Style the proxy element for dragging
53    proxy.style.position = "absolute";
54    proxy.style.width = "100%";
55    proxy.style.height = "100%";
56    proxy.style.top = "0";
57    proxy.style.left = "0";
58    proxy.style.zIndex = "1";
59    proxy.style.cursor = "grab";
60    wrapper.children[0].appendChild(proxy);
61
62    // Draggable config
63    Draggable.create(proxy, {
64      trigger: wrapper,
65      type: "x",
66      inertia: true,
67      onPress() {
68        // Pause the spin on drag
69        gsap.killTweensOf(spin);
70        spin.timeScale(0);
71        startProgress = spin.progress();
72      },
73      onDrag: updateRotation,
74      onThrowUpdate: updateRotation,
75      onRelease() {
76        // Resume spin when released
77        if (!this.tween || !this.tween.isActive()) {
78          gsap.to(spin, { timeScale: 1, duration: 1 });
79        }
80      },
81      onThrowComplete() {
82        gsap.to(spin, { timeScale: 1, duration: 1 });
83      },
84    });
85
86    // Adjust rotation based on drag
87    function updateRotation() {
88      let p = startProgress + (this.startX - this.x) / dragDistancePerRotation;
89      spin.progress(progressWrap(p));
90    }
91
92    // Recalculate item positions on resize
93    function recalculatePositions() {
94      itemWidth = items[0].offsetWidth;
95      itemCount = items.length;
96      radius = (itemWidth / (1 * Math.sin(Math.PI / itemCount))) * 0.8;
97
98      items.forEach((item, i) => {
99        gsap.set(item, {
100          rotationY: (i * 360) / itemCount,
101          z: -radius,
102        });
103      });
104
105      spin.vars.transformOrigin = "50% 50% " + -radius + "px";
106      spin.vars.z = -radius;
107      spin.invalidate(); // Refresh animation
108      gsap.set(wrapper, { perspective: perspective });
109    }
110
111    // Recalculate on window resize
112    window.addEventListener("resize", recalculatePositions);
113
114    // Prevent scrolling while dragging on mobile
115    wrapper.addEventListener(
116      "touchstart",
117      (e) => {
118        if (e.target === wrapper || e.target === proxy) {
119          e.preventDefault();
120        }
121      },
122      { passive: false }
123    );
124  });
125
126  // Ticker: Flip .inner-face when behind
127  gsap.ticker.add(() => {
128    items.forEach((item) => {
129      const face = item.querySelector(".inner-face");
130      if (!face) return;
131
132      const rotationY = gsap.getProperty(item, "rotationY") % 360;
133      const normalized = (rotationY + 360) % 360;
134
135      // Flip inner-face if the item is rotated backward
136      if (normalized > 90 && normalized < 270) {
137        gsap.set(face, { rotationY: 180 });
138      } else {
139        gsap.set(face, { rotationY: 0 });
140      }
141    });
142  });
143}
144</script>
(CTA) View projects - image trail animation
This script creates a mouse-following image trail effect using GSAP. When you move the cursor over a section with fc-trail-image="component", it dynamically fades and scales images in and out, leaving a trail. You can customize behavior using attributes like fc-trail-image-threshold, fc-trail-image-scale-from, and more—all within Webflow’s custom attribute panel.
1<!-- GSAP Trail Image Effect -->
2
3<style>
4  [fc-trail-image=list] img {
5    opacity: 0;
6    position: absolute;
7    will-change: transform;
8    pointer-events: none;
9    max-width: none;
10  }
11</style>
12
13<script>
14// Utility functions
15const MathUtils = {
16  lerp: (a, b, n) => (1 - n) * a + n * b,
17  distance: (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1)
18};
19
20// Get mouse position relative to the trail container
21const getMousePos = (e, container) => {
22  const rect = container.getBoundingClientRect();
23  return {
24    x: e.clientX - rect.left,
25    y: e.clientY - rect.top
26  };
27};
28
29// Represents a single image in the trail
30class Image {
31  constructor(el) {
32    this.DOM = { el };
33    this.defaultStyle = {
34      scale: 1,
35      x: 0,
36      y: 0,
37      opacity: 0
38    };
39    this.getRect();
40    this.initEvents();
41  }
42
43  initEvents() {
44    window.addEventListener('resize', () => this.resize());
45  }
46
47  resize() {
48    gsap.set(this.DOM.el, this.defaultStyle);
49    this.getRect();
50  }
51
52  getRect() {
53    this.rect = this.DOM.el.getBoundingClientRect();
54  }
55
56  isActive() {
57    return gsap.isTweening(this.DOM.el) || this.DOM.el.style.opacity != 0;
58  }
59}
60
61// Controls the image trail behavior
62class ImageTrail {
63  constructor(list, mouseThreshold, opacityFrom, scaleFrom, opacityTo, scaleTo, mainDuration, mainEase, fadeOutDuration, fadeOutDelay, fadeOutEase, resetIndex, resetIndexDelay) {
64    this.DOM = { content: list };
65    this.images = [];
66    [...this.DOM.content.querySelectorAll('img')].forEach(img => this.images.push(new Image(img)));
67    this.imagesTotal = this.images.length;
68    this.imgPosition = 0;
69    this.zIndexVal = 1;
70
71    // ✅ Attribute-based values from Webflow
72    this.threshold = isNaN(mouseThreshold) ? 100 : mouseThreshold;                  // `fc-trail-image-threshold`
73    this.opacityFrom = isNaN(opacityFrom) ? 0.6 : opacityFrom;                      // `fc-trail-image-opacity-from`
74    this.scaleFrom = isNaN(scaleFrom) ? 0.8 : scaleFrom;                            // `fc-trail-image-scale-from`
75    this.opacityTo = isNaN(opacityTo) ? 1 : opacityTo;                              // `fc-trail-image-opacity-to`
76    this.scaleTo = isNaN(scaleTo) ? 1 : scaleTo;                                    // `fc-trail-image-scale-to`
77    this.mainDuration = isNaN(mainDuration) ? 0.7 : mainDuration;                  // `fc-trail-image-main-duration`
78    this.mainEase = mainEase === null ? 'power3' : mainEase;                       // `fc-trail-image-main-ease`
79    this.fadeOutDuration = isNaN(fadeOutDuration) ? 1 : fadeOutDuration;          // `fc-trail-image-fade-out-duration`
80    this.fadeOutDelay = isNaN(fadeOutDelay) ? 0.3 : fadeOutDelay;                 // `fc-trail-image-fade-out-delay`
81    this.fadeOutEase = fadeOutEase === null ? 'power3' : fadeOutEase;             // `fc-trail-image-fade-out-ease`
82    this.resetIndex = resetIndex === null ? "false" : resetIndex;                 // `fc-trail-image-reset-index`
83    this.resetIndexDelay = isNaN(resetIndexDelay) ? 200 : resetIndexDelay;        // `fc-trail-image-reset-index-delay`
84
85    this.mousePos = { x: 0, y: 0 };
86    this.lastMousePos = { x: 0, y: 0 };
87    this.cacheMousePos = { x: 0, y: 0 };
88    this.frameCount = 0;
89    this.stopAnimationFrame = false;
90  }
91
92  render() {
93    const distance = MathUtils.distance(this.mousePos.x, this.mousePos.y, this.lastMousePos.x, this.lastMousePos.y);
94    this.cacheMousePos.x = MathUtils.lerp(this.cacheMousePos.x || this.mousePos.x, this.mousePos.x, 0.1);
95    this.cacheMousePos.y = MathUtils.lerp(this.cacheMousePos.y || this.mousePos.y, this.mousePos.y, 0.1);
96
97    if (distance > this.threshold) {
98      this.showNextImage();
99      ++this.zIndexVal;
100      this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
101      this.lastMousePos = this.mousePos;
102    }
103
104    let isIdle = this.images.every((img) => !img.isActive());
105
106    if (isIdle) {
107      this.frameCount++;
108      if (this.resetIndex === "true" && this.frameCount >= this.resetIndexDelay) {
109        this.frameCount = 0;
110        this.imgPosition = 0;
111      }
112      if (this.zIndexVal !== 1) {
113        this.zIndexVal = 1;
114      }
115    }
116
117    if (!this.stopAnimationFrame) requestAnimationFrame(() => this.render());
118  }
119
120  showNextImage() {
121    const img = this.images[this.imgPosition];
122    gsap.killTweensOf(img.DOM.el);
123
124    gsap.timeline()
125      .set(img.DOM.el, {
126        opacity: this.opacityFrom,
127        scale: this.scaleFrom,
128        zIndex: this.zIndexVal,
129        x: this.cacheMousePos.x - img.rect.width / 2,
130        y: this.cacheMousePos.y - img.rect.height / 2
131      })
132      .to(img.DOM.el, {
133        ease: this.mainEase,
134        x: this.mousePos.x - img.rect.width / 2,
135        y: this.mousePos.y - img.rect.height / 2,
136        opacity: this.opacityTo,
137        scale: this.scaleTo,
138        duration: this.mainDuration
139      })
140      .to(img.DOM.el, {
141        ease: this.fadeOutEase,
142        opacity: 0,
143        scale: this.scaleFrom,
144        duration: this.fadeOutDuration,
145        delay: this.fadeOutDelay,
146        onComplete: () => {
147          // Reset image style for reuse
148          gsap.set(img.DOM.el, {
149            x: 0,
150            y: 0,
151            scale: 1,
152            opacity: 0,
153            zIndex: 1
154          });
155        }
156      });
157  }
158}
159
160// Initialize after DOM is ready
161document.addEventListener("DOMContentLoaded", () => {
162  requestAnimationFrame(() => {
163    const components = document.querySelectorAll('[fc-trail-image=component]');
164    let imageTrails = [];
165
166    components.forEach((component, i) => {
167      const list = component.querySelector('[fc-trail-image=list]');
168      const mouseThreshold = parseInt(component.getAttribute('fc-trail-image-threshold'));
169      const opacityFrom = parseFloat(component.getAttribute('fc-trail-image-opacity-from'));
170      const scaleFrom = parseFloat(component.getAttribute('fc-trail-image-scale-from'));
171      const opacityTo = parseFloat(component.getAttribute('fc-trail-image-opacity-to'));
172      const scaleTo = parseFloat(component.getAttribute('fc-trail-image-scale-to'));
173      const mainDuration = parseFloat(component.getAttribute('fc-trail-image-main-duration'));
174      const mainEase = component.getAttribute('fc-trail-image-main-ease');
175      const fadeOutDuration = parseFloat(component.getAttribute('fc-trail-image-fade-out-duration'));
176      const fadeOutDelay = parseFloat(component.getAttribute('fc-trail-image-fade-out-delay'));
177      const fadeOutEase = component.getAttribute('fc-trail-image-fade-out-ease');
178      const resetIndex = component.getAttribute('fc-trail-image-reset-index');
179      const resetIndexDelay = parseInt(component.getAttribute('fc-trail-image-reset-index-delay'));
180
181      // Track mouse inside component
182      component.addEventListener('mousemove', function (ev) {
183        if (imageTrails[i].resetIndex === "true" && imageTrails[i].frameCount > 0)
184          imageTrails[i].frameCount = 0;
185        imageTrails[i].mousePos = getMousePos(ev, component);
186      });
187
188      // Start animation on hover
189      component.addEventListener("mouseenter", () => {
190        imageTrails[i].stopAnimationFrame = false;
191        requestAnimationFrame(() => imageTrails[i].render());
192
193        if (imageTrails[i].resetIndex === "true") {
194          imageTrails[i].imgPosition = 0;
195          imageTrails[i].frameCount = 0;
196        }
197      });
198
199      // Stop animation on mouse leave
200      component.addEventListener("mouseleave", () => {
201        imageTrails[i].stopAnimationFrame = true;
202      });
203
204      // Create instance of ImageTrail
205      imageTrails.push(new ImageTrail(
206        list,
207        mouseThreshold,
208        opacityFrom,
209        scaleFrom,
210        opacityTo,
211        scaleTo,
212        mainDuration,
213        mainEase,
214        fadeOutDuration,
215        fadeOutDelay,
216        fadeOutEase,
217        resetIndex,
218        resetIndexDelay
219      ));
220    });
221  });
222});
223</script>

Attributes

fc-trail-image="component"

*Required

Defines the entire interactive image trail wrapper

fc-trail-image="list"

*Required

The wrapper that contains all the <img> elements to animate

fc-trail-image-threshold

Optional

Minimum mouse movement (in px) before next image is triggered

fc-trail-image-opacity-from

Optional

Starting opacity value for each image (e.g. 0.6)

fc-trail-image-scale-from

Optional

Starting scale value for each image (e.g. 0.8)

fc-trail-image-opacity-to

Optional

Final opacity value when image appears (e.g. 1)

fc-trail-image-scale-to

Optional

Final scale value when image appears (e.g. 1)

fc-trail-image-main-duration

Optional

Duration (in seconds) of the image entering animation

fc-trail-image-main-ease

Optional

GSAP easing function (e.g. power3, sine.inOut)

fc-trail-image-fade-out-duration

Optional

How long the image takes to fade out

fc-trail-image-fade-out-delay

Optional

How long to wait before fading out

fc-trail-image-fade-out-ease

Optional

GSAP easing function for the fade out

fc-trail-image-reset-index

Optional

"true" resets image index when idle or on mouse enter

fc-trail-image-reset-index-delay

Optional

Number of frames before reset happens if reset-index is "true"

(Every pages) Marquee animation
This GSAP-powered script creates smooth infinite marquee animations for text or images. You can configure direction (horizontal or vertical), reverse animation, scroll-responsive behavior, pause on hover or click, and more — all using custom tr-marquee-* attributes directly in Webflow. Use this for creating dynamic banners, sliders, or text tickers with total control.

See below the code block how to use attributes.
1<script>
2// This code is for the MARQUEE Animations
3
4window.addEventListener("DOMContentLoaded", () => {
5  // Utility to parse attribute values
6  function attr(defaultVal, attrVal) {
7    const defaultValType = typeof defaultVal;
8    if (typeof attrVal !== "string" || attrVal.trim() === "") return defaultVal;
9    if (attrVal === "true" && defaultValType === "boolean") return true;
10    if (attrVal === "false" && defaultValType === "boolean") return false;
11    if (isNaN(attrVal) && defaultValType === "string") return attrVal;
12    if (!isNaN(attrVal) && defaultValType === "number") return +attrVal;
13    return defaultVal;
14  }
15
16  // Run marquee only after Lenis is ready (if you're dispatching the event)
17  const runMarquee = () => {
18    $("[tr-marquee-element='component']").each(function () {
19      const componentEl = $(this);
20      const panelEl = componentEl.find("[tr-marquee-element='panel']");
21      const triggerHoverEl = componentEl.find("[tr-marquee-element='triggerhover']");
22      const triggerClickEl = componentEl.find("[tr-marquee-element='triggerclick']");
23
24      let speedSetting = attr(100, componentEl.attr("tr-marquee-speed"));
25      const verticalSetting = attr(false, componentEl.attr("tr-marquee-vertical"));
26      const reverseSetting = attr(false, componentEl.attr("tr-marquee-reverse"));
27      const scrollDirectionSetting = attr(false, componentEl.attr("tr-marquee-scrolldirection"));
28      const scrollScrubSetting = attr(false, componentEl.attr("tr-marquee-scrollscrub"));
29
30      let moveDistanceSetting = reverseSetting ? 100 : -100;
31      let timeScaleSetting = 1;
32      let pausedStateSetting = false;
33
34      const marqueeTimeline = gsap.timeline({
35        repeat: -1,
36        onReverseComplete: () => marqueeTimeline.progress(1)
37      });
38
39      if (verticalSetting) {
40        speedSetting = panelEl.first().height() / speedSetting;
41        marqueeTimeline.fromTo(panelEl, { yPercent: 0 }, { yPercent: moveDistanceSetting, ease: "none", duration: speedSetting });
42      } else {
43        speedSetting = panelEl.first().width() / speedSetting;
44        marqueeTimeline.fromTo(panelEl, { xPercent: 0 }, { xPercent: moveDistanceSetting, ease: "none", duration: speedSetting });
45      }
46
47      const scrubObject = { value: 1 };
48
49      ScrollTrigger.create({
50        trigger: "body",
51        start: "top top",
52        end: "bottom bottom",
53        onUpdate: (self) => {
54          if (!pausedStateSetting) {
55            if (scrollDirectionSetting && timeScaleSetting !== self.direction) {
56              timeScaleSetting = self.direction;
57              marqueeTimeline.timeScale(self.direction);
58            }
59
60            if (scrollScrubSetting) {
61              let v = gsap.utils.clamp(-3, 3, self.getVelocity() * 0.002);
62              gsap.timeline({
63                onUpdate: () => marqueeTimeline.timeScale(scrubObject.value)
64              }).fromTo(scrubObject, { value: v }, {
65                value: timeScaleSetting,
66                duration: 1.2,
67                ease: "power2.out"
68              });
69            }
70          }
71        }
72      });
73
74      // Pause logic
75      function pauseMarquee(isPausing) {
76        pausedStateSetting = isPausing;
77        const pauseObject = { value: 1 };
78        const pauseTimeline = gsap.timeline({
79          onUpdate: () => marqueeTimeline.timeScale(pauseObject.value)
80        });
81
82        if (isPausing) {
83          pauseTimeline.fromTo(pauseObject, { value: timeScaleSetting }, { value: 0, duration: 0.5 });
84          triggerClickEl.addClass("is-paused");
85        } else {
86          pauseTimeline.fromTo(pauseObject, { value: 0 }, { value: timeScaleSetting, duration: 0.5 });
87          triggerClickEl.removeClass("is-paused");
88        }
89      }
90
91      if (window.matchMedia("(pointer: fine)").matches) {
92        triggerHoverEl.on("mouseenter", () => pauseMarquee(true));
93        triggerHoverEl.on("mouseleave", () => pauseMarquee(false));
94      }
95
96      triggerClickEl.on("click", function () {
97        !$(this).hasClass("is-paused") ? pauseMarquee(true) : pauseMarquee(false);
98      });
99    });
100  };
101  runMarquee();
102});
103</script>

Webflow Custom Attributes in this Script ⬆︎ (for Marquee animations)

These are custom HTML attributes you add directly to elements in Webflow using the Settings Panel. The script reads them and adjusts the behavior dynamically.

tr-marquee-element="component"

What it does:

Defines the main container for one marquee instance. The script will look inside this element to find everything related to that marquee.

Where to use it:

Apply to the outer wrapper of your marquee section (ex: a Div Block).

tr-marquee-element="panel"

What it does:

Defines the element that will move (i.e., your content inside the marquee).

Where to use it:

Apply to the element you want to scroll — typically a div containing repeated text/images. It should be a direct child (or inside) the "component" element.

tr-marquee-element="triggerhover"

What it does:

Lets the user pause the marquee on hover (only works on desktop devices).

Where to use it:

Add this to an element that users will hover to pause scrolling (usually the "panel" itself or a child of it).

tr-marquee-element="triggerclick"

What it does:

Lets users toggle the marquee pause/resume on click.

Where to use it:

Add to a clickable element (like a pause button or the panel itself). It adds/removes an .is-paused class based on state.

Optional Configuration Attributes (Behavior Modifiers)

These control how the animation behaves and can be changed without editing the JavaScript:

tr-marquee-speed="100"

What it does:

Controls the speed of the marquee (in pixels per second).

Higher = slower, Lower = faster.

Default: 100

Recommended range: 50 – 200

tr-marquee-vertical="true"

What it does:

Switches the marquee direction to vertical instead of horizontal.

Values:

  • "true" → vertical scrolling
  • "false" → horizontal (default)
tr-marquee-reverse="true"

What it does:

Reverses the direction of the marquee (left → right, or bottom → top).

Values:

  • "true" → reversed direction
  • "false" → normal

tr-marquee-scrolldirection="true"

What it does:

Enables scroll direction syncing.

The marquee will scroll forward when you scroll down, and reverse when scrolling up.

Values:

  • "true" → sync with scroll direction
  • "false" → ignore scroll direction (default)

tr-marquee-scrollscrub="true"

What it does:

Makes the marquee speed change dynamically based on your scroll velocity.

Values:

  • "true" → speed reacts to scroll velocity
  • "false" → constant speed (default)

*To delete animations, you only need to delete the code block linked to the animation. Every code block is identified.