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
- Paste the snippet just before
</head>. - Add
data-pulseto any element. It starts pulsing when it scrolls into view.
Options
Add any of these attributes to the same element.
data-scale | How big it swells. Default 1.06. |
data-duration | How long one swell and settle takes. Default 1600 ms. |
data-count | Stop after this many pulses. By default it keeps going. |
data-delay | Wait before it starts. Default 0 ms. |
data-hover | Pause, 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.
<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><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>