chunk-019-scroll-animation.js

I’ll build this as a standalone advanced scroll-animation chunk for RX Theme: safe defaults, accessibility support, performance-friendly observers, counters, progress bars, parallax, sticky reveal, and WordPress-friendly auto-init.

Below is a complete advanced code for:

assets/static-js/static-chunks-js/chunk-019-scroll-animation.js

It is written as a safe, performance-focused, WordPress/RX Theme friendly scroll animation chunk.

/*!
 * RX Theme - Chunk 019 Scroll Animation
 * File: assets/static-js/static-chunks-js/chunk-019-scroll-animation.js
 * Version: 1.0.0
 *
 * Features:
 * - Reveal on scroll
 * - Stagger animation
 * - Counter animation
 * - Progress bar animation
 * - Circle progress animation
 * - Parallax effect
 * - Sticky class on scroll
 * - Scroll progress indicator
 * - Back to top button
 * - Header show/hide on scroll
 * - Section active class
 * - Reduced motion support
 * - IntersectionObserver based performance
 * - WordPress friendly auto-init
 */

(function () {
  'use strict';

  /**
   * Prevent duplicate loading.
   */
  if (window.RXScrollAnimationLoaded) {
    return;
  }

  window.RXScrollAnimationLoaded = true;

  /**
   * Main namespace.
   */
  window.RXTheme = window.RXTheme || {};

  const RXScrollAnimation = {
    config: {
      revealSelector: '[data-rx-reveal], .rx-reveal',
      staggerSelector: '[data-rx-stagger]',
      counterSelector: '[data-rx-counter]',
      progressSelector: '[data-rx-progress]',
      circleProgressSelector: '[data-rx-circle-progress]',
      parallaxSelector: '[data-rx-parallax]',
      stickySelector: '[data-rx-sticky]',
      sectionSelector: '[data-rx-section]',
      scrollProgressSelector: '[data-rx-scroll-progress]',
      backToTopSelector: '[data-rx-back-to-top]',
      headerSelector: '[data-rx-scroll-header]',

      revealVisibleClass: 'rx-is-visible',
      revealHiddenClass: 'rx-is-hidden',
      activeClass: 'rx-is-active',
      stickyClass: 'rx-is-sticky',
      headerHiddenClass: 'rx-header-hidden',
      headerVisibleClass: 'rx-header-visible',

      rootMargin: '0px 0px -10% 0px',
      threshold: 0.12,

      counterDuration: 1600,
      progressDuration: 1400,
      circleDuration: 1400,

      parallaxStrength: 0.18,
      stickyOffset: 80,
      headerHideOffset: 120,
      backToTopOffset: 400,

      enableReveal: true,
      enableStagger: true,
      enableCounters: true,
      enableProgress: true,
      enableCircleProgress: true,
      enableParallax: true,
      enableSticky: true,
      enableScrollProgress: true,
      enableBackToTop: true,
      enableHeaderScroll: true,
      enableSectionSpy: true
    },

    state: {
      lastScrollY: 0,
      ticking: false,
      reducedMotion: false,
      observers: [],
      animatedCounters: new WeakSet(),
      animatedProgress: new WeakSet(),
      animatedCircleProgress: new WeakSet()
    },

    init() {
      this.state.reducedMotion = window.matchMedia &&
        window.matchMedia('(prefers-reduced-motion: reduce)').matches;

      this.applyBaseClasses();

      if (this.config.enableReveal) {
        this.initReveal();
      }

      if (this.config.enableStagger) {
        this.initStagger();
      }

      if (this.config.enableCounters) {
        this.initCounters();
      }

      if (this.config.enableProgress) {
        this.initProgressBars();
      }

      if (this.config.enableCircleProgress) {
        this.initCircleProgress();
      }

      if (this.config.enableScrollProgress) {
        this.initScrollProgress();
      }

      if (this.config.enableBackToTop) {
        this.initBackToTop();
      }

      if (
        this.config.enableParallax ||
        this.config.enableSticky ||
        this.config.enableHeaderScroll ||
        this.config.enableSectionSpy
      ) {
        this.bindScrollEvents();
      }

      this.bindResizeEvents();

      document.documentElement.classList.add('rx-scroll-animation-ready');
    },

    applyBaseClasses() {
      const revealItems = document.querySelectorAll(this.config.revealSelector);

      revealItems.forEach((el) => {
        if (!el.classList.contains(this.config.revealVisibleClass)) {
          el.classList.add(this.config.revealHiddenClass);
        }
      });
    },

    createObserver(callback, options = {}) {
      if (!('IntersectionObserver' in window)) {
        return null;
      }

      const observer = new IntersectionObserver(callback, {
        root: null,
        rootMargin: options.rootMargin || this.config.rootMargin,
        threshold: options.threshold || this.config.threshold
      });

      this.state.observers.push(observer);

      return observer;
    },

    initReveal() {
      const elements = document.querySelectorAll(this.config.revealSelector);

      if (!elements.length) {
        return;
      }

      if (this.state.reducedMotion || !('IntersectionObserver' in window)) {
        elements.forEach((el) => this.showElement(el));
        return;
      }

      const observer = this.createObserver((entries, obs) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            this.showElement(entry.target);
            obs.unobserve(entry.target);
          }
        });
      });

      elements.forEach((el) => observer.observe(el));
    },

    showElement(el) {
      const delay = parseInt(el.getAttribute('data-rx-delay') || '0', 10);
      const duration = parseInt(el.getAttribute('data-rx-duration') || '650', 10);
      const animation = el.getAttribute('data-rx-animation') || '';

      if (animation) {
        el.setAttribute('data-rx-animation-active', animation);
      }

      el.style.transitionDuration = `${duration}ms`;

      window.setTimeout(() => {
        el.classList.remove(this.config.revealHiddenClass);
        el.classList.add(this.config.revealVisibleClass);
      }, Math.max(delay, 0));
    },

    initStagger() {
      const groups = document.querySelectorAll(this.config.staggerSelector);

      if (!groups.length) {
        return;
      }

      groups.forEach((group) => {
        const childrenSelector = group.getAttribute('data-rx-stagger-child') || '> *';
        const delayStep = parseInt(group.getAttribute('data-rx-stagger-delay') || '90', 10);
        const children = group.querySelectorAll(childrenSelector);

        children.forEach((child, index) => {
          child.classList.add(this.config.revealHiddenClass);
          child.setAttribute('data-rx-delay', String(index * delayStep));
        });
      });

      if (this.state.reducedMotion || !('IntersectionObserver' in window)) {
        groups.forEach((group) => {
          const childrenSelector = group.getAttribute('data-rx-stagger-child') || '> *';
          group.querySelectorAll(childrenSelector).forEach((child) => this.showElement(child));
        });
        return;
      }

      const observer = this.createObserver((entries, obs) => {
        entries.forEach((entry) => {
          if (!entry.isIntersecting) {
            return;
          }

          const group = entry.target;
          const childrenSelector = group.getAttribute('data-rx-stagger-child') || '> *';
          const children = group.querySelectorAll(childrenSelector);

          children.forEach((child) => this.showElement(child));
          obs.unobserve(group);
        });
      });

      groups.forEach((group) => observer.observe(group));
    },

    initCounters() {
      const counters = document.querySelectorAll(this.config.counterSelector);

      if (!counters.length) {
        return;
      }

      if (this.state.reducedMotion || !('IntersectionObserver' in window)) {
        counters.forEach((counter) => this.finishCounter(counter));
        return;
      }

      const observer = this.createObserver((entries, obs) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            this.animateCounter(entry.target);
            obs.unobserve(entry.target);
          }
        });
      });

      counters.forEach((counter) => observer.observe(counter));
    },

    animateCounter(el) {
      if (this.state.animatedCounters.has(el)) {
        return;
      }

      this.state.animatedCounters.add(el);

      const start = parseFloat(el.getAttribute('data-rx-counter-start') || '0');
      const end = parseFloat(el.getAttribute('data-rx-counter') || el.textContent || '0');
      const duration = parseInt(el.getAttribute('data-rx-counter-duration') || this.config.counterDuration, 10);
      const decimals = parseInt(el.getAttribute('data-rx-counter-decimals') || '0', 10);
      const prefix = el.getAttribute('data-rx-counter-prefix') || '';
      const suffix = el.getAttribute('data-rx-counter-suffix') || '';
      const separator = el.getAttribute('data-rx-counter-separator') || '';

      const startTime = performance.now();

      const update = (now) => {
        const elapsed = now - startTime;
        const progress = Math.min(elapsed / duration, 1);
        const eased = this.easeOutCubic(progress);
        const value = start + (end - start) * eased;

        el.textContent = prefix + this.formatNumber(value, decimals, separator) + suffix;

        if (progress < 1) {
          requestAnimationFrame(update);
        } else {
          el.textContent = prefix + this.formatNumber(end, decimals, separator) + suffix;
        }
      };

      requestAnimationFrame(update);
    },

    finishCounter(el) {
      const end = parseFloat(el.getAttribute('data-rx-counter') || el.textContent || '0');
      const decimals = parseInt(el.getAttribute('data-rx-counter-decimals') || '0', 10);
      const prefix = el.getAttribute('data-rx-counter-prefix') || '';
      const suffix = el.getAttribute('data-rx-counter-suffix') || '';
      const separator = el.getAttribute('data-rx-counter-separator') || '';

      el.textContent = prefix + this.formatNumber(end, decimals, separator) + suffix;
    },

    initProgressBars() {
      const bars = document.querySelectorAll(this.config.progressSelector);

      if (!bars.length) {
        return;
      }

      bars.forEach((bar) => {
        const value = this.clamp(parseFloat(bar.getAttribute('data-rx-progress') || '0'), 0, 100);
        bar.style.setProperty('--rx-progress-value', '0%');
        bar.setAttribute('aria-valuemin', '0');
        bar.setAttribute('aria-valuemax', '100');
        bar.setAttribute('aria-valuenow', '0');
        bar.setAttribute('role', 'progressbar');
        bar.dataset.rxProgressTarget = String(value);
      });

      if (this.state.reducedMotion || !('IntersectionObserver' in window)) {
        bars.forEach((bar) => this.finishProgressBar(bar));
        return;
      }

      const observer = this.createObserver((entries, obs) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            this.animateProgressBar(entry.target);
            obs.unobserve(entry.target);
          }
        });
      });

      bars.forEach((bar) => observer.observe(bar));
    },

    animateProgressBar(bar) {
      if (this.state.animatedProgress.has(bar)) {
        return;
      }

      this.state.animatedProgress.add(bar);

      const target = this.clamp(parseFloat(bar.dataset.rxProgressTarget || '0'), 0, 100);
      const duration = parseInt(bar.getAttribute('data-rx-progress-duration') || this.config.progressDuration, 10);
      const startTime = performance.now();

      const update = (now) => {
        const elapsed = now - startTime;
        const progress = Math.min(elapsed / duration, 1);
        const eased = this.easeOutCubic(progress);
        const value = target * eased;

        bar.style.setProperty('--rx-progress-value', `${value}%`);
        bar.setAttribute('aria-valuenow', String(Math.round(value)));

        if (progress < 1) {
          requestAnimationFrame(update);
        } else {
          bar.style.setProperty('--rx-progress-value', `${target}%`);
          bar.setAttribute('aria-valuenow', String(Math.round(target)));
        }
      };

      requestAnimationFrame(update);
    },

    finishProgressBar(bar) {
      const target = this.clamp(parseFloat(bar.dataset.rxProgressTarget || '0'), 0, 100);
      bar.style.setProperty('--rx-progress-value', `${target}%`);
      bar.setAttribute('aria-valuenow', String(Math.round(target)));
    },

    initCircleProgress() {
      const circles = document.querySelectorAll(this.config.circleProgressSelector);

      if (!circles.length) {
        return;
      }

      circles.forEach((circle) => {
        const value = this.clamp(parseFloat(circle.getAttribute('data-rx-circle-progress') || '0'), 0, 100);
        circle.style.setProperty('--rx-circle-progress-value', '0');
        circle.setAttribute('aria-valuemin', '0');
        circle.setAttribute('aria-valuemax', '100');
        circle.setAttribute('aria-valuenow', '0');
        circle.setAttribute('role', 'progressbar');
        circle.dataset.rxCircleProgressTarget = String(value);
      });

      if (this.state.reducedMotion || !('IntersectionObserver' in window)) {
        circles.forEach((circle) => this.finishCircleProgress(circle));
        return;
      }

      const observer = this.createObserver((entries, obs) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            this.animateCircleProgress(entry.target);
            obs.unobserve(entry.target);
          }
        });
      });

      circles.forEach((circle) => observer.observe(circle));
    },

    animateCircleProgress(circle) {
      if (this.state.animatedCircleProgress.has(circle)) {
        return;
      }

      this.state.animatedCircleProgress.add(circle);

      const target = this.clamp(parseFloat(circle.dataset.rxCircleProgressTarget || '0'), 0, 100);
      const duration = parseInt(circle.getAttribute('data-rx-circle-duration') || this.config.circleDuration, 10);
      const label = circle.querySelector('[data-rx-circle-label]');
      const startTime = performance.now();

      const update = (now) => {
        const elapsed = now - startTime;
        const progress = Math.min(elapsed / duration, 1);
        const eased = this.easeOutCubic(progress);
        const value = target * eased;

        circle.style.setProperty('--rx-circle-progress-value', String(value));
        circle.setAttribute('aria-valuenow', String(Math.round(value)));

        if (label) {
          label.textContent = `${Math.round(value)}%`;
        }

        if (progress < 1) {
          requestAnimationFrame(update);
        } else {
          circle.style.setProperty('--rx-circle-progress-value', String(target));
          circle.setAttribute('aria-valuenow', String(Math.round(target)));

          if (label) {
            label.textContent = `${Math.round(target)}%`;
          }
        }
      };

      requestAnimationFrame(update);
    },

    finishCircleProgress(circle) {
      const target = this.clamp(parseFloat(circle.dataset.rxCircleProgressTarget || '0'), 0, 100);
      const label = circle.querySelector('[data-rx-circle-label]');

      circle.style.setProperty('--rx-circle-progress-value', String(target));
      circle.setAttribute('aria-valuenow', String(Math.round(target)));

      if (label) {
        label.textContent = `${Math.round(target)}%`;
      }
    },

    initScrollProgress() {
      const progressItems = document.querySelectorAll(this.config.scrollProgressSelector);

      if (!progressItems.length) {
        return;
      }

      this.updateScrollProgress();
    },

    initBackToTop() {
      const buttons = document.querySelectorAll(this.config.backToTopSelector);

      if (!buttons.length) {
        return;
      }

      buttons.forEach((button) => {
        button.setAttribute('type', 'button');

        button.addEventListener('click', (event) => {
          event.preventDefault();

          window.scrollTo({
            top: 0,
            behavior: this.state.reducedMotion ? 'auto' : 'smooth'
          });
        });
      });

      this.updateBackToTop();
    },

    bindScrollEvents() {
      window.addEventListener(
        'scroll',
        () => {
          this.requestTick();
        },
        {
          passive: true
        }
      );

      this.requestTick();
    },

    bindResizeEvents() {
      let resizeTimer = null;

      window.addEventListener(
        'resize',
        () => {
          window.clearTimeout(resizeTimer);

          resizeTimer = window.setTimeout(() => {
            this.requestTick();
          }, 120);
        },
        {
          passive: true
        }
      );
    },

    requestTick() {
      if (!this.state.ticking) {
        window.requestAnimationFrame(() => {
          this.onScrollFrame();
          this.state.ticking = false;
        });

        this.state.ticking = true;
      }
    },

    onScrollFrame() {
      const currentScrollY = window.scrollY || window.pageYOffset || 0;
      const direction = currentScrollY > this.state.lastScrollY ? 'down' : 'up';

      if (this.config.enableParallax && !this.state.reducedMotion) {
        this.updateParallax(currentScrollY);
      }

      if (this.config.enableSticky) {
        this.updateSticky(currentScrollY);
      }

      if (this.config.enableScrollProgress) {
        this.updateScrollProgress();
      }

      if (this.config.enableBackToTop) {
        this.updateBackToTop(currentScrollY);
      }

      if (this.config.enableHeaderScroll) {
        this.updateHeaderScroll(currentScrollY, direction);
      }

      if (this.config.enableSectionSpy) {
        this.updateSectionSpy();
      }

      this.state.lastScrollY = Math.max(currentScrollY, 0);
    },

    updateParallax(scrollY) {
      const items = document.querySelectorAll(this.config.parallaxSelector);

      if (!items.length) {
        return;
      }

      items.forEach((el) => {
        const rect = el.getBoundingClientRect();
        const speed = parseFloat(el.getAttribute('data-rx-parallax-speed') || this.config.parallaxStrength);
        const direction = el.getAttribute('data-rx-parallax-direction') || 'vertical';
        const viewportHeight = window.innerHeight || document.documentElement.clientHeight;

        if (rect.bottom < 0 || rect.top > viewportHeight) {
          return;
        }

        const centerOffset = rect.top + rect.height / 2 - viewportHeight / 2;
        const movement = centerOffset * speed * -1;

        if (direction === 'horizontal') {
          el.style.transform = `translate3d(${movement}px, 0, 0)`;
        } else {
          el.style.transform = `translate3d(0, ${movement}px, 0)`;
        }
      });
    },

    updateSticky(scrollY) {
      const items = document.querySelectorAll(this.config.stickySelector);

      if (!items.length) {
        return;
      }

      items.forEach((el) => {
        const offset = parseInt(el.getAttribute('data-rx-sticky-offset') || this.config.stickyOffset, 10);
        const targetSelector = el.getAttribute('data-rx-sticky-target');
        const target = targetSelector ? document.querySelector(targetSelector) : null;

        let shouldStick = scrollY > offset;

        if (target) {
          const targetRect = target.getBoundingClientRect();
          shouldStick = targetRect.top <= offset && targetRect.bottom > offset;
        }

        el.classList.toggle(this.config.stickyClass, shouldStick);
      });
    },

    updateScrollProgress() {
      const progressItems = document.querySelectorAll(this.config.scrollProgressSelector);

      if (!progressItems.length) {
        return;
      }

      const doc = document.documentElement;
      const scrollTop = window.scrollY || doc.scrollTop || 0;
      const scrollHeight = doc.scrollHeight - doc.clientHeight;
      const percentage = scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0;
      const safePercentage = this.clamp(percentage, 0, 100);

      progressItems.forEach((el) => {
        el.style.setProperty('--rx-scroll-progress', `${safePercentage}%`);
        el.setAttribute('aria-valuemin', '0');
        el.setAttribute('aria-valuemax', '100');
        el.setAttribute('aria-valuenow', String(Math.round(safePercentage)));
        el.setAttribute('role', 'progressbar');
      });
    },

    updateBackToTop(scrollY) {
      const buttons = document.querySelectorAll(this.config.backToTopSelector);

      if (!buttons.length) {
        return;
      }

      const currentScrollY = typeof scrollY === 'number'
        ? scrollY
        : window.scrollY || window.pageYOffset || 0;

      buttons.forEach((button) => {
        const offset = parseInt(button.getAttribute('data-rx-back-to-top-offset') || this.config.backToTopOffset, 10);
        button.classList.toggle(this.config.activeClass, currentScrollY > offset);
        button.setAttribute('aria-hidden', currentScrollY > offset ? 'false' : 'true');
      });
    },

    updateHeaderScroll(scrollY, direction) {
      const headers = document.querySelectorAll(this.config.headerSelector);

      if (!headers.length) {
        return;
      }

      headers.forEach((header) => {
        const offset = parseInt(header.getAttribute('data-rx-header-offset') || this.config.headerHideOffset, 10);
        const alwaysShowAtTop = header.getAttribute('data-rx-header-show-top') !== 'false';

        if (alwaysShowAtTop && scrollY <= offset) {
          header.classList.remove(this.config.headerHiddenClass);
          header.classList.add(this.config.headerVisibleClass);
          return;
        }

        if (direction === 'down' && scrollY > offset) {
          header.classList.add(this.config.headerHiddenClass);
          header.classList.remove(this.config.headerVisibleClass);
        } else {
          header.classList.remove(this.config.headerHiddenClass);
          header.classList.add(this.config.headerVisibleClass);
        }
      });
    },

    updateSectionSpy() {
      const sections = document.querySelectorAll(this.config.sectionSelector);

      if (!sections.length) {
        return;
      }

      let activeSection = null;
      const viewportMiddle = window.innerHeight * 0.38;

      sections.forEach((section) => {
        const rect = section.getBoundingClientRect();

        if (rect.top <= viewportMiddle && rect.bottom >= viewportMiddle) {
          activeSection = section;
        }
      });

      sections.forEach((section) => {
        section.classList.toggle(this.config.activeClass, section === activeSection);
      });

      if (activeSection && activeSection.id) {
        this.updateActiveNavLink(activeSection.id);
      }
    },

    updateActiveNavLink(sectionId) {
      const links = document.querySelectorAll(`[data-rx-section-link], a[href="#${CSS.escape(sectionId)}"]`);

      if (!links.length) {
        return;
      }

      const allSectionLinks = document.querySelectorAll('[data-rx-section-link], a[href^="#"]');

      allSectionLinks.forEach((link) => {
        const href = link.getAttribute('href') || '';
        const target = link.getAttribute('data-rx-section-link') || href.replace('#', '');
        const isActive = target === sectionId;

        link.classList.toggle(this.config.activeClass, isActive);

        if (isActive) {
          link.setAttribute('aria-current', 'true');
        } else {
          link.removeAttribute('aria-current');
        }
      });
    },

    easeOutCubic(value) {
      return 1 - Math.pow(1 - value, 3);
    },

    formatNumber(value, decimals, separator) {
      const fixed = Number(value).toFixed(decimals);

      if (!separator) {
        return fixed;
      }

      const parts = fixed.split('.');
      parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, separator);

      return parts.join('.');
    },

    clamp(value, min, max) {
      return Math.min(Math.max(value, min), max);
    },

    refresh() {
      this.destroy();
      this.init();
    },

    destroy() {
      this.state.observers.forEach((observer) => observer.disconnect());
      this.state.observers = [];
      this.state.ticking = false;
    }
  };

  window.RXTheme.ScrollAnimation = RXScrollAnimation;

  /**
   * Auto init.
   */
  const boot = () => {
    RXScrollAnimation.init();
  };

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', boot, {
      once: true
    });
  } else {
    boot();
  }

  /**
   * Support WordPress Customizer partial refresh / AJAX content.
   */
  document.addEventListener('rxThemeContentUpdated', () => {
    RXScrollAnimation.refresh();
  });

  document.addEventListener('wp-customize-preview-init', () => {
    RXScrollAnimation.refresh();
  });
})();

