← Scroll

Fade-up on Scroll

Elements fade in and lift into place as they scroll into view. Anything that arrives together is staggered, so lists and grids cascade in order.

Scroll inside ↓

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-fade-up-on-scroll to each element that should animate in: cards, list items, sections.

Options

Add any of these attributes to the same element.

data-repeatAnimate again every time it comes back into view.
data-distanceHow far it rises, in px. Default 32.
data-durationHow long it takes. Default 900 ms.
data-staggerGap between items that arrive together. Default 90 ms.
1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Fade-up on scroll: elements fade in and lift into place as they scroll into
    // view. Elements that arrive together are staggered, so lists and grids cascade.
    // Usage: <div data-fade-up-on-scroll>…</div>   (each element you want to animate)
    // Optional: data-distance (px, default 32), data-duration (ms), data-stagger
    // (ms between items arriving together), data-repeat (animate again every time
    // it re-enters the view).
    var DEFAULTS = { distance: 32, duration: 900, stagger: 90 };

    function hide(el) {
      var dist = +el.dataset.distance || DEFAULTS.distance;
      anime.utils.set(el, { opacity: 0, y: dist, scale: 0.98 });
    }

    function init() {
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      var els = document.querySelectorAll('[data-fade-up-on-scroll]');
      var io = new IntersectionObserver(function (entries) {
        var batch = [];
        entries.forEach(function (e) {
          var el = e.target;
          var repeat = el.dataset.repeat !== undefined && el.dataset.repeat !== 'false';
          if (e.isIntersecting) {
            if (el._fuShown) return;
            el._fuShown = true;
            batch.push(el);
            if (!repeat) io.unobserve(el);
          } else if (repeat && el._fuShown && e.boundingClientRect.top > 0) {
            // Left through the bottom: hide again so it replays on the way back.
            el._fuShown = false;
            hide(el);
          }
        });
        // Sort by position so a cascade always runs top-left to bottom-right.
        batch.sort(function (a, b) {
          var ra = a.getBoundingClientRect(), rb = b.getBoundingClientRect();
          return ra.top - rb.top || ra.left - rb.left;
        });
        batch.forEach(function (el, i) {
          anime.animate(el, {
            opacity: 1,
            y: 0,
            scale: 1,
            duration: +el.dataset.duration || DEFAULTS.duration,
            delay: i * (+el.dataset.stagger || DEFAULTS.stagger),
            ease: 'out(3)'
          });
        });
      }, { threshold: 0.15, rootMargin: '0px 0px -8% 0px' });
      els.forEach(function (el) {
        if (el._fuInit) return;
        el._fuInit = true;
        hide(el);
        io.observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<div class="card" data-fade-up-on-scroll>…</div>
<div class="card" data-fade-up-on-scroll>…</div>
<div class="card" data-fade-up-on-scroll>…</div>