Scroll Colour Shift
The page background fades to a new colour as each section scrolls in, and the text colour follows, so the whole page changes mood from one part to the next. Scroll back up and the colours fade back.
Scroll inside ↓
How to use it
- Paste the snippet just before
</head>. - Add
data-scroll-colour-shiftto the<body>, or to any wrapper around your sections. - Give each section a
data-bgcolour, and adata-fgtext colour where it needs one.
Options
Add any of these attributes to the same element.
data-bg | On each section: the background colour it brings in. |
data-fg | On each section: the text colour that goes with it. Optional. |
data-line | Where in the view a section takes over, from 0 (top) to 1 (bottom). Default 0.5. |
data-duration | How long each fade takes. Default 700 ms. |
Leave the sections themselves without a background so the colour shows through. Works inside scrolling boxes too.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Scroll Colour Shift: the page background fades to a new colour as each
// section scrolls in, and the text colour follows. Scroll back and the
// colours fade back too.
// Usage: <body data-scroll-colour-shift> (or any wrapper around the sections)
// <section data-bg="#fef3c7">…</section>
// <section data-bg="#1e1b4b" data-fg="#fff">…</section>
// </body>
// On each section: data-bg (the background it brings in), data-fg (text
// colour, optional).
// Optional on the wrapper: data-line (where in the view a section takes
// over, 0 top to 1 bottom, default 0.5), data-duration (the fade, in ms,
// default 700).
// Works inside scrolling boxes too: on a box that scrolls, or inside one.
var DEFAULTS = { line: 0.5, duration: 700 };
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function num(v, fallback) { return v !== undefined && v !== '' && !isNaN(+v) ? +v : fallback; }
function scroller(el) {
var own = getComputedStyle(el).overflowY;
if (own === 'auto' || own === 'scroll') return el; // the wrapper scrolls itself
for (var p = el.parentElement; p && p !== document.body; p = p.parentElement) {
var o = getComputedStyle(p).overflowY;
if (o === 'auto' || o === 'scroll') return p;
}
return null;
}
function setup(el) {
var line = Math.max(0, Math.min(1, num(el.dataset.line, DEFAULTS.line)));
var duration = reduced() ? 0 : num(el.dataset.duration, DEFAULTS.duration);
var sections = Array.prototype.slice.call(el.querySelectorAll('[data-bg]'));
if (!sections.length) return;
var box = el === document.body ? null : scroller(el);
var current = null, queued = false;
var baseFg = getComputedStyle(el).color;
function update() {
queued = false;
var top = 0, h = window.innerHeight;
if (box) { var b = box.getBoundingClientRect(); top = b.top + box.clientTop; h = box.clientHeight; }
var y = top + h * line;
// The last section whose top has passed the line takes over.
var pick = sections[0];
for (var i = 0; i < sections.length; i++) {
if (sections[i].getBoundingClientRect().top <= y) pick = sections[i];
}
if (pick === current) return;
var first = !current;
current = pick;
var to = { backgroundColor: pick.dataset.bg, color: pick.dataset.fg || baseFg };
anime.utils.remove(el);
if (first || !duration) anime.utils.set(el, to);
else anime.animate(el, { backgroundColor: to.backgroundColor, color: to.color, duration: duration, ease: 'inOut(2)' });
}
function onScroll() {
if (queued) return;
queued = true;
requestAnimationFrame(update);
}
update();
(box || window).addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll);
}
function init() {
document.querySelectorAll('[data-scroll-colour-shift]').forEach(function (el) {
if (el._sclr) return;
el._sclr = true;
setup(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><body data-scroll-colour-shift>
<section data-bg="#fef3c7">…</section>
<section data-bg="#bae6fd">…</section>
<section data-bg="#1e1b4b" data-fg="#fff">…</section>
</body>