← Entrances

Zoom In

The element grows from small to full size while it fades in. Start it bigger than full size and it zooms down instead, or grow it out of a corner.

Photo savedAdded to Holidays

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-zoom-in to any element.

Options

Add any of these attributes to the same element.

data-scaleThe size it starts at. Above 1 it zooms down from large. Default 0.3.
data-originThe point it grows from, as a CSS transform-origin like left top. Default the centre.
data-durationHow long the zoom takes. Default 700 ms.
data-delayWait before the first entrance. Default 0 ms.
data-loopHold, fade away and zoom in again.
data-holdHow long it stays before fading, when looping. Default 1400 ms.
1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Zoom In: the element grows from small to full size while it fades in.
    // Usage: <div data-zoom-in>…</div>
    // Optional: data-scale (the size it starts at, default 0.3; above 1 it
    // zooms down from large instead), data-origin (the point it grows from,
    // any CSS transform-origin like "left top", default the centre),
    // data-duration (ms, default 700), data-delay (ms before the first
    // entrance), data-loop (hold, fade away and zoom in again), data-hold (ms
    // to stay when looping, default 1400).
    var DEFAULTS = { scale: 0.3, duration: 700, delay: 0, hold: 1400 };

    function reduced() {
      return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    }

    function enter(el, done) {
      var d = el.dataset;
      var t = +d.duration || DEFAULTS.duration;
      if (d.origin) el.style.transformOrigin = d.origin;
      anime.utils.set(el, { scale: d.scale === undefined ? DEFAULTS.scale : +d.scale, opacity: 0 });
      anime.animate(el, {
        scale: { to: 1, ease: 'out(3)' },
        opacity: { to: 1, duration: t * 0.6, ease: 'out(2)' },
        duration: t,
        onComplete: done
      });
    }

    function cycle(el) {
      var d = el.dataset;
      var s = el._zmi;
      enter(el, function () {
        if (d.loop === undefined || d.loop === 'false') return;
        setTimeout(function () {
          anime.animate(el, {
            opacity: 0,
            duration: 400,
            ease: 'out(2)',
            onComplete: function () {
              // Wait until it's back on screen before the next entrance.
              setTimeout(function () {
                if (s.visible) cycle(el);
                else s.waiting = true;
              }, 500);
            }
          });
        }, +d.hold || DEFAULTS.hold);
      });
    }

    function init() {
      var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) {
          var s = e.target._zmi;
          s.visible = e.isIntersecting;
          if (!e.isIntersecting) return;
          if (!s.started) {
            s.started = true;
            setTimeout(function () { cycle(e.target); }, +e.target.dataset.delay || DEFAULTS.delay);
          } else if (s.waiting) {
            s.waiting = false;
            cycle(e.target);
          }
        });
      }, { threshold: 0.2 });
      document.querySelectorAll('[data-zoom-in]').forEach(function (el) {
        if (el._zmi) return;
        el._zmi = { n: 0 };
        if (reduced()) return;
        anime.utils.set(el, { opacity: 0 });
        io.observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<div class="card" data-zoom-in>Photo saved</div>

<!-- or zooming down from large: -->
<div class="card" data-zoom-in data-scale="1.6">Photo saved</div>