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
- Paste the snippet just before
</head>. - Put the number in an element with
data-seven-segment, or leave it empty and adddata-clockfor the time. Size it withfont-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-hold | How long each value stays. Default 2400 ms. |
data-clock | Show the visitor’s local time, ticking every second. data-clock="12" for a 12-hour clock. |
data-glow | A soft glow around the lit bars. Lovely on dark backgrounds. |
data-dim | Opacity 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.
<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><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>