← Entrances

Flip In

The element flips into view from edge-on, swinging past flat and back a couple of times before it settles. Tip it over its horizontal axis or turn it round its vertical one.

Card addedVisa ending 4242

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-flip-in to any element.
  3. Add data-axis="y" to turn it sideways instead of tipping it forwards.

Options

Add any of these attributes to the same element.

data-axisx tips it over its horizontal axis, y turns it round its vertical one. Give both, x y, and each loop uses the next. Default x.
data-perspectiveHow close the viewer is, in px. Smaller is more dramatic. Default 800.
data-durationHow long the flip takes. Default 1000 ms.
data-delayWait before the first entrance. Default 0 ms.
data-loopHold, fade away and flip 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 () {
    // Flip In: the element flips into view round its horizontal or vertical
    // axis, starting edge-on and swinging past flat a couple of times before
    // it settles.
    // Usage: <div data-flip-in>…</div>
    // Optional: data-axis ("x", default, tips over the horizontal axis; "y"
    // turns round the vertical one; give both, "x y", and each loop uses the
    // next), data-perspective (px, smaller is more dramatic, default 800),
    // data-duration (ms, default 1000), data-delay (ms before the first
    // entrance), data-loop (hold, fade away and flip in again), data-hold (ms
    // to stay when looping, default 1400).
    var DEFAULTS = { axis: 'x', perspective: 800, duration: 1000, delay: 0, hold: 1400 };

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

    function enter(el, axis, done) {
      var d = el.dataset;
      var t = +d.duration || DEFAULTS.duration;
      var prop = axis === 'y' ? 'rotateY' : 'rotateX';
      var other = axis === 'y' ? 'rotateX' : 'rotateY';
      var start = { opacity: 0, perspective: (+d.perspective || DEFAULTS.perspective) + 'px' };
      start[prop] = 90;
      start[other] = 0;
      anime.utils.set(el, start);
      var props = {
        opacity: { to: 1, duration: t * 0.6, ease: 'linear' },
        onComplete: done
      };
      // Edge-on, then past flat, back, and a smaller swing to rest.
      props[prop] = [
        { to: -20, duration: t * 0.4, ease: 'in(2)' },
        { to: 10, duration: t * 0.2, ease: 'inOut(2)' },
        { to: -5, duration: t * 0.2, ease: 'inOut(2)' },
        { to: 0, duration: t * 0.2, ease: 'out(2)' }
      ];
      anime.animate(el, props);
    }

    function cycle(el) {
      var d = el.dataset;
      var s = el._fli;
      var axes = (d.axis || DEFAULTS.axis).split(/[\s,]+/);
      enter(el, axes[s.n++ % axes.length], 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._fli;
          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-flip-in]').forEach(function (el) {
        if (el._fli) return;
        el._fli = { 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-flip-in>Card added</div>

<!-- or turning round the vertical axis: -->
<div class="card" data-flip-in data-axis="y">Card added</div>