← E-commerce

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

  1. Paste the snippet just before </head>.
  2. Add data-price-drop-strike to the price, with the new price as its text and the old one in data-was.

Options

Add any of these attributes to the same element.

data-wasThe old price.
data-badgeBadge text, e.g. Sale. By default it works out the saving as a percentage; false hides it.
data-colorColour of the strike, new price and badge. Default #e11d48.
data-loopPlay again every few seconds while in view.

Screen readers hear “Was £89.00, now £59.00”. From code, animacious.priceDrop(element) plays it again.

1 · Snippet
<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>
2 · Your HTML
<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>