Breaking News Banner
A banner slams in big and lands with a white flash and a shudder, the headline slides up into its bar, and the red “Breaking” tag keeps pulsing, with a blinking dot, for as long as it’s up.
How to use it
- Paste the snippet just before
</head>. - Add
data-breaking-news-bannerto an element holding the headline. It fills the width of its container; size it withfont-size.
Options
Add any of these attributes to the same element.
data-tag | Text of the tag. Default “Breaking”. |
data-sub | Smaller line under the tag. Default “News”; set it empty for none. |
data-color | Tag colour. Default #e11d48. |
data-loop | Leave after a while and slam in again. |
data-hold | How long it stays before leaving, when looping. Default 4200 ms. |
Call animacious.breakingNews(el) to slam it in again, for example after changing the headline.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Breaking News Banner: a banner slams in with a white flash and a little
// shudder, the headline slides up into the bar, and a "Breaking" tag keeps
// pulsing red while it's on screen.
// Usage: <div data-breaking-news-banner>Storm closes every bridge in the city</div>
// Size it with font-size; it fills the width of its container.
// Optional: data-tag (tag text, default "Breaking"), data-sub (smaller line
// under the tag, default "News"; data-sub="" for none), data-color (tag
// colour, default #e11d48), data-loop (leave and slam in again),
// data-hold (ms on screen before leaving when looping, default 4200).
// Script use: animacious.breakingNews(el) slams it in again.
var DEFAULTS = { tag: 'Breaking', sub: 'News', color: '#e11d48', hold: 4200 };
var css =
'.bnb{display:block;text-align:left;line-height:1.15;--bnb-c:' + DEFAULTS.color + '}' +
'.bnb-in{position:relative;display:flex;align-items:stretch}' +
'.bnb-tag{position:relative;flex:none;display:flex;flex-direction:column;justify-content:center;padding:0.45em 0.8em;background:var(--bnb-c);color:#fff;text-transform:uppercase;z-index:1;box-shadow:0 0 0 rgba(225,29,72,0)}' +
'.bnb-tag b{font-weight:900;font-size:1.05em;letter-spacing:0.04em;display:flex;align-items:center;gap:0.4em}' +
'.bnb-tag b::before{content:"";width:0.5em;height:0.5em;border-radius:50%;background:#fff;animation:bnb-blink 1s steps(1) infinite}' +
'.bnb-tag b,.bnb-tag small{position:relative}' +
'.bnb-tag small{font-size:0.62em;font-weight:700;letter-spacing:0.3em;opacity:.85}' +
'.bnb-glow{position:absolute;inset:0;background:#fff;opacity:0;pointer-events:none}' +
'.bnb-body{position:relative;flex:1;min-width:0;display:flex;align-items:center;overflow:hidden;background:#fff;color:#111827;padding:0.4em 0.9em;transform-origin:0 50%}' +
'.bnb-text{display:block;font-weight:800;letter-spacing:-0.01em}' +
'.bnb-flash{position:absolute;inset:-0.2em;background:#fff;opacity:0;pointer-events:none;z-index:2}' +
'@keyframes bnb-blink{50%{opacity:0}}' +
'@media (prefers-reduced-motion:reduce){.bnb-tag b::before{animation:none}}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function setup(el) {
var d = el.dataset;
var text = el.textContent.replace(/\s+/g, ' ').trim();
var tagText = d.tag !== undefined ? d.tag : DEFAULTS.tag;
var subText = d.sub !== undefined ? d.sub : DEFAULTS.sub;
el.classList.add('bnb');
el.style.setProperty('--bnb-c', d.color || DEFAULTS.color);
el.setAttribute('role', 'status');
el.setAttribute('aria-label', [tagText, subText].filter(Boolean).join(' ') + ': ' + text);
el.textContent = '';
el.innerHTML = '<span class="bnb-in">' +
'<span class="bnb-tag" aria-hidden="true"><span class="bnb-glow"></span><b></b>' +
(subText ? '<small></small>' : '') + '</span>' +
'<span class="bnb-body" aria-hidden="true"><span class="bnb-text"></span></span>' +
'<span class="bnb-flash" aria-hidden="true"></span></span>';
el.querySelector('.bnb-tag b').textContent = tagText;
if (subText) el.querySelector('.bnb-tag small').textContent = subText;
el.querySelector('.bnb-text').textContent = text;
el._bnb = {
tag: el.querySelector('.bnb-tag'),
glow: el.querySelector('.bnb-glow'),
body: el.querySelector('.bnb-body'),
text: el.querySelector('.bnb-text'),
flash: el.querySelector('.bnb-flash')
};
if (!reduced()) {
anime.utils.set(el, { opacity: 0 });
}
}
function play(el) {
var s = el._bnb, d = el.dataset;
var loop = d.loop !== undefined && d.loop !== 'false';
var hold = d.hold !== undefined ? +d.hold : DEFAULTS.hold;
if (s.tl) s.tl.pause();
if (s.pulse) s.pulse.pause();
anime.utils.set(el, { x: 0 });
anime.utils.set(s.body, { scaleX: 0 });
anime.utils.set(s.text, { y: '120%' });
var tl = anime.createTimeline({
onComplete: function () { if (loop) setTimeout(function () { play(el); }, 900); }
});
// Slam: drops in big and lands hard, with a flash and a shudder.
tl.add(el, { opacity: [0, 1], scale: [1.35, 1], duration: 260, ease: 'in(3)' })
.add(s.flash, { opacity: [0.95, 0], duration: 520, ease: 'out(2)' }, '-=10')
.add(el, { x: [0, -10, 8, -5, 3, 0], duration: 360, ease: 'linear' }, '<<')
.add(s.body, { scaleX: [0, 1], duration: 440, ease: 'out(4)' }, '-=380')
.add(s.text, { y: ['120%', '0%'], duration: 520, ease: 'out(4)' }, '-=220');
if (loop) {
tl.add(s.text, { y: '-120%', duration: 320, ease: 'in(3)' }, '+=' + hold)
.add(s.body, { scaleX: 0, duration: 360, ease: 'inOut(3)' }, '-=120')
.add(el, { opacity: 0, x: '-6%', duration: 300, ease: 'in(3)' }, '-=120');
}
s.tl = tl;
// The tag throbs: a glow that swells and fades on a heartbeat.
s.pulse = anime.createTimeline({ loop: true, delay: 500 })
.add(s.glow, { opacity: [0, 0.28, 0], duration: 900, ease: 'inOut(2)' })
.add(s.tag, { scale: [1, 1.06, 1], duration: 900, ease: 'inOut(2)' }, 0);
}
window.animacious = window.animacious || {};
window.animacious.breakingNews = function (el) { if (el && el._bnb && !reduced()) play(el); };
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (!e.isIntersecting) return;
io.unobserve(e.target);
play(e.target);
});
}, { threshold: 0.2 });
document.querySelectorAll('[data-breaking-news-banner]').forEach(function (el) {
if (el._bnb) return;
setup(el);
if (!reduced()) io.observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div data-breaking-news-banner>
Storm closes every bridge across the city
</div>