Price Drop Strike
A red line slashes through the old price, which shrinks and greys out to one side. Then the new price drops in from above with a bounce, and a badge with the saving pops up next to it.
£59.00
How to use it
- Paste the snippet just before
</head>. - Add
data-price-drop-striketo the price, with the new price as its text and the old one indata-was.
Options
Add any of these attributes to the same element.
data-was | The old price. |
data-badge | Badge text, e.g. Sale. By default it works out the saving as a percentage; false hides it. |
data-color | Colour of the strike, new price and badge. Default #e11d48. |
data-loop | Play again every few seconds while in view. |
Screen readers hear “Was £89.00, now £59.00”. From code, animacious.priceDrop(element) plays it again.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Price Drop Strike: a red line slashes through the old price, which
// shrinks and greys out, then the new price drops in from above with a
// bounce and a "-34%" badge pops up beside it.
// Usage: <p data-price-drop-strike data-was="£89.00">£59.00</p>
// The element's text is the new price; data-was is the old one.
// Optional: data-badge (badge text, e.g. "Sale"; by default the saving is
// worked out as a percentage, and data-badge="false" hides it),
// data-color (colour of the strike, the new price and the badge, default
// #e11d48), data-loop (play again every few seconds while in view).
// Script use: animacious.priceDrop(el) plays it again from code.
var DEFAULTS = { color: '#e11d48', hold: 2600 };
var css =
'.pds{display:inline-flex;align-items:baseline;flex-wrap:wrap;justify-content:center;gap:0 .35em;font-variant-numeric:tabular-nums}' +
'.pds-was{position:relative;display:inline-block;transform-origin:0 70%}' +
'.pds-line{position:absolute;left:-6%;right:-6%;top:52%;height:.09em;min-height:2px;margin-top:-.045em;border-radius:99px;background:var(--pds-color);transform-origin:0 50%;rotate:-8deg}' +
'.pds-now{display:inline-block;color:var(--pds-color)}' +
'.pds-badge{display:inline-block;align-self:center;padding:.18em .45em;border-radius:.35em;background:var(--pds-color);color:#fff;' +
'font-size:.38em;font-weight:800;letter-spacing:0;line-height:1.2;transform-origin:0 50%}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function num(s) { var n = parseFloat(String(s).replace(/[^0-9.,-]/g, '').replace(/,(?=\d{3}\b)/g, '').replace(',', '.')); return isNaN(n) ? null : n; }
function setup(el) {
var d = el.dataset;
var now = el.textContent.trim();
var was = d.was || '';
el.classList.add('pds');
el.style.setProperty('--pds-color', d.color || DEFAULTS.color);
el.setAttribute('aria-label', (was ? 'Was ' + was + ', now ' : '') + now);
el.textContent = '';
var w = document.createElement('span');
w.className = 'pds-was';
w.setAttribute('aria-hidden', 'true');
w.innerHTML = '<span></span><span class="pds-line"></span>';
w.firstChild.textContent = was;
var n = document.createElement('span');
n.className = 'pds-now';
n.setAttribute('aria-hidden', 'true');
n.textContent = now;
el.appendChild(w);
el.appendChild(n);
var badgeText = d.badge;
if (badgeText === undefined) {
var a = num(was), b = num(now);
badgeText = a && b !== null && b < a ? '−' + Math.round((1 - b / a) * 100) + '%' : '';
}
var badge = null;
if (badgeText && badgeText !== 'false') {
badge = document.createElement('span');
badge.className = 'pds-badge';
badge.setAttribute('aria-hidden', 'true');
badge.textContent = badgeText;
el.appendChild(badge);
}
el._pds = { was: w, label: w.firstChild, line: w.lastChild, now: n, badge: badge };
if (!reduced()) start(el);
else finish(el);
}
function start(el) {
var s = el._pds;
anime.utils.set(s.line, { scaleX: 0 });
anime.utils.set(s.now, { opacity: 0 });
if (s.badge) anime.utils.set(s.badge, { opacity: 0, scale: 0 });
}
function finish(el) {
var s = el._pds;
anime.utils.set(s.line, { scaleX: 1 });
anime.utils.set(s.was, { fontSize: '0.55em', opacity: 0.55 });
anime.utils.set(s.now, { opacity: 1, y: 0 });
if (s.badge) anime.utils.set(s.badge, { opacity: 1, scale: 1 });
}
function play(el) {
var s = el._pds;
if (reduced()) { finish(el); return; }
if (s.tl) s.tl.pause();
start(el);
anime.utils.set(s.was, { fontSize: '1em', opacity: 1 });
var loop = el.dataset.loop !== undefined && el.dataset.loop !== 'false';
var tl = s.tl = anime.createTimeline({
onComplete: function () {
if (!loop) return;
s.wait = setTimeout(function () {
// Fade the sale away and bring the old price back, then go again.
var parts = [s.now, s.line].concat(s.badge ? [s.badge] : []);
anime.animate(parts, { opacity: 0, duration: 300, ease: 'in(2)' });
anime.animate(s.was, { fontSize: '1em', opacity: 1, duration: 420, delay: 200, ease: 'out(3)' });
s.wait = setTimeout(function () { if (s.visible) play(el); else s.pending = true; }, 900);
}, DEFAULTS.hold);
}
});
tl.add(s.line, { scaleX: [0, 1], duration: 380, ease: 'inOut(3)' }, 250)
.add(s.label, { x: [0, -2, 2, 0], duration: 240, ease: 'out(2)' }, 560)
.add(s.was, { fontSize: '0.55em', opacity: 0.55, duration: 520, ease: 'outBack(1.4)' }, 760)
.add(s.now, { opacity: [0, 1], y: ['-1em', 0], scaleY: [1.15, 1], duration: 760, ease: 'outBounce' }, 900);
if (s.badge) tl.add(s.badge, { opacity: [0, 1], scale: [0, 1], rotate: [-25, -6], duration: 640, ease: 'outElastic(1, .5)' }, 1450);
}
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
var el = e.target, s = el._pds;
s.visible = e.isIntersecting;
if (!e.isIntersecting) return;
if (!s.played) { s.played = true; play(el); }
else if (s.pending) { s.pending = false; play(el); }
});
}, { threshold: 0.3 });
document.querySelectorAll('[data-price-drop-strike]').forEach(function (el) {
if (el._pds) return;
setup(el);
if (!reduced()) io.observe(el);
});
}
window.animacious = window.animacious || {};
window.animacious.priceDrop = function (el) { if (el._pds) { clearTimeout(el._pds.wait); play(el); } };
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><p class="price" data-price-drop-strike data-was="£89.00">£59.00</p>
<!-- with your own badge: -->
<p class="price" data-price-drop-strike data-was="£89.00" data-badge="Black Friday">£59.00</p>