Skip to content
Behruz BekDigital Lab·Dubai

Scroll reveals that cannot strand your content

Entry animations start at opacity 0 and wait for an observer. If it never fires, the page is blank forever.

Published

24 Aug 2026

Type
Technical note
Status
live

The question

Nearly every "fade in on scroll" tutorial does the same thing: set opacity: 0, watch with an IntersectionObserver, set opacity: 1 when the element enters view.

What happens when the observer never fires?

Setup

A page of reveal-on-scroll sections, opened in a background tab that the browser is not compositing — the same situation as a tab restored on launch, or a device under memory pressure.

Result

The page loaded perfectly. HTML present, CSS applied, no errors in the console.

And completely blank.

IntersectionObserver does not fire for a tab that is not painting. The content was there the whole time, sitting at opacity: 0, waiting for a callback that never came. Nothing in the code review would show this — the code is correct. The failure is environmental.

Learned

Two rails, and both earn their place:

/* 1. Entry styles only exist when scripting does */
@media (scripting: enabled) {
  [data-reveal] { opacity: 0; }
}

With JavaScript off, or in a browser without the scripting feature, the rule never applies and everything renders normally. No blank page, no polyfill.

/* 2. A timer that does not care why the observer stayed quiet */
const failsafe = setTimeout(() => setRevealed(true), 2500);

If the observer fires first, the timer is cleared. If it never fires, the content appears anyway, two and a half seconds late.

The principle generalises past animation: decoration must never be load-bearing. If the only path to visible content runs through an effect, a listener, or a callback, that path is a single point of failure for the entire page.

Tagged
  • animation
  • accessibility
  • css
  • web-development
Related
Next

Need something built along these lines?