Error Cross
A circle draws itself round, two quick strokes strike through it to make a cross, and the icon gives a short “no” shake. For failed payments, rejected forms and anything else that didn’t work. Outline or filled, sized with font-size.
Couldn’t save
Payment failed
How to use it
- Paste the snippet just before
</head>. - Add an empty
<div data-error-cross></div>where the cross should appear. Itsfont-sizeis the size of the icon. - It draws when it scrolls into view, or call
animacious.errorCross(el)to draw it again after another failed try.
Options
Add any of these attributes to the same element.
data-color | Colour of the circle and cross. Default #dc2626. |
data-fill | Fill the circle in behind a white cross. |
data-shake="false" | Pop instead of shaking once the cross lands. |
data-duration | How long the circle takes to draw. Default 650 ms. |
data-loop | Fade away after a pause and draw again. |
data-hold | How long the finished cross shows before looping. Default 1800 ms. |
It’s labelled “Error” for screen readers; set your own aria-label to say what went wrong. With reduced motion the finished cross simply appears.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Error Cross: a circle draws itself round, then two strokes strike through
// it to make a cross, and the whole thing gives a short "no" shake.
// For failed payments, rejected forms and anything that didn't work.
// Usage: <div data-error-cross></div> (size it with font-size: 1em = the icon)
// Optional: data-color (default #dc2626), data-fill (the circle fills in
// behind a white cross), data-shake="false" (no shake), data-duration (ms
// the circle takes, default 650), data-loop (fade away and draw again),
// data-hold (ms the finished cross shows before looping, default 1800),
// aria-label (default "Error").
// Script use: animacious.errorCross(el) draws it again.
var DEFAULTS = { color: '#dc2626', duration: 650, hold: 1800 };
var css =
'.ecx{position:relative;display:inline-block;width:1em;height:1em;vertical-align:middle;line-height:0}' +
'.ecx-bg{transform-box:fill-box;transform-origin:50% 50%}' +
'.ecx svg{position:relative;display:block;width:100%;height:100%;overflow:visible}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function setup(el) {
var d = el.dataset;
var c = d.color || DEFAULTS.color;
var fill = d.fill !== undefined && d.fill !== 'false';
var ink = fill ? '#fff' : c;
el.classList.add('ecx');
el.setAttribute('role', 'img');
if (!el.getAttribute('aria-label')) el.setAttribute('aria-label', 'Error');
el.innerHTML = '<svg viewBox="0 0 52 52" aria-hidden="true">' +
'<circle class="ecx-bg" cx="26" cy="26" r="23" fill="' + c + '"/>' +
'<path class="ecx-circle" d="M26 3a23 23 0 1 1 0 46a23 23 0 1 1 0-46" fill="none" stroke="' + c + '" stroke-width="3.2" stroke-linecap="round"/>' +
'<path class="ecx-line" d="M18 18L34 34" fill="none" stroke="' + ink + '" stroke-width="4" stroke-linecap="round"/>' +
'<path class="ecx-line" d="M34 18L18 34" fill="none" stroke="' + ink + '" stroke-width="4" stroke-linecap="round"/>' +
'</svg>';
var lines = el.querySelectorAll('.ecx-line');
var s = el._ecx = {
svg: el.querySelector('svg'), bg: el.querySelector('.ecx-bg'),
circle: anime.svg.createDrawable(el.querySelector('.ecx-circle')),
lines: [anime.svg.createDrawable(lines[0])[0], anime.svg.createDrawable(lines[1])[0]],
timer: null, run: 0
};
anime.utils.set(s.bg, { opacity: fill ? 1 : 0.1 });
if (reduced()) return;
anime.utils.set(s.circle, { draw: '0 0' });
anime.utils.set(s.lines, { draw: '0 0' });
anime.utils.set(s.bg, { scale: 0 });
}
function play(el) {
var s = el._ecx, d = el.dataset;
var dur = +d.duration || DEFAULTS.duration;
var loop = d.loop !== undefined && d.loop !== 'false';
var run = ++s.run;
clearTimeout(s.timer);
anime.utils.set(s.svg, { opacity: 1, scale: 1, x: 0 });
anime.utils.set(s.circle, { draw: '0 0' });
anime.utils.set(s.lines, { draw: '0 0' });
anime.utils.set(s.bg, { scale: 0 });
anime.animate(s.circle, { draw: ['0 0', '0 1'], duration: dur, ease: 'inOut(2)' });
anime.animate(s.bg, { scale: [0, 1], duration: dur * 0.6, delay: dur * 0.75, ease: 'outBack(1.4)' });
// Two quick strikes, like a pen crossing something out.
anime.animate(s.lines[0], { draw: ['0 0', '0 1'], duration: 220, delay: dur * 0.9, ease: 'in(1.6)' });
anime.animate(s.lines[1], { draw: ['0 0', '0 1'], duration: 220, delay: dur * 0.9 + 200, ease: 'in(1.6)' });
var landed = dur * 0.9 + 420;
if (d.shake !== 'false') {
anime.animate(s.svg, {
x: ['0em', '-0.09em', '0.08em', '-0.06em', '0.04em', '-0.02em', '0em'],
duration: 520, delay: landed, ease: 'inOut(1.5)'
});
} else {
anime.animate(s.svg, { scale: [{ to: 1.08, duration: 140 }, { to: 1, duration: 500, ease: 'outElastic(1, .5)' }], delay: landed });
}
if (!loop) return;
s.timer = setTimeout(function () {
if (run !== s.run) return;
anime.animate(s.svg, {
opacity: 0, scale: 0.85, duration: 350, ease: 'in(2)',
onComplete: function () { if (run === s.run) s.timer = setTimeout(function () { play(el); }, 350); }
});
}, landed + 520 + (+d.hold || DEFAULTS.hold));
}
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (!e.isIntersecting) return;
io.unobserve(e.target);
play(e.target);
});
}, { threshold: 0.4 });
document.querySelectorAll('[data-error-cross]').forEach(function (el) {
if (el._ecx) return;
setup(el);
if (!reduced()) io.observe(el);
});
}
window.animacious = window.animacious || {};
window.animacious.errorCross = function (el) { if (el._ecx && !reduced()) play(el); };
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div data-error-cross style="font-size: 64px"></div>
<!-- filled, without the shake: -->
<div data-error-cross data-fill data-shake="false" aria-label="Payment failed"></div>