Pulsing Call-to-action
Soft rings ripple out from the button one after another, fading as they grow, while the button swells gently with each pulse. It draws the eye without shouting, and pauses while someone hovers it.
How to use it
- Paste the snippet just before
</head>. - Add
data-pulsing-call-to-actionto the one button you most want people to press.
Options
Add any of these attributes to the same element.
data-color | Colour of the rings. Default: the button’s background colour. |
data-rings | Rings per pulse, 1 to 4. Default 2. |
data-size | How far the rings travel, in pixels. Default 24. |
data-interval | Time between pulses. Default 2200 ms. |
It only pulses while on screen, and keeps still with reduced motion. Use it on one button per page at most.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Pulsing Call-to-action: soft rings ripple out from the button, one after
// another, fading as they grow, while the button itself swells gently with
// each pulse, to draw the eye. Hover or focus it and the pulsing pauses, so
// it stays calm while someone is about to click.
// Usage: <a class="button" href="/signup" data-pulsing-call-to-action>Start free trial</a>
// Optional: data-color (ring colour; default the button's background
// colour), data-rings (rings per pulse, default 2), data-size (how far the
// rings travel in px, default 24), data-interval (ms between pulses,
// default 2200).
// It only pulses while on screen, and keeps still with reduced motion.
var DEFAULTS = { rings: 2, size: 24, interval: 2200 };
var css =
'.pcta{isolation:isolate}' +
'.pcta-ring{position:absolute;inset:0;z-index:-1;border-radius:inherit;border:2px solid var(--pcta-color);' +
'background:color-mix(in srgb,var(--pcta-color) 14%,transparent);opacity:0;pointer-events:none}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function setup(el) {
var d = el.dataset;
var count = Math.max(1, Math.min(4, +d.rings || DEFAULTS.rings));
var size = +d.size || DEFAULTS.size;
var every = +d.interval || DEFAULTS.interval;
var cs = getComputedStyle(el);
var color = d.color || (cs.backgroundColor !== 'rgba(0, 0, 0, 0)' ? cs.backgroundColor : '#6a0dad');
el.classList.add('pcta');
if (cs.position === 'static') el.style.position = 'relative';
el.style.setProperty('--pcta-color', color);
var rings = [];
for (var i = 0; i < count; i++) {
var r = document.createElement('span');
r.className = 'pcta-ring';
r.setAttribute('aria-hidden', 'true');
el.insertBefore(r, el.firstChild);
rings.push(r);
}
function pulse() {
// The rings grow by moving their edges outwards, so the gap is the same
// all round whatever the button's shape.
anime.animate(rings, {
top: [0, -size], right: [0, -size], bottom: [0, -size], left: [0, -size],
opacity: [{ to: 0.85, duration: 120, ease: 'out(2)' }, { to: 0, duration: 1480, ease: 'in(1.6)' }],
borderWidth: [2.5, 1],
duration: 1600,
delay: anime.stagger(340),
ease: 'out(2.4)'
});
anime.animate(el, {
scale: [{ to: 1.045, duration: 260, ease: 'out(2)' }, { to: 1, duration: 520, ease: 'inOut(2)' }]
});
}
var timer = null, seen = false;
function calm() { return el.matches(':hover') || document.activeElement === el; }
var tick = function () {
if (!calm()) pulse();
timer = setTimeout(tick, every);
};
new IntersectionObserver(function (entries) {
seen = entries[0].isIntersecting;
clearTimeout(timer);
if (seen) timer = setTimeout(tick, 400);
}).observe(el);
}
function init() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
document.querySelectorAll('[data-pulsing-call-to-action]').forEach(function (el) {
if (el._pcta) return;
el._pcta = true;
setup(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><a class="button" href="/signup" data-pulsing-call-to-action>Start free trial</a>
<!-- three rings, a little calmer: -->
<a class="button" href="/signup" data-pulsing-call-to-action data-rings="3" data-interval="3000">Start free trial</a>