Weather Forecast Strip
A row of days for a weather segment. The days slide in one after another, each icon pops in and keeps gently moving (the sun turns, clouds drift, rain and snow fall, lightning flickers), the temperatures count up and a small bar shows each day’s range against the week.
- Mon
- Tue
- Wed
- Thu
- Fri
- Sat
How to use it
- Paste the snippet just before
</head>. - Add
data-weather-forecast-stripto a<ul>with one<li>per day: the day as its text, plusdata-icon,data-highanddata-low. - It fills its container’s width; size it with
font-size.
Options
Add any of these attributes to the same element.
data-icon (on a day) | sun, partly, cloud, rain, storm or snow. |
data-high, data-low (on a day) | That day’s temperatures. |
data-title | A heading above the strip, such as the place. |
data-unit | Shown after each number. Default “°”. |
data-loop | Slide away and in again, over and over. |
data-hold | How long it stays before looping. Default 4000 ms. |
The icons move only while the strip is on screen. Call animacious.weatherForecastStrip(el) to play it again.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Weather Forecast Strip: a row of days for a weather segment. The days
// slide in one after another, each icon pops in and keeps gently moving
// (the sun turns, clouds drift, rain falls), the temperatures count up and
// a small bar shows each day's range against the week.
// Usage: <ul data-weather-forecast-strip data-title="London">
// <li data-icon="sun" data-high="24" data-low="15">Mon</li>
// <li data-icon="rain" data-high="18" data-low="12">Tue</li>
// </ul>
// Icons: sun, partly, cloud, rain, storm, snow. It fills the width of its
// container; size it with font-size.
// Optional: data-title (a heading above the strip), data-unit (shown after
// the numbers, default "°"), data-loop (slide away and in again),
// data-hold (ms it stays before looping, default 4000).
// Script use: animacious.weatherForecastStrip(el) plays it again.
var DEFAULTS = { unit: '°', hold: 4000 };
var css =
'.wfst{position:relative;display:block;margin:0;padding:0;list-style:none;line-height:1.15;color:#fff;overflow:hidden}' +
'.wfst-title{display:flex;align-items:center;gap:0.5em;margin:0 0 0.6em 0.2em;font-size:0.85em;font-weight:800;letter-spacing:0.12em;text-transform:uppercase}' +
'.wfst-title::before{content:"";width:0.25em;height:1.1em;border-radius:0.1em;background:#facc15}' +
'.wfst-row{display:flex;gap:0.4em}' +
'.wfst-day{flex:1 1 0;min-width:0;display:flex;flex-direction:column;align-items:center;gap:0.35em;padding:0.7em 0.2em 0.75em;border-radius:0.7em;' +
'background:linear-gradient(rgba(255,255,255,.16),rgba(255,255,255,.06));border:1px solid rgba(255,255,255,.18);backdrop-filter:blur(6px)}' +
'.wfst-day:first-child{background:linear-gradient(rgba(255,255,255,.3),rgba(255,255,255,.12))}' +
'.wfst-name{font-size:0.8em;font-weight:800;letter-spacing:0.08em;text-transform:uppercase;opacity:.9}' +
'.wfst-ic{width:2.6em;height:2.6em;overflow:visible}' +
'.wfst-hi{font-size:1.25em;font-weight:900;font-variant-numeric:tabular-nums}' +
'.wfst-lo{font-size:0.8em;font-weight:600;opacity:.7;font-variant-numeric:tabular-nums}' +
'.wfst-range{position:relative;width:70%;height:0.28em;border-radius:1em;background:rgba(255,255,255,.18)}' +
'.wfst-range i{position:absolute;top:0;bottom:0;border-radius:inherit;background:linear-gradient(90deg,#38bdf8,#facc15,#f97316);transform-origin:0 50%}' +
'.wfst-spin{transform-box:fill-box;transform-origin:50% 50%}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
var SUN_RAYS = '';
for (var r = 0; r < 8; r++) {
var a = r * Math.PI / 4;
SUN_RAYS += '<line x1="' + (24 + Math.cos(a) * 13).toFixed(1) + '" y1="' + (24 + Math.sin(a) * 13).toFixed(1) +
'" x2="' + (24 + Math.cos(a) * 18).toFixed(1) + '" y2="' + (24 + Math.sin(a) * 18).toFixed(1) + '"/>';
}
function sun(cx, cy, s) {
return '<g transform="translate(' + cx + ' ' + cy + ') scale(' + s + ') translate(-24 -24)">' +
'<g class="wfst-spin wfst-rays" stroke="#facc15" stroke-width="3" stroke-linecap="round">' + SUN_RAYS + '</g>' +
'<circle cx="24" cy="24" r="9" fill="#fde047"/></g>';
}
var CLOUD = '<path class="wfst-cloud" d="M14 36h22a8 8 0 0 0 0-16 11 11 0 0 0-21-2A9 9 0 0 0 14 36z" fill="#f8fafc"/>';
var DARK_CLOUD = '<path class="wfst-cloud" d="M14 30h22a8 8 0 0 0 0-16 11 11 0 0 0-21-2A9 9 0 0 0 14 30z" fill="#cbd5e1"/>';
var ICONS = {
sun: function () { return sun(24, 24, 1); },
partly: function () { return sun(17, 17, 0.75) + CLOUD; },
cloud: function () { return '<path class="wfst-cloud wfst-back" d="M26 24h14a6 6 0 0 0 0-12 8 8 0 0 0-15-1" fill="none" stroke="#cbd5e1" stroke-width="2.5" stroke-linecap="round"/>' + CLOUD; },
rain: function () {
var drops = '';
[15, 23, 31].forEach(function (x, i) { drops += '<line class="wfst-drop" x1="' + x + '" y1="34" x2="' + (x - 2) + '" y2="39" stroke="#7dd3fc" stroke-width="2.5" stroke-linecap="round" data-i="' + i + '"/>'; });
return DARK_CLOUD + drops;
},
storm: function () {
return DARK_CLOUD + '<path class="wfst-bolt" d="M26 28l-6 9h5l-3 8 9-11h-5l3-6z" fill="#facc15"/>';
},
snow: function () {
var flakes = '';
[15, 24, 33].forEach(function (x, i) { flakes += '<circle class="wfst-drop" cx="' + x + '" cy="36" r="1.8" fill="#fff" data-i="' + i + '"/>'; });
return DARK_CLOUD + flakes;
}
};
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function setup(el) {
var d = el.dataset;
var unit = d.unit !== undefined ? d.unit : DEFAULTS.unit;
var days = [].slice.call(el.children).map(function (li) {
return { name: li.textContent.trim(), icon: ICONS[li.dataset.icon] ? li.dataset.icon : 'sun', hi: +li.dataset.high || 0, lo: +li.dataset.low || 0 };
});
if (!days.length) return false;
var min = Math.min.apply(null, days.map(function (x) { return x.lo; }));
var max = Math.max.apply(null, days.map(function (x) { return x.hi; }));
var span = Math.max(1, max - min);
el.classList.add('wfst');
el.innerHTML = '';
if (d.title) {
var t = document.createElement('li');
t.className = 'wfst-title';
t.textContent = d.title;
el.appendChild(t);
}
var rowLi = document.createElement('li');
rowLi.innerHTML = '<ul class="wfst-row" style="margin:0;padding:0;list-style:none"></ul>';
var row = rowLi.firstChild;
el.appendChild(rowLi);
var cards = days.map(function (day) {
var li = document.createElement('li');
li.className = 'wfst-day';
li.setAttribute('aria-label', day.name + ': ' + day.icon + ', high ' + day.hi + unit + ', low ' + day.lo + unit);
li.innerHTML =
'<span class="wfst-name" aria-hidden="true"></span>' +
'<svg class="wfst-ic" viewBox="0 0 48 48" aria-hidden="true">' + ICONS[day.icon]() + '</svg>' +
'<span class="wfst-hi" aria-hidden="true"></span><span class="wfst-lo" aria-hidden="true"></span>' +
'<span class="wfst-range" aria-hidden="true"><i style="left:' + ((day.lo - min) / span * 100) + '%;width:' + ((day.hi - day.lo) / span * 100) + '%"></i></span>';
li.querySelector('.wfst-name').textContent = day.name;
row.appendChild(li);
var c = {
el: li, day: day, ic: li.querySelector('.wfst-ic'), hi: li.querySelector('.wfst-hi'), lo: li.querySelector('.wfst-lo'),
bar: li.querySelector('.wfst-range i')
};
setTemps(c, 1, unit);
return c;
});
el._wfst = { cards: cards, unit: unit, title: el.querySelector('.wfst-title'), vis: false, idle: [] };
if (!reduced()) hide(el);
return true;
}
function setTemps(c, p, unit) {
c.hi.textContent = Math.round(c.day.hi * p) + unit;
c.lo.textContent = Math.round(c.day.lo * p) + unit;
}
function hide(el) {
var s = el._wfst;
s.cards.forEach(function (c) {
anime.utils.set(c.el, { opacity: 0, x: '3em' });
anime.utils.set(c.ic, { scale: 0 });
anime.utils.set(c.bar, { scaleX: 0 });
});
if (s.title) anime.utils.set(s.title, { opacity: 0, x: '-1em' });
}
// Gentle looping motion for each icon while it is on screen.
function idle(el) {
var s = el._wfst;
s.idle.forEach(function (a) { a.pause(); });
s.idle = [];
var q = function (sel) { return [].slice.call(el.querySelectorAll(sel)); };
var rays = q('.wfst-rays');
if (rays.length) s.idle.push(anime.animate(rays, { rotate: 360, duration: 12000, ease: 'linear', loop: true }));
var clouds = q('.wfst-cloud');
if (clouds.length) s.idle.push(anime.animate(clouds, { x: [-1.2, 1.2], duration: 2400, ease: 'inOut(2)', loop: true, alternate: true, delay: anime.stagger(200) }));
q('.wfst-drop').forEach(function (d) {
var i = +d.getAttribute('data-i');
s.idle.push(anime.animate(d, { y: [-3, 6], opacity: [{ from: 0, to: 1, duration: 200 }, { to: 1, duration: 500 }, { to: 0, duration: 200 }], duration: 900, ease: 'in(2)', loop: true, delay: i * 280 }));
});
var bolts = q('.wfst-bolt');
if (bolts.length) s.idle.push(anime.animate(bolts, { opacity: [1, 1, 0.2, 1, 0.3, 1], duration: 2200, ease: 'linear', loop: true }));
}
function play(el) {
var s = el._wfst;
if (s.tl) s.tl.pause();
clearTimeout(s.wait);
s.pending = false;
hide(el);
var loop = el.dataset.loop !== undefined && el.dataset.loop !== 'false';
var hold = el.dataset.hold ? +el.dataset.hold : DEFAULTS.hold;
var tl = anime.createTimeline();
if (s.title) tl.add(s.title, { opacity: [0, 1], x: ['-1em', '0em'], duration: 500, ease: 'out(3)' }, 0);
s.cards.forEach(function (c, i) {
var at = 150 + i * 130;
var p = { v: 0 };
tl.add(c.el, { opacity: [0, 1], x: ['3em', '0em'], duration: 650, ease: 'out(4)' }, at)
.add(c.ic, { scale: [0, 1], rotate: [-30, 0], duration: 600, ease: 'outBack(2.4)' }, at + 220)
.add(p, { v: 1, duration: 700, ease: 'out(3)', onUpdate: function () { setTemps(c, p.v, s.unit); } }, at + 250)
.add(c.bar, { scaleX: [0, 1], duration: 600, ease: 'out(3)' }, at + 400);
});
var built = 150 + s.cards.length * 130 + 1000;
tl.call(function () { idle(el); }, 400);
if (loop) {
tl.add(s.cards.map(function (c) { return c.el; }), { opacity: 0, x: '-2em', duration: 420, ease: 'in(2)', delay: anime.stagger(70) }, built + hold);
if (s.title) tl.add(s.title, { opacity: 0, duration: 300 }, built + hold);
tl.then(function () {
s.wait = setTimeout(function () {
if (s.vis) play(el); else s.pending = true;
}, 300);
});
}
s.tl = tl;
}
window.animacious = window.animacious || {};
window.animacious.weatherForecastStrip = function (el) {
if (el && el._wfst && !reduced()) play(el);
};
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
var el = e.target, s = el._wfst;
s.vis = e.isIntersecting;
if (!s.vis) { s.idle.forEach(function (a) { a.pause(); }); return; }
s.idle.forEach(function (a) { a.play(); });
if (!s.started) { s.started = true; play(el); }
else if (s.pending) play(el);
});
}, { threshold: 0.2 });
document.querySelectorAll('[data-weather-forecast-strip]').forEach(function (el) {
if (el._wfst) return;
if (!setup(el)) return;
if (!reduced()) io.observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><ul data-weather-forecast-strip data-title="London">
<li data-icon="sun" data-high="24" data-low="15">Mon</li>
<li data-icon="partly" data-high="22" data-low="14">Tue</li>
<li data-icon="rain" data-high="18" data-low="12">Wed</li>
</ul>