Waving Hand
A hand rocks back and forth from the wrist in a friendly wave, with little motion lines at the end of each swing, then settles with a wobble. Use the drawn cartoon hand, or make your own emoji or image wave.
Hello there ๐
How to use it
- Paste the snippet just before
</head>. - Add
data-waving-handto an empty<span>for the drawn hand. Size it withfont-size. - Or put your own hand inside it, e.g.
<span data-waving-hand>๐</span>, and that waves instead.
Options
Add any of these attributes to the same element.
data-loop | Wave again every few seconds. |
data-interval | Pause between waves, when looping. Default 2400 ms. |
data-angle | How far it swings, in degrees. Default 18. |
data-hover | Also wave when hovered or tapped. |
data-color | Skin colour of the drawn hand. Default #fbbf24. |
It waves when scrolled into view. Call animacious.wave(el) to wave it from code, e.g. when someone signs in. With reduced motion it stays still.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Waving Hand: a hand rocks back and forth from the wrist in a friendly
// wave, then rests. Leave the element empty for a drawn cartoon hand, or
// put your own in it (an emoji, an image, an SVG) and that waves instead.
// Usage: <span data-waving-hand></span> or <span data-waving-hand>๐</span>
// Size it with font-size.
// Optional: data-loop (wave again every few seconds), data-interval (ms
// between waves when looping, default 2400), data-angle (degrees of the
// swing, default 18), data-hover (also wave when hovered or tapped),
// data-color (skin colour of the drawn hand, default #fbbf24).
// Script use: animacious.wave(el) waves it from code.
var DEFAULTS = { interval: 2400, angle: 18, color: '#fbbf24' };
var NS = 'http://www.w3.org/2000/svg';
var css =
'.wvh{display:inline-block;line-height:1;transform-origin:70% 85%}' +
'.wvh-drawn{transform-origin:48% 92%}' +
'.wvh svg{display:block;width:1em;height:1em;overflow:visible}' +
'.wvh-line{opacity:0}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
// A cartoon hand: thick round strokes for the fingers, drawn twice, once
// wider in a darker tone for the outline.
function hand(color) {
var parts =
'<path d="M23 34 L20 13"/><path d="M30.5 31 L30.5 8.5"/><path d="M38 32 L39.5 11"/><path d="M44 36 L48 19"/><path d="M21 44 L10.5 33"/>' +
'<rect x="18" y="28" width="29" height="25" rx="12"/>';
return '<g stroke="#b45309" stroke-width="11.5" stroke-linecap="round" stroke-linejoin="round" fill="#b45309">' + parts + '</g>' +
'<g stroke="' + color + '" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round" fill="' + color + '">' + parts + '</g>' +
'<path d="M26 45 Q32 49 38 45" fill="none" stroke="#b45309" stroke-width="1.6" stroke-linecap="round" opacity=".45"/>' +
'<rect x="20" y="51" width="25" height="10" rx="3" fill="#8b5cf6"/>' +
// Motion lines either side, shown at the end of each swing.
'<g fill="none" stroke="#a1a1aa" stroke-width="2.4" stroke-linecap="round">' +
'<path class="wvh-line wvh-r" d="M55 12 Q59 17 58 23 M60 6 Q66 14 64 24"/>' +
'<path class="wvh-line wvh-l" d="M8 18 Q4 23 5 29 M3 11 Q-3 19 -1 29"/></g>';
}
function wave(el) {
var s = el._wvh;
if (s.busy || reduced()) return;
s.busy = true;
var a = s.angle;
var tl = anime.createTimeline({ onComplete: function () { s.busy = false; } });
tl.add(el, {
rotate: [
{ to: a, duration: 180, ease: 'out(2)' },
{ to: -a * 0.45, duration: 200, ease: 'inOut(2)' },
{ to: a, duration: 200, ease: 'inOut(2)' },
{ to: -a * 0.3, duration: 200, ease: 'inOut(2)' },
{ to: a * 0.6, duration: 200, ease: 'inOut(2)' },
{ to: 0, duration: 420, ease: 'outElastic(1, .6)' }
]
});
if (s.right) {
tl.add(s.right, { opacity: [{ to: 1, duration: 90 }, { to: 0, duration: 220 }] }, 60)
.add(s.left, { opacity: [{ to: 1, duration: 90 }, { to: 0, duration: 220 }] }, 260)
.add(s.right, { opacity: [{ to: 1, duration: 90 }, { to: 0, duration: 220 }] }, 460);
}
}
function setup(el) {
var d = el.dataset;
el.classList.add('wvh');
var s = { angle: +d.angle || DEFAULTS.angle };
el._wvh = s;
if (!el.innerHTML.trim()) {
el.classList.add('wvh-drawn');
var svg = document.createElementNS(NS, 'svg');
svg.setAttribute('viewBox', '0 0 64 64');
svg.setAttribute('aria-hidden', 'true');
svg.innerHTML = hand(d.color || DEFAULTS.color);
el.appendChild(svg);
s.right = svg.querySelector('.wvh-r');
s.left = svg.querySelector('.wvh-l');
if (!el.getAttribute('aria-label') && !el.closest('[aria-hidden="true"]')) {
el.setAttribute('role', 'img');
el.setAttribute('aria-label', 'A waving hand');
}
}
if (d.hover !== undefined && d.hover !== 'false') {
el.addEventListener('pointerenter', function () { wave(el); });
el.addEventListener('pointerdown', function () { wave(el); });
}
}
function play(el) {
var d = el.dataset;
var loop = d.loop !== undefined && d.loop !== 'false';
var every = +d.interval || DEFAULTS.interval;
var s = el._wvh;
var tick = function () {
if (s.visible) wave(el);
if (loop) s.timer = setTimeout(tick, every + 1400);
};
s.timer = setTimeout(tick, 250);
}
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
var s = e.target._wvh;
s.visible = e.isIntersecting;
if (e.isIntersecting && !s.started) {
s.started = true;
play(e.target);
}
});
}, { threshold: 0.2 });
document.querySelectorAll('[data-waving-hand]').forEach(function (el) {
if (el._wvh) return;
setup(el);
if (!reduced()) io.observe(el);
});
}
window.animacious = window.animacious || {};
window.animacious.wave = function (el) { if (el._wvh) wave(el); };
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><h1>Hello there <span data-waving-hand data-hover>๐</span></h1>
<!-- the drawn hand, waving every few seconds: -->
<span data-waving-hand data-loop style="font-size: 96px"></span>