Helper CSS needed for this JS

Add this in your main CSS or animation CSS file, for example:

assets/static-css/components/_scroll-animation.css

.rx-scroll-animation-ready .rx-is-hidden {
  opacity: 0;
  transform: translate3d(0, 28px, 0);
  transition-property: opacity, transform;
  transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  will-change: opacity, transform;
}

.rx-scroll-animation-ready .rx-is-visible {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

[data-rx-animation-active="fade-up"] {
  transform: translate3d(0, 32px, 0);
}

[data-rx-animation-active="fade-down"] {
  transform: translate3d(0, -32px, 0);
}

[data-rx-animation-active="fade-left"] {
  transform: translate3d(32px, 0, 0);
}

[data-rx-animation-active="fade-right"] {
  transform: translate3d(-32px, 0, 0);
}

.rx-is-visible[data-rx-animation-active="fade-up"],
.rx-is-visible[data-rx-animation-active="fade-down"],
.rx-is-visible[data-rx-animation-active="fade-left"],
.rx-is-visible[data-rx-animation-active="fade-right"] {
  transform: translate3d(0, 0, 0);
}

[data-rx-progress] {
  position: relative;
  overflow: hidden;
}

[data-rx-progress]::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: var(--rx-progress-value, 0%);
  transition: width 0.3s ease;
}

