← Buttons

Press Squash

Press the button and it squashes down like soft rubber, wider and flatter, for as long as you hold it. Let go and it springs back, overshooting into a little stretch before it settles. Enter and Space work too.

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-press-squash to buttons or any clickable element.

Options

Add any of these attributes to the same element.

data-amountHow much it squashes, 0 to 1. Default 0.14.
data-autoplayIt presses itself every couple of seconds while in view. Handy for demos.

From code, animacious.squash(element) plays one press.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Press Squash: press a button and it squashes down like soft rubber, wider
    // and flatter, for as long as you hold it. Let go and it springs back,
    // overshooting into a stretch before it settles. Enter and Space work too.
    // Usage: <button data-press-squash>Press me</button>
    // Optional: data-amount (how much it squashes, 0–1, default 0.14),
    // data-autoplay (it presses itself every couple of seconds while in view;
    // handy for demos).
    // Script use: animacious.squash(element) plays one press.
    var DEFAULTS = { amount: 0.14, every: 2200 };

    function amount(el) {
      var a = parseFloat(el.dataset.amount);
      return a >= 0 && a <= 1 ? a : DEFAULTS.amount;
    }

    function down(el) {
      if (el._psqDown) return;
      el._psqDown = true;
      var a = amount(el);
      anime.animate(el, {
        scaleX: 1 + a * 0.75,
        scaleY: 1 - a,
        y: el.offsetHeight * a * 0.25,
        duration: 160,
        ease: 'out(3)'
      });
    }

    function up(el) {
      if (!el._psqDown) return;
      el._psqDown = false;
      // The elastic ease carries it past rest into a stretch, then back.
      anime.animate(el, {
        scaleX: 1,
        scaleY: 1,
        y: 0,
        duration: 900,
        ease: 'outElastic(1.2, .35)'
      });
    }

    function squash(el) {
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      down(el);
      setTimeout(function () { up(el); }, 220);
    }

    function setup(el) {
      el.style.transformOrigin = '50% 100%';
      el.addEventListener('pointerdown', function (e) { if (e.button === 0) down(el); });
      ['pointerup', 'pointerleave', 'pointercancel'].forEach(function (t) {
        el.addEventListener(t, function () { up(el); });
      });
      el.addEventListener('keydown', function (e) { if ((e.key === ' ' || e.key === 'Enter') && !e.repeat) down(el); });
      el.addEventListener('keyup', function (e) { if (e.key === ' ' || e.key === 'Enter') up(el); });
      el.addEventListener('blur', function () { up(el); });

      if (el.dataset.autoplay !== undefined && el.dataset.autoplay !== 'false') {
        var timer = null;
        var press = function () {
          if (!el.matches(':hover')) squash(el);
          timer = setTimeout(press, DEFAULTS.every);
        };
        new IntersectionObserver(function (entries) {
          clearTimeout(timer);
          if (entries[0].isIntersecting) timer = setTimeout(press, 600);
        }).observe(el);
      }
    }

    function init() {
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      document.querySelectorAll('[data-press-squash]').forEach(function (el) {
        if (el._psq) return;
        el._psq = true;
        setup(el);
      });
    }

    window.animacious = window.animacious || {};
    window.animacious.squash = squash;

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<button data-press-squash>Add to cart</button>