Bar Grow
A bar chart whose bars grow up from the axis one after another, overshoot a touch and settle, while their values count up above them.
How to use it
- Paste the snippet just before
</head>. - Add
data-bar-growto a container with a height, and one child per bar withdata-valueanddata-label.
Options
Add any of these attributes to the same element.
data-color | Bar colour, on the chart or on one bar. Default #a855f7. |
data-max | Top of the scale. Default: the largest value. |
data-suffix | Text after each value, e.g. %. |
data-loop | Drop and regrow every few seconds. |
data-duration | Time for each bar to grow. Default 1100 ms. |
data-stagger | Gap between bars. Default 90 ms. |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Bar Grow: a bar chart whose bars grow up from the axis one after another,
// overshoot a touch and settle, while their values count up above them.
// Usage:
// <div data-bar-grow style="height: 220px">
// <div data-value="42" data-label="Mon"></div>
// <div data-value="68" data-label="Tue"></div> …
// </div>
// Optional: data-color on the chart or on a bar (default #a855f7), data-max
// (top of the scale; default is the largest value), data-suffix (e.g. "%"),
// data-duration, data-stagger (ms), data-loop (drop and regrow every few seconds).
var DEFAULTS = { color: '#a855f7', duration: 1100, stagger: 90, hold: 2600 };
var css =
'.bgr{display:flex;align-items:stretch;gap:0.6em;font-size:13px}' +
'.bgr-col{flex:1;display:flex;flex-direction:column;align-items:stretch;min-width:0}' +
'.bgr-track{flex:1;display:flex;flex-direction:column;justify-content:flex-end;align-items:stretch}' +
'.bgr-val{text-align:center;font-weight:600;font-variant-numeric:tabular-nums;margin-bottom:4px}' +
'.bgr-bar{border-radius:6px 6px 2px 2px;transform-origin:50% 100%}' +
'.bgr-label{text-align:center;opacity:.65;margin-top:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function build(el) {
var d = el.dataset;
var items = Array.from(el.children).map(function (c) {
return { value: +c.dataset.value || 0, label: c.dataset.label || '', color: c.dataset.color };
});
var max = +d.max || Math.max.apply(null, items.map(function (i) { return i.value; })) || 1;
el.classList.add('bgr');
el.setAttribute('role', 'img');
el.setAttribute('aria-label', items.map(function (i) { return (i.label ? i.label + ': ' : '') + i.value + (d.suffix || ''); }).join(', '));
el.textContent = '';
return items.map(function (it) {
var col = document.createElement('div');
col.className = 'bgr-col';
col.setAttribute('aria-hidden', 'true');
var track = document.createElement('div');
track.className = 'bgr-track';
var val = document.createElement('div');
val.className = 'bgr-val';
val.textContent = it.value + (d.suffix || '');
var bar = document.createElement('div');
bar.className = 'bgr-bar';
bar.style.height = (it.value / max) * 100 + '%';
bar.style.background = it.color || d.color || DEFAULTS.color;
track.appendChild(val);
track.appendChild(bar);
col.appendChild(track);
if (it.label) {
var lab = document.createElement('div');
lab.className = 'bgr-label';
lab.textContent = it.label;
col.appendChild(lab);
}
el.appendChild(col);
return { bar: bar, val: val, value: it.value };
});
}
function grow(el, bars) {
var d = el.dataset;
var duration = +d.duration || DEFAULTS.duration;
var stagger = +d.stagger || DEFAULTS.stagger;
var suffix = d.suffix || '';
bars.forEach(function (b, i) {
anime.animate(b.bar, { scaleY: [0, 1], duration: duration, delay: i * stagger, ease: 'outBack(1.5)' });
var counter = { n: 0 };
anime.animate(b.val, { opacity: [0, 1], duration: 300, delay: i * stagger });
anime.animate(counter, {
n: b.value,
duration: duration * 0.8,
delay: i * stagger,
ease: 'out(3)',
onUpdate: function () { b.val.textContent = Math.round(counter.n) + suffix; }
});
});
if (d.loop !== undefined && d.loop !== 'false') {
setTimeout(function () {
anime.animate(bars.map(function (b) { return b.bar; }), {
scaleY: 0, duration: 400, delay: anime.stagger(40), ease: 'in(2)',
onComplete: function () { grow(el, bars); }
});
anime.animate(bars.map(function (b) { return b.val; }), { opacity: 0, duration: 300 });
}, duration + bars.length * stagger + DEFAULTS.hold);
}
}
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);
grow(e.target, e.target._bgr);
});
}, { threshold: 0.3 });
document.querySelectorAll('[data-bar-grow]').forEach(function (el) {
if (el._bgr) return;
var bars = (el._bgr = build(el));
if (reduced) return;
bars.forEach(function (b) { anime.utils.set(b.bar, { scaleY: 0 }); b.val.style.opacity = 0; });
io.observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div data-bar-grow data-suffix="%" style="height: 220px">
<div data-value="42" data-label="Mon"></div>
<div data-value="68" data-label="Tue"></div>
<div data-value="55" data-label="Wed"></div>
</div>