← Attention

Pulse

The element swells gently and settles back, over and over, like a slow heartbeat. It pauses while it’s off screen, and can stop once someone hovers over it.

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-pulse to any element. It starts pulsing when it scrolls into view.

Options

Add any of these attributes to the same element.

data-scaleHow big it swells. Default 1.06.
data-durationHow long one swell and settle takes. Default 1600 ms.
data-countStop after this many pulses. By default it keeps going.
data-delayWait before it starts. Default 0 ms.
data-hoverPause, back at normal size, while the pointer is over it or it has focus.

Call animacious.pulse(el, false) to stop it, for example once the visitor has clicked, and animacious.pulse(el) to start it again.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Pulse: the element swells gently and settles back, over and over, like
    // a slow heartbeat. It pauses while off screen.
    // Usage: <div data-pulse>…</div>
    // Optional: data-scale (how big it swells, default 1.06), data-duration (ms
    // for one swell and settle, default 1600), data-count (stop after this many
    // pulses; default keeps going), data-delay (ms before it starts),
    // data-hover (pause while hovered or focused, so it stops nagging once
    // someone is on it).
    // Script use: animacious.pulse(el, false) stops it, animacious.pulse(el)
    // starts it again.
    var DEFAULTS = { scale: 1.06, duration: 1600, delay: 0 };

    function on(v) { return v !== undefined && v !== 'false'; }
    function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }

    function build(el) {
      var d = el.dataset;
      var s = el._psw;
      var count = Math.round(+d.count || 0);
      s.anim = anime.animate(el, {
        scale: [1, +d.scale || DEFAULTS.scale],
        duration: (+d.duration || DEFAULTS.duration) / 2,
        ease: 'inOut(2)',
        alternate: true,
        // One pulse is a swell and a settle: two legs of the alternate loop.
        loop: count > 0 ? count * 2 - 1 : true,
        autoplay: false,
        onComplete: function () { s.done = true; }
      });
    }

    function update(el) {
      var s = el._psw;
      var run = s.started && s.visible && !s.stopped && !s.held && !s.done;
      if (run) {
        if (!s.anim) build(el);
        s.anim.play();
      } else if (s.anim) {
        s.anim.pause();
        if (s.stopped || s.held) {
          // Ease back to normal size rather than freezing mid-swell.
          anime.animate(el, { scale: 1, duration: 300, ease: 'out(2)' });
          s.anim = null;
        }
      }
    }

    function init() {
      var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) {
          var el = e.target;
          var s = el._psw;
          s.visible = e.isIntersecting;
          if (e.isIntersecting && !s.waiting && !s.started) {
            s.waiting = true;
            setTimeout(function () { s.started = true; update(el); }, +el.dataset.delay || DEFAULTS.delay);
          } else update(el);
        });
      }, { threshold: 0.2 });
      document.querySelectorAll('[data-pulse]').forEach(function (el) {
        if (el._psw) return;
        el._psw = {};
        if (on(el.dataset.hover)) {
          var hold = function (v) { return function () { el._psw.held = v; update(el); }; };
          el.addEventListener('pointerenter', hold(true));
          el.addEventListener('pointerleave', hold(false));
          el.addEventListener('focusin', hold(true));
          el.addEventListener('focusout', hold(false));
        }
        if (!reduced()) io.observe(el);
      });
    }

    window.animacious = window.animacious || {};
    window.animacious.pulse = function (el, run) {
      var s = el._psw;
      if (!s || reduced()) return;
      s.stopped = run === false;
      if (!s.stopped) { s.started = true; s.done = false; }
      update(el);
    };
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<button class="cta" data-pulse>Start free trial</button>

<!-- or three bigger pulses, then stop: -->
<button class="cta" data-pulse data-scale="1.1" data-count="3">Start free trial</button>