Nudge
The element darts sideways and eases back a few times, each nudge a little smaller, as if to say “tap here”. Great on a Next button that people keep missing.
Welcome aboardLet’s set up your profile.
How to use it
- Paste the snippet just before
</head>. - Add
data-nudgeto any element. It nudges once when it scrolls into view. - Add
data-loopto nudge again every so often.
Options
Add any of these attributes to the same element.
data-direction | Which way it nudges: right, left, up or down. Default right. |
data-distance | How far each nudge goes, in px. Default 8. |
data-times | How many nudges. Default 3. |
data-duration | How long all the nudges take. Default 900 ms. |
data-delay | Wait before the first nudge. Default 0 ms. |
data-loop | Nudge again every so often while it’s on screen. |
data-every | Rest between rounds, when looping. Default 1800 ms. |
data-hover | Also play it when the pointer moves over the element or it’s tapped. |
Call animacious.nudge(el) to nudge it from your own code, for example when someone has sat on a step for a while.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Nudge: the element nudges sideways a few times, quick out and soft back,
// as if to say "tap here".
// Usage: <button data-nudge>Next</button>
// Optional: data-direction ("right", default, "left", "up" or "down"),
// data-distance (px of each nudge, default 8), data-times (how many nudges,
// default 3), data-duration (ms for all the nudges, default 900),
// data-delay (ms before the first nudge), data-loop (nudge again every so
// often while in view), data-every (ms of rest between loops, default 1800),
// data-hover (also nudge on hover or tap).
// Script use: animacious.nudge(el) nudges it from code.
var DEFAULTS = { direction: 'right', distance: 8, times: 3, duration: 900, delay: 0, every: 1800 };
function on(v) { return v !== undefined && v !== 'false'; }
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function play(el, done) {
var s = el._ndg;
if (s.busy) { if (done) s.after = done; return; }
if (reduced()) { if (done) done(); return; }
s.busy = true;
var d = el.dataset;
function finish() {
s.busy = false;
var after = s.after;
s.after = null;
if (done) done();
if (after) after();
}
var dist = d.distance === undefined ? DEFAULTS.distance : +d.distance;
var times = Math.max(1, +d.times || DEFAULTS.times);
var dir = d.direction || DEFAULTS.direction;
var prop = dir === 'up' || dir === 'down' ? 'y' : 'x';
var sign = dir === 'left' || dir === 'up' ? -1 : 1;
var each = (+d.duration || DEFAULTS.duration) / times;
var tl = anime.createTimeline({ onComplete: finish });
for (var i = 0; i < times; i++) {
// Each nudge darts out fast and eases back, a touch smaller than the last.
var out = {}, back = {};
out[prop] = sign * dist * (1 - i * 0.12);
back[prop] = 0;
out.duration = each * 0.35;
out.ease = 'out(3)';
back.duration = each * 0.65;
back.ease = 'inOut(2)';
tl.add(el, out).add(el, back);
}
}
function cycle(el) {
var s = el._ndg;
play(el, function () {
if (!on(el.dataset.loop)) return;
clearTimeout(s.timer);
s.timer = setTimeout(function () {
if (s.visible) cycle(el);
else s.waiting = true;
}, +el.dataset.every || DEFAULTS.every);
});
}
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
var s = e.target._ndg;
s.visible = e.isIntersecting;
if (!e.isIntersecting) return;
if (!s.started) {
s.started = true;
setTimeout(function () { cycle(e.target); }, +e.target.dataset.delay || DEFAULTS.delay);
} else if (s.waiting) {
s.waiting = false;
cycle(e.target);
}
});
}, { threshold: 0.2 });
document.querySelectorAll('[data-nudge]').forEach(function (el) {
if (el._ndg) return;
el._ndg = {};
if (on(el.dataset.hover)) {
el.addEventListener('pointerenter', function () { play(el); });
el.addEventListener('pointerdown', function () { play(el); });
}
if (!reduced()) io.observe(el);
});
}
window.animacious = window.animacious || {};
window.animacious.nudge = function (el) { if (el._ndg) play(el); };
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><button class="next" data-nudge data-loop>Next</button>
<!-- or nudging upwards, twice: -->
<button class="to-top" data-nudge data-direction="up" data-times="2">Back to top</button>