instruction

Guide for Roofio Webflow Template

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 additional explanation to help you understand everything.

You can find the code in (Site settings) Footer Code.

STAT COUNTER ANIMATION

This script creates a smooth, scroll-triggered count-up animation for statistics (e.g., counting from 0 to 100+). To use it in Webflow give your statistic text element the class stat-number, and add two custom attributes in the element settings: data-target (the final number, like 150) and data-suffix (any optional symbol, like % or +). The animation will automatically trigger and play once as soon as the element scrolls into view.

// 1. Select all elements with the class '.stat-number'
const stats = document.querySelectorAll('.stat-number');

// 2. Loop through each statistic element to set up individual animations
stats.forEach((stat) => {
  // Extract the target number from the 'data-target' attribute and convert it to a number (+)
  const target = +stat.getAttribute('data-target');

  // Extract the optional suffix (like %, +, or k). Defaults to an empty string if missing
  const suffix = stat.getAttribute('data-suffix') || '';

  // Set the initial text display to "0" plus the suffix (e.g., "0%") before animation starts
  stat.innerText = '0' + suffix;

  // Create a temporary object to hold the counting value for GSAP to animate
  let countObj = { value: 0 };

  // 3. Initialize the GSAP animation
  gsap.to(countObj, {
    value: target, // Animate the value from 0 to the target number
    duration: 2.3, // Animation duration in seconds
    ease: 'power3.out', // Smooth deceleration easing effect

    // Trigger the animation based on scroll position
    scrollTrigger: {
      trigger: stat, // Element that triggers the animation
      start: 'top bottom', // Starts when the top of the element hits the bottom of the viewport
      toggleActions: 'play none none none', // Plays once when entering, ignores scroll-backs
    },

    // Function that runs on every frame of the animation
    onUpdate: () => {
      // Round down to the nearest whole number and update the text with the suffix
      stat.innerText = Math.floor(countObj.value) + suffix;
    },
  });
});

TEXT SCRUB REVEAL ANIMATION

This script creates a scroll-driven text reveal, where words light up one by one from a faded opacity as the user scrolls down the page. To use it in Webflow add the custom attribute scrub-each-word (leave the value blank) to any text block or heading you want to animate.

// 1. Split text inside elements with the '[scrub-each-word]' attribute into individual word spans
let typeSplit = new SplitType("[scrub-each-word]", {
  types: "words",
  tagName: "span"
});

// 2. Loop through each text element to apply the scroll animation
$("[scrub-each-word]").each(function () {
  // Find all the split words inside the current text element
  const words = $(this).find(".word");

  // Create a GSAP timeline tied to the page scroll
  let tl = gsap.timeline({
    scrollTrigger: {
      trigger: $(this),    // Trigger animation when this text element enters the viewport
      start: "top 90%",    // Animation starts when the top of the element hits 90% from the top of the screen
      end: "top 15%",      // Animation ends when it reaches 15% from the top of the screen
      scrub: true          // Links the animation progress directly to the user's scroll speed
    }
  });

  // Animate the words by having them start slightly faded and light up as you scroll
  tl.from(words, { 
    opacity: 0.4,          // Start at 40% opacity
    duration: 0.2, 
    ease: "none",          // Linear transition for a smooth, consistent scrub effect
    stagger: { each: 0.4 } // Animate the words one after the other in sequence
  });
});

// 3. Reveal the main container instantly so there is no layout flash before the split happens
gsap.set("[scrub-each-word]", { opacity: 1 });

BLUR STAGGER ANIMATION

This script creates a premium, high-end text reveal animation that unblurs and fades letters or words into place as you scroll. To use it in Webflow add one of these custom attributes to your text blocks or headings: data-animation="blur-stagger" to make full words slide in from the right, or data-animation="blur-stagger-chars" to make individual letters float upwards from the bottom.

<style>
  /* 1. Prevent Flash of Unstyled Content (FOUC) */
  /* Hide both text elements instantly when the page starts loading to avoid flashing before split happens */
  [data-animation="blur-stagger"],
  [data-animation="blur-stagger-chars"] {
    opacity: 0;
  }
</style>

