← Numbers

Seven-segment Display

Digits built from seven glowing bars, like a bedside clock or an old calculator. When it scrolls into view the bars flicker on like a display warming up, and when the value changes only the bars that differ switch over. Show a number, cycle through values or run a live clock.

HELLO

How to use it

  1. Paste the snippet just before </head>.
  2. Put the number in an element with data-seven-segment, or leave it empty and add data-clock for the time. Size it with font-size; the bars take the text colour.

Options

Add any of these attributes to the same element.

data-values="A|B|C"Several values to switch between in turn.
data-holdHow long each value stays. Default 2400 ms.
data-clockShow the visitor’s local time, ticking every second. data-clock="12" for a 12-hour clock.
data-glowA soft glow around the lit bars. Lovely on dark backgrounds.
data-dimOpacity of the unlit bars. Default 0.08; 0 hides them.

It shows 0–9, - _ : . and spaces, plus the letters a seven-segment display can manage: A b C d E F G H I J L n O P r S t U y Z.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Seven-segment Display: digits drawn from seven bars, like a digital clock
    // or an old calculator. When it scrolls into view the segments flicker on
    // like a warming display; when the value changes, only the bars that differ
    // switch over.
    // Usage: <span data-seven-segment>12:45</span>
    //    or: <span data-seven-segment data-clock></span>   (live local time)
    // Optional: data-values (several values to switch between, "A|B|C"),
    // data-hold (ms each value stays, default 2400), data-clock (show the time,
    // ticking; data-clock="12" for a 12-hour clock), data-glow (a soft glow
    // round the lit bars), data-dim (opacity of unlit bars, default 0.08).
    // Bars take the text colour; size it with font-size.
    // It can show 0–9, - _ : . and space, and the letters
    // A b C c d E F G H h I J L n O o P q r S t U u y Z.
    var DEFAULTS = { hold: 2400, dim: 0.08 };
    var SEGS = 'abcdefg';
    var GLYPHS = {
      '0': 'abcdef', '1': 'bc', '2': 'abdeg', '3': 'abcdg', '4': 'bcfg', '5': 'acdfg', '6': 'acdefg', '7': 'abc',
      '8': 'abcdefg', '9': 'abcdfg', '-': 'g', '_': 'd', ' ': '', A: 'abcefg', b: 'cdefg', C: 'adef', c: 'deg',
      d: 'bcdeg', E: 'adefg', F: 'aefg', G: 'acdef', H: 'bcefg', h: 'cefg', I: 'ef', J: 'bcde', L: 'def',
      n: 'ceg', O: 'abcdef', o: 'cdeg', P: 'abefg', q: 'abcfg', r: 'eg', S: 'acdfg', t: 'defg', U: 'bcdef',
      u: 'cde', y: 'bcdfg', Z: 'abdeg'
    };

    // Bar shapes in a 56 × 100 box: hexagons with pointed ends and a small gap
    // where they meet.
    var T = 9, H = T / 2, L = 4.5, R = 51.5, TOP = 4.5, MID = 50, BOT = 95.5, G = 2.2;
    function hBar(y, x1, x2) {
      return [x1, y, x1 + H, y - H, x2 - H, y - H, x2, y, x2 - H, y + H, x1 + H, y + H].join(' ');
    }
    function vBar(x, y1, y2) {
      return [x, y1, x + H, y1 + H, x + H, y2 - H, x, y2, x - H, y2 - H, x - H, y1 + H].join(' ');
    }
    var SHAPES = {
      a: hBar(TOP, L + G, R - G), g: hBar(MID, L + G, R - G), d: hBar(BOT, L + G, R - G),
      f: vBar(L, TOP + G, MID - G), b: vBar(R, TOP + G, MID - G),
      e: vBar(L, MID + G, BOT - G), c: vBar(R, MID + G, BOT - G)
    };

    var css =
      '.ssd{display:inline-flex;align-items:flex-end;gap:0.1em;line-height:1;vertical-align:middle}' +
      '.ssd svg{display:block;height:1em;overflow:visible;fill:currentColor;transform:skewX(-6deg)}' +
      '.ssd-digit{width:0.56em}.ssd-colon{width:0.2em}.ssd-dot{width:0.16em}' +
      '.ssd-glow svg{filter:drop-shadow(0 0 0.05em currentColor) drop-shadow(0 0 0.18em currentColor)}' +
      '.ssd-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    var NS = 'http://www.w3.org/2000/svg';
    function svg(cls, w) {
      var s = document.createElementNS(NS, 'svg');
      s.setAttribute('class', cls);
      s.setAttribute('viewBox', '0 0 ' + w + ' 100');
      s.setAttribute('aria-hidden', 'true');
      return s;
    }
    function shape(parent, tag, attrs) {
      var n = document.createElementNS(NS, tag);
      for (var k in attrs) n.setAttribute(k, attrs[k]);
      parent.appendChild(n);
      return n;
    }

    function glyph(ch) {
      if (GLYPHS[ch] !== undefined) return GLYPHS[ch];
      if (GLYPHS[ch.toUpperCase()] !== undefined) return GLYPHS[ch.toUpperCase()];
      if (GLYPHS[ch.toLowerCase()] !== undefined) return GLYPHS[ch.toLowerCase()];
      return '';
    }
    function kind(ch) { return ch === ':' ? 'colon' : ch === '.' ? 'dot' : 'digit'; }

    // One cell per character. Each keeps its bars and which of them are lit.
    function buildCells(d, text) {
      d.box.textContent = '';
      d.cells = text.split('').map(function (ch) {
        var k = kind(ch), cell = { kind: k, parts: {}, on: {} };
        if (k === 'colon') {
          cell.svg = svg('ssd-colon', 20);
          cell.parts.x = shape(cell.svg, 'g', {});
          shape(cell.parts.x, 'circle', { cx: 10, cy: 30, r: 5.5 });
          shape(cell.parts.x, 'circle', { cx: 10, cy: 70, r: 5.5 });
        } else if (k === 'dot') {
          cell.svg = svg('ssd-dot', 16);
          cell.parts.x = shape(cell.svg, 'circle', { cx: 6, cy: 94, r: 5.5 });
        } else {
          cell.svg = svg('ssd-digit', 56);
          SEGS.split('').forEach(function (s) { cell.parts[s] = shape(cell.svg, 'polygon', { points: SHAPES[s] }); });
        }
        for (var p in cell.parts) { cell.parts[p].style.opacity = d.dim; cell.on[p] = false; }
        d.box.appendChild(cell.svg);
        return cell;
      });
      d.layout = text.split('').map(kind).join(',');
    }

    function litFor(cell, ch) {
      var want = {};
      if (cell.kind === 'digit') { var g = glyph(ch); SEGS.split('').forEach(function (s) { want[s] = g.indexOf(s) >= 0; }); }
      else want.x = true;
      return want;
    }

    // Show a value. warm: flicker every lit bar on, in a scattered order, as if
    // the display had just been switched on. Otherwise only changed bars switch.
    function show(d, text, animate, warm) {
      if (d.layout !== text.split('').map(kind).join(',')) { buildCells(d, text); warm = true; }
      d.sr.textContent = text;
      var turnOn = [], turnOff = [];
      text.split('').forEach(function (ch, i) {
        var cell = d.cells[i], want = litFor(cell, ch);
        for (var p in want) {
          if (want[p] === cell.on[p]) continue;
          cell.on[p] = want[p];
          (want[p] ? turnOn : turnOff).push(cell.parts[p]);
        }
      });
      if (!animate) {
        turnOn.forEach(function (n) { n.style.opacity = 1; });
        turnOff.forEach(function (n) { n.style.opacity = d.dim; });
        return;
      }
      if (turnOff.length) anime.animate(turnOff, { opacity: d.dim, duration: 160, ease: 'out(2)' });
      if (!turnOn.length) return;
      if (warm) {
        anime.animate(turnOn, {
          opacity: [d.dim, 1, 0.25, 1, 0.6, 1],
          duration: 520,
          delay: anime.stagger(22, { from: 'random' }),
          ease: 'linear'
        });
      } else {
        anime.animate(turnOn, { opacity: [d.dim, 1], duration: 140, ease: 'out(2)' });
      }
    }

    function two(n) { return (n < 10 ? '0' : '') + n; }
    function clockText(d) {
      var now = new Date(), h = now.getHours();
      if (d.twelve) h = h % 12 || 12;
      return (d.twelve && h < 10 ? ' ' : two(h).charAt(0)) + two(h).charAt(1) + ':' + two(now.getMinutes()) + ':' + two(now.getSeconds());
    }

    function startClock(d, animate) {
      show(d, clockText(d), animate, true);
      var colons = d.cells.filter(function (c) { return c.kind === 'colon'; }).map(function (c) { return c.parts.x; });
      function tick() {
        show(d, clockText(d), animate, false);
        // The colons blink once a second, as on a bedside clock.
        if (animate) anime.animate(colons, { opacity: [1, 0.3, 1], duration: 1000, ease: 'inOut(2)' });
        setTimeout(tick, 1000 - (Date.now() % 1000));
      }
      setTimeout(tick, 1000 - (Date.now() % 1000));
    }

    function play(d) {
      if (d.clock) return startClock(d, true);
      show(d, d.values[0], true, true);
      if (d.values.length < 2) return;
      var i = 0;
      setInterval(function () {
        i = (i + 1) % d.values.length;
        show(d, d.values[i], true, false);
      }, d.hold);
    }

    function setup(el) {
      var ds = el.dataset;
      var d = {
        el: el,
        clock: ds.clock !== undefined && ds.clock !== 'false',
        twelve: ds.clock === '12',
        hold: +ds.hold || DEFAULTS.hold,
        dim: ds.dim !== undefined ? +ds.dim : DEFAULTS.dim
      };
      var first = el.textContent.trim();
      d.values = ds.values ? ds.values.split('|') : [first || '0'];
      el.textContent = '';
      el.classList.add('ssd');
      if (ds.glow !== undefined && ds.glow !== 'false') el.classList.add('ssd-glow');
      d.box = document.createElement('span');
      d.box.style.display = 'contents';
      d.sr = document.createElement('span');
      d.sr.className = 'ssd-sr';
      if (d.clock) el.setAttribute('role', 'timer');
      el.appendChild(d.box);
      el.appendChild(d.sr);
      // Start with every bar unlit, so the display reads as switched off.
      buildCells(d, d.clock ? clockText(d) : d.values[0]);
      return d;
    }

    function init() {
      var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) {
          if (!e.isIntersecting) return;
          io.unobserve(e.target);
          play(e.target._ssd);
        });
      }, { threshold: 0.4 });
      document.querySelectorAll('[data-seven-segment]').forEach(function (el) {
        if (el._ssd) return;
        var d = (el._ssd = setup(el));
        if (reduced) {
          if (d.clock) startClock(d, false);
          else show(d, d.values[0], false);
          return;
        }
        io.observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<p style="font-size: 64px; color: #ff3b6b">
  <span data-seven-segment data-clock data-glow></span>
</p>

<!-- a value, or several in turn: -->
<span data-seven-segment data-values="12:45|HELLO|98.6">12:45</span>