← Entrances

Rotate Out

The element lifts slightly, then tips away round one of its corners, as if pinned there, picking up speed and fading as it turns. Pick the corner, or give several and a looping demo takes each in turn.

Card removedVisa ending 4242

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-rotate-out to any element.
  3. Choose a corner with data-corner if it shouldn’t turn round its bottom left.

Options

Add any of these attributes to the same element.

data-cornerThe corner it turns round: bottom-left, bottom-right, top-left or top-right. Bottom corners tip it down, top corners tip it up. Give several, like bottom-left bottom-right, and each loop uses the next. Default bottom-left.
data-angleHow far it turns, in degrees. Negative turns the other way. Default 90.
data-durationHow long it takes to leave. Default 750 ms.
data-delayHow long it stays in view before it leaves. Default 1200 ms.
data-loopFade back in, hold and rotate out again.
data-holdHow long it stays between exits, when looping. Default 1400 ms.

It plays once it has been in view for data-delay, and ends hidden (visibility: hidden) but still taking up its space. To play it when you choose, such as when something is dismissed, leave the attribute off and call animacious.rotateOut(el). The script sets transform-origin on the element while it turns.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Rotate Out: the element tips away round one of its corners, as if
    // pinned there, picking up speed and fading as it turns.
    // Usage: <div data-rotate-out>…</div>
    // It plays once the element has been in view for data-delay ms, and ends
    // hidden (visibility: hidden) but still taking up its space.
    // Optional: data-corner (the corner it turns round: "bottom-left",
    // default, "bottom-right", "top-left" or "top-right"; give several, like
    // "bottom-left bottom-right", and each loop uses the next), data-angle
    // (how far it turns, in degrees, default 90; negative turns the other
    // way), data-duration (ms, default 750), data-delay (ms in view before it
    // leaves, default 1200), data-loop (fade back in, hold and rotate out
    // again), data-hold (ms to stay between exits when looping, default 1400).
    // Script use: animacious.rotateOut(el, done) plays it on any element now,
    // using its data-* options, and calls done afterwards.
    var DEFAULTS = { corner: 'bottom-left', angle: 90, duration: 750, delay: 1200, hold: 1400 };

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

    // "bottom-left" (or "left-bottom") -> the CSS origin and which way it turns.
    function corner(name) {
      var top = /top/.test(name);
      var right = /right/.test(name);
      return {
        origin: (right ? 'right' : 'left') + ' ' + (top ? 'top' : 'bottom'),
        // Bottom corners tip down and away; top corners tip up and away.
        sign: (top ? -1 : 1) * (right ? -1 : 1)
      };
    }

    function leave(el, name, done) {
      var d = el.dataset;
      var t = +d.duration || DEFAULTS.duration;
      var a = (d.angle === undefined ? DEFAULTS.angle : +d.angle || 0) * corner(name).sign;
      if (reduced()) {
        anime.utils.set(el, { opacity: 0 });
        if (done) done();
        return;
      }
      el.style.transformOrigin = corner(name).origin;
      anime.utils.set(el, { rotate: 0 });
      anime.createTimeline({
        onComplete: function () {
          if (done) done();
        }
      })
        // A small lift the other way first, as it comes unstuck.
        .add(el, { rotate: a < 0 ? 4 : -4, duration: t * 0.18, ease: 'out(2)' })
        .add(el, {
          rotate: { to: a, ease: 'in(3)' },
          opacity: { to: 0, delay: t * 0.35, duration: t * 0.47, ease: 'in(2)' },
          duration: t * 0.82
        });
    }

    function cycle(el) {
      var d = el.dataset;
      var s = el._rto;
      var corners = (d.corner || DEFAULTS.corner).split(/[\s,]+/);
      leave(el, corners[s.n++ % corners.length], function () {
        if (d.loop === undefined || d.loop === 'false') {
          el.style.visibility = 'hidden';
          return;
        }
        // Come back quietly in place, then go again once it's on screen.
        setTimeout(function () {
          anime.utils.set(el, { rotate: 0, opacity: 0 });
          el.style.transformOrigin = '';
          anime.animate(el, {
            opacity: 1,
            duration: 400,
            ease: 'out(2)',
            onComplete: function () {
              setTimeout(function () {
                if (s.visible) cycle(el);
                else s.waiting = true;
              }, +d.hold || DEFAULTS.hold);
            }
          });
        }, 400);
      });
    }

    window.animacious = window.animacious || {};
    window.animacious.rotateOut = function (el, done) {
      leave(el, (el.dataset.corner || DEFAULTS.corner).split(/[\s,]+/)[0], function () {
        el.style.visibility = 'hidden';
        if (done) done(el);
      });
    };

    function init() {
      var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) {
          var s = e.target._rto;
          s.visible = e.isIntersecting;
          if (!e.isIntersecting) return;
          if (!s.started) {
            s.started = true;
            var wait = e.target.dataset.delay;
            setTimeout(function () { cycle(e.target); }, wait === undefined ? DEFAULTS.delay : +wait || 0);
          } else if (s.waiting) {
            s.waiting = false;
            cycle(e.target);
          }
        });
      }, { threshold: 0.2 });
      document.querySelectorAll('[data-rotate-out]').forEach(function (el) {
        if (el._rto) return;
        el._rto = { n: 0 };
        if (!reduced()) io.observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<div class="card" data-rotate-out>Card removed</div>

<!-- or tipping up round its top right corner: -->
<div class="card" data-rotate-out data-corner="top-right">Card removed</div>