← Hover & cursor

Magnetic Button

As the pointer comes near, the button is pulled towards it and its label leans a little further, then it springs home when the pointer leaves.

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-magnetic-button to a button or link.

Options

Add any of these attributes to the same element.

data-strengthHow strongly it follows, 0 to 1. Default 0.35.
data-radiusHow far around the button the pull starts, in px. Default 70.

Turned off on touch screens, where there is no pointer to follow.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Magnetic Button: as the pointer comes near, the button is pulled towards it
    // and its label leans a little further, then it springs home when you leave.
    // Usage: <a class="btn" href="/signup" data-magnetic-button>Get started</a>
    // Optional: data-strength (0–1, default 0.35), data-radius (px around the
    // button where the pull starts, default 70).
    var DEFAULTS = { strength: 0.35, radius: 70 };
    var items = [];

    function setup(el) {
      // Wrap the contents so the label can move a bit more than the button.
      var inner = document.createElement('span');
      inner.style.display = 'inline-block';
      inner.style.pointerEvents = 'none';
      while (el.firstChild) inner.appendChild(el.firstChild);
      el.appendChild(inner);
      items.push({ el: el, inner: inner, active: false });
    }

    function onMove(e) {
      items.forEach(function (it) {
        var d = it.el.dataset;
        var r = it.el.getBoundingClientRect();
        var radius = +d.radius || DEFAULTS.radius;
        var cx = r.left + r.width / 2;
        var cy = r.top + r.height / 2;
        var dx = e.clientX - cx;
        var dy = e.clientY - cy;
        var near = Math.abs(dx) < r.width / 2 + radius && Math.abs(dy) < r.height / 2 + radius;
        if (near) {
          var s = +d.strength || DEFAULTS.strength;
          it.active = true;
          anime.animate(it.el, { x: dx * s, y: dy * s, duration: 400, ease: 'out(3)' });
          anime.animate(it.inner, { x: dx * s * 0.5, y: dy * s * 0.5, duration: 400, ease: 'out(3)' });
        } else if (it.active) {
          it.active = false;
          anime.animate([it.el, it.inner], { x: 0, y: 0, duration: 900, ease: 'outElastic(1, .4)' });
        }
      });
    }

    function init() {
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      if (window.matchMedia('(hover: none)').matches) return; // touch screens: nothing to follow
      document.querySelectorAll('[data-magnetic-button]').forEach(function (el) {
        if (el._mb) return;
        el._mb = true;
        setup(el);
      });
      if (items.length) document.addEventListener('pointermove', onMove, { passive: true });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<a class="button" href="/signup" data-magnetic-button>Get started</a>