Animated Emoji
Drawn emoji faces with little loops of their own. One smiles, blinks and glances about, one rocks with laughter till tears of joy fly off, one winks with a sparkle and one sobs as tears roll down its cheeks.
How to use it
- Paste the snippet just before
</head>. - Add
data-animated-emojito an empty<span>, set to the face you want:blink,laugh,winkorcry. - Size it with
font-size: the face is 1em square, so it sits nicely in a line of text.
Options
Add any of these attributes to the same element.
data-color | Face colour. Default #fcd34d. |
Each face gets an aria-label such as “Winking face”. The loops pause while off screen, and stay still with reduced motion.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Animated Emoji: drawn emoji faces with little loops of their own. One
// blinks and glances about, one laughs till it cries, one winks with a
// sparkle and one sobs, tears rolling down its cheeks.
// Usage: <span data-animated-emoji="laugh"></span>
// Faces: blink, laugh, wink, cry. Size it with font-size (1em square).
// Optional: data-color (face colour, default #fcd34d).
// The loops pause while off screen, and stay still with reduced motion.
var LABELS = { blink: 'Smiling face', laugh: 'Laughing face', wink: 'Winking face', cry: 'Crying face' };
var DEFAULTS = { color: '#fcd34d' };
var NS = 'http://www.w3.org/2000/svg';
var INK = '#5b3a12';
var count = 0;
var css =
'.aem{display:inline-block;width:1em;height:1em;line-height:0;vertical-align:middle}' +
'.aem svg{display:block;width:100%;height:100%;overflow:visible}' +
'.aem svg [class]{transform-box:fill-box;transform-origin:center}' +
'.aem-off *{animation-play-state:paused!important}' +
// blink
'.aem-blink{animation:aem-blink 4s linear infinite}' +
'.aem-look{animation:aem-look 4s ease-in-out infinite}' +
// laugh
'.aem-shake{transform-origin:50% 80%!important;animation:aem-shake .5s ease-in-out infinite}' +
'.aem-jaw{transform-origin:50% 0!important;animation:aem-jaw .25s ease-in-out infinite alternate}' +
'.aem-joy{animation:aem-joy 1.4s ease-out infinite}' +
'.aem-joy+.aem-joy{animation-name:aem-joy2;animation-delay:-.7s}' +
// wink
'.aem-tilt{transform-origin:50% 50%!important;animation:aem-tilt 3.2s ease-in-out infinite}' +
'.aem-open{animation:aem-open 3.2s linear infinite}' +
'.aem-shut{animation:aem-shut 3.2s linear infinite}' +
'.aem-spark{animation:aem-spark 3.2s ease-out infinite}' +
// cry
'.aem-sob{animation:aem-sob 2.6s ease-in-out infinite}' +
'.aem-tear{animation:aem-tear 1.3s ease-in infinite}' +
'.aem-quiver{transform-origin:50% 0!important;animation:aem-quiver .35s ease-in-out infinite alternate}' +
'@keyframes aem-blink{0%,44%,49%,100%{transform:scaleY(1)}46.5%{transform:scaleY(.1)}}' +
'@keyframes aem-look{0%,15%,85%,100%{transform:translate(0,0)}25%,40%{transform:translate(-2.5px,-.5px)}55%,72%{transform:translate(2.5px,-.5px)}}' +
'@keyframes aem-shake{0%,100%{transform:translateY(0) rotate(-3deg)}50%{transform:translateY(-1.5px) rotate(3deg)}}' +
'@keyframes aem-jaw{from{transform:scaleY(.82)}to{transform:scaleY(1.05)}}' +
'@keyframes aem-joy{0%{transform:translate(0,0) scale(.4);opacity:0}15%{opacity:1}100%{transform:translate(-7px,8px) scale(1);opacity:0}}' +
'@keyframes aem-joy2{0%{transform:translate(0,0) scale(.4);opacity:0}15%{opacity:1}100%{transform:translate(7px,8px) scale(1);opacity:0}}' +
'@keyframes aem-tilt{0%,30%,85%,100%{transform:rotate(0)}42%,72%{transform:rotate(-8deg)}}' +
'@keyframes aem-open{0%,36%,78%,100%{transform:scaleY(1);opacity:1}40%,74%{transform:scaleY(.1);opacity:0}}' +
'@keyframes aem-shut{0%,36%,78%,100%{opacity:0}40%,74%{opacity:1}}' +
'@keyframes aem-spark{0%,38%{transform:scale(0) rotate(0);opacity:0}46%{transform:scale(1.2) rotate(45deg);opacity:1}62%{transform:scale(.9) rotate(90deg);opacity:1}72%,100%{transform:scale(0) rotate(120deg);opacity:0}}' +
'@keyframes aem-sob{0%,100%{transform:translateY(0)}10%,30%{transform:translateY(1px)}20%,40%{transform:translateY(-.5px)}}' +
'@keyframes aem-tear{0%{transform:translateY(-2px) scale(.5);opacity:0}15%{opacity:1;transform:translateY(0) scale(1)}100%{transform:translateY(17px) scale(1);opacity:0}}' +
'@keyframes aem-quiver{from{transform:scaleY(1) translateY(0)}to{transform:scaleY(.86) translateY(.6px)}}' +
'@media (prefers-reduced-motion:reduce){.aem svg *{animation:none!important}.aem-shut,.aem-spark,.aem-joy{opacity:0}}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function eye(x, cls) {
return '<ellipse class="' + cls + '" cx="' + x + '" cy="26" rx="3.4" ry="4.8" fill="' + INK + '"/>';
}
function drop(x, y, cls, extra) {
return '<path class="' + cls + '"' + (extra || '') + ' d="M' + x + ' ' + y + ' C' + (x - 3) + ' ' + (y + 4) + ' ' + (x - 3) + ' ' + (y + 7) + ' ' + x + ' ' + (y + 7) +
' C' + (x + 3) + ' ' + (y + 7) + ' ' + (x + 3) + ' ' + (y + 4) + ' ' + x + ' ' + y + ' Z" fill="#60a5fa" stroke="#3b82f6" stroke-width=".8"/>';
}
var CHEEKS = '<ellipse cx="16" cy="38" rx="4.5" ry="2.8" fill="#fb7185" opacity=".35"/><ellipse cx="48" cy="38" rx="4.5" ry="2.8" fill="#fb7185" opacity=".35"/>';
var FACES = {
blink: function () {
return '<g class="aem-look"><g class="aem-blink">' + eye(23, '') + eye(41, '') + '</g></g>' + CHEEKS +
'<path d="M21 39 Q32 50 43 39" fill="none" stroke="' + INK + '" stroke-width="3.2" stroke-linecap="round"/>';
},
laugh: function () {
return '<g fill="none" stroke="' + INK + '" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round">' +
'<path d="M17 28 L24 24.5 L17 21"/><path d="M47 28 L40 24.5 L47 21"/></g>' + CHEEKS +
'<g class="aem-jaw"><path d="M18 35 H46 Q45 52 32 52 Q19 52 18 35 Z" fill="' + INK + '"/>' +
'<path d="M20 36 H44 Q44 39 42.5 40 H21.5 Q20 39 20 36 Z" fill="#fff"/>' +
'<path d="M24 48.5 Q32 43 40 48.5 Q36.5 51 32 51 Q27.5 51 24 48.5 Z" fill="#fb7185"/></g>' +
drop(14, 27, 'aem-joy') + drop(50, 27, 'aem-joy');
},
wink: function () {
return '<g class="aem-tilt">' + eye(23, '') + eye(41, 'aem-open') +
'<path class="aem-shut" d="M36.5 27 Q41 22.5 45.5 27" fill="none" stroke="' + INK + '" stroke-width="3.2" stroke-linecap="round"/>' + CHEEKS +
'<path d="M22 41 Q33 49 43 38" fill="none" stroke="' + INK + '" stroke-width="3.2" stroke-linecap="round"/>' +
'<path d="M37.5 43 Q38.5 48 42 47 Q44 45.5 42.5 41" fill="#fb7185" stroke="' + INK + '" stroke-width="1.6" stroke-linejoin="round"/></g>' +
'<path class="aem-spark" d="M56 10 L57.6 14.4 L62 16 L57.6 17.6 L56 22 L54.4 17.6 L50 16 L54.4 14.4 Z" fill="#fde047" stroke="#eab308" stroke-width=".8"/>';
},
cry: function () {
return '<g class="aem-sob"><g fill="none" stroke="' + INK + '" stroke-width="2.8" stroke-linecap="round">' +
'<path d="M16 21 L26 17"/><path d="M38 17 L48 21"/>' +
'<path d="M18 28 Q23 31.5 28 28"/><path d="M36 28 Q41 31.5 46 28"/></g>' +
'<rect x="20.5" y="30" width="5" height="16" rx="2.5" fill="#93c5fd" opacity=".55"/><rect x="38.5" y="30" width="5" height="16" rx="2.5" fill="#93c5fd" opacity=".55"/>' +
'<g class="aem-quiver"><path d="M24 47 Q32 39 40 47" fill="none" stroke="' + INK + '" stroke-width="3.2" stroke-linecap="round"/></g></g>' +
drop(23, 31, 'aem-tear') + drop(41, 31, 'aem-tear', ' style="animation-delay:-.6s"');
}
};
function setup(el) {
var name = el.getAttribute('data-animated-emoji') || 'blink';
if (!FACES[name]) name = 'blink';
var id = 'aem-grad-' + ++count;
var color = el.dataset.color || DEFAULTS.color;
el.classList.add('aem', 'aem-off');
var svg = document.createElementNS(NS, 'svg');
svg.setAttribute('viewBox', '0 0 64 64');
svg.setAttribute('aria-hidden', 'true');
svg.innerHTML =
'<defs><radialGradient id="' + id + '" cx=".38" cy=".32" r=".75"><stop offset="0" stop-color="#fff" stop-opacity=".45"/><stop offset=".5" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#b45309" stop-opacity=".22"/></radialGradient></defs>' +
'<g' + (name === 'laugh' ? ' class="aem-shake"' : '') + '>' +
'<circle cx="32" cy="32" r="28" fill="' + color + '"/><circle cx="32" cy="32" r="28" fill="url(#' + id + ')" stroke="#d97706" stroke-width="1.4"/>' +
FACES[name]() + '</g>';
el.appendChild(svg);
if (!el.getAttribute('aria-label') && !el.closest('[aria-hidden="true"]')) {
el.setAttribute('role', 'img');
el.setAttribute('aria-label', LABELS[name]);
}
}
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { e.target.classList.toggle('aem-off', !e.isIntersecting); });
});
document.querySelectorAll('[data-animated-emoji]').forEach(function (el) {
if (el._aem) return;
el._aem = true;
setup(el);
io.observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><p>That was brilliant <span data-animated-emoji="laugh"></span></p>
<span data-animated-emoji="wink" style="font-size: 96px"></span>