[data-rx-scroll-progress] {
  --rx-scroll-progress: 0%;
}

[data-rx-scroll-progress]::before {
  width: var(--rx-scroll-progress);
}

[data-rx-back-to-top] {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(12px);
  transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s ease;
}

[data-rx-back-to-top].rx-is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0);
}

[data-rx-scroll-header] {
  transition: transform 0.25s ease;
  will-change: transform;
}

[data-rx-scroll-header].rx-header-hidden {
  transform: translateY(-100%);
}

[data-rx-scroll-header].rx-header-visible {
  transform: translateY(0);
}

[data-rx-sticky].rx-is-sticky {
  position: sticky;
  top: var(--rx-sticky-top, 0);
  z-index: 50;
}

@media (prefers-reduced-motion: reduce) {
  .rx-scroll-animation-ready .rx-is-hidden,
  .rx-scroll-animation-ready .rx-is-visible,
  [data-rx-back-to-top],
  [data-rx-scroll-header] {
    transition: none !important;
    transform: none !important;
  }
}

Example HTML usage

<div class="rx-reveal" data-rx-animation="fade-up">
  This content reveals on scroll.
</div>

<div data-rx-stagger data-rx-stagger-delay="120">
  <div>First card</div>
  <div>Second card</div>
  <div>Third card</div>
</div>

<h2>
  <span data-rx-counter="2500" data-rx-counter-separator=",">0</span>+
</h2>

<div class="rx-progress" data-rx-progress="75"></div>

<div data-rx-parallax data-rx-parallax-speed="0.12">
  Parallax content
</div>

<header data-rx-scroll-header>
  Header content
</header>

<button data-rx-back-to-top>
  Back to top
</button>

<div data-rx-scroll-progress class="rx-scroll-progress"></div>

<section id="about" data-rx-section>
  About section
</section>

<a href="#about" data-rx-section-link="about">About</a>

Best enqueue position in RX Theme

Use this file with defer:

wp_enqueue_script(
    'rx-scroll-animation',
    get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-019-scroll-animation.js',
    array(),
    RX_THEME_VERSION,
    true
);

wp_script_add_data('rx-scroll-animation', 'defer', true);

This file is best as a frontend-only chunk, not admin-side.

Leave a Reply

Your email address will not be published. Required fields are marked *