<script>
  // 2. Select all text elements using either the word-stagger or character-stagger configuration
  const blurTargetElements = document.querySelectorAll('[data-animation="blur-stagger"], [data-animation="blur-stagger-chars"]');

  // Loop through each element to set up its unique animation settings
  blurTargetElements.forEach((element) => {
    
    // Check which animation mode this specific element is using
    const animType = element.getAttribute('data-animation');
    const isCharMode = animType === 'blur-stagger-chars';
    
    // 3. Split the text using SplitType based on the chosen mode (letters vs full words)
    const splitTypeOptions = isCharMode ? { types: 'words, chars' } : { types: 'words' };
    const splitText = new SplitType(element, splitTypeOptions);
    
    // 4. Set the default baseline visual variables for a smooth, premium feel
    let animationVars = {
      opacity: 0,
      filter: "blur(8px)",  // The starting blur amount before it clears up
      x: 0,
      y: 0,
      duration: 1.2,        // Longer duration for an elegant, cinematic transition
      stagger: 0.06,        // Time gap between each animated item
      ease: "quart.out"     // "Quart.out" creates a smooth, high-end deceleration
    };

    // 5. Customize directional movement based on the mode
    if (isCharMode) {
      // Character Mode: Letters float up smoothly from 15px down
      animationVars.y = 15; 
      animationVars.stagger = 0.06;
    } else {
      // Word Mode: Full words slide in sideways from 14px to the right
      animationVars.x = 14; 
    }

    // 6. Define the final animation targets (either the array of letters or array of words)
    const animTargets = isCharMode ? splitText.chars : splitText.words;

    // Optimize browser rendering performance to prevent stuttering/lag during animation
    gsap.set(animTargets, { willChange: "transform, filter, opacity" });
        
    // 7. Run the scroll-triggered animation
    gsap.from(animTargets, {
      ...animationVars,     // Inject the configured styling variables from above
      scrollTrigger: {
        trigger: element,   // Trigger animation when this text element hits the viewport
        start: "top 88%",   // Starts when the element is 12% above the bottom of the screen
        toggleActions: "play none none none" // Plays once when visible and ignores scroll-backs
      }
    });
  });

  // 8. Safely reveal the main parent containers once the script has successfully loaded
  gsap.set('[data-animation="blur-stagger"], [data-animation="blur-stagger-chars"]', { opacity: 1 });
</script>

Interactive Infinite Marquee

This script creates an Interactive Infinite Marquee that seamlessly loops content horizontally and pauses instantly whenever a user hovers over it. To use it in Webflow Create an outer container div with the class .awards-marquee-box, and put your moving list div inside it with the class .awards-list. By default, it scrolls to the left, but you can change its direction by adding the custom attribute data-direction="right" to the outer wrapper element.

// 1. Find all marquee wrapper containers on the page
gsap.utils.toArray(".awards-marquee-box").forEach((wrap) => {
  // Find the moving track(s) inside this specific wrapper
  const tracks = wrap.querySelectorAll(".awards-list");
  
  // 2. Check the Webflow custom attribute for movement direction (defaults to left)
  const direction = wrap.getAttribute("data-direction") === "right" ? "right" : "left";

  // 3. Create individual animations for each track inside the wrapper
  const tweens = Array.from(tracks).map((track) => {
    // Determine the start and end positions based on the direction attribute
    const startX = direction === "right" ? -100 : 0;
    const endX = direction === "right" ? 0 : -100;

    // Set the starting position instantly
    gsap.set(track, { xPercent: startX });

    // Create and return the infinite seamless loop animation
    return gsap.to(track, { 
      xPercent: endX, 
      duration: 25,    // Time in seconds to complete one full loop (lower = faster)
      ease: "none",    // Linear ease keeps the speed perfectly constant
      repeat: -1       // Loops infinitely
    });
  });

  // 4. Interactive Hover Controls: Pause when mouse enters, resume when mouse leaves
  wrap.addEventListener("mouseenter", () => tweens.forEach(t => t.pause()));
  wrap.addEventListener("mouseleave", () => tweens.forEach(t => t.resume()));
});
Powered By Rowdy Designs