Urgent.News

What's breaking now, across thousands of outlets.

Tech

Auto-scroll is not `window.scrollTo`

The document you measure is not always the document you see This is the last post in a five-part series about recording a page while it moves. Capture models and clocks do not matter if the page does not move the way you think it does. window.scrollTo(0, y) is the demo. Production sites are scroll-snap, Lenis-class smooth engines, overflow-owned layouts, and WebGL scenes that never exposed…

The document you measure isn't always the one you see. This marks the final installment in a series about recording a page as it scrolls. The window.scrollTo method is the demonstration used for this purpose. Many production sites employ scroll-snap, smooth engines like Lenis, layouts controlled by overflow properties, and WebGL scenes that hide scroll height.

The default value for document.documentElement.scrollHeight isn't a guarantee; it's just a default. Some marketing pages position the real scroll on an inner div with overflow set to auto. Animation libraries often embed a hidden scroller within the tree and map it onto transforms. Canvas scenes interact with wheel events, drawing a camera without revealing scrollHeight.

The window's scrollY barely changes when auto-stop is triggered by "near scrollHeight - innerHeight," either immediately or never. I detect overflow boxes and control their scrollTop, alongside adjusting the window's scroll position. This over-triggering occurs on pages with small scrollable code samples. A short article featuring a modal or widget can interfere with the "page" height.

The safer approach is to trust a container covering most of the viewport. However, some owners are still misdetected, and widgets are still inadvertently captured. This is a heuristic. Wheel events must mimic user behavior; libraries check for hover before responding to wheel events. Dispatching events only on the document isn't sufficient.

I target the element under the pointer (or simulate a pointer), emit a wheel event, and also engage the first canvas if the pointer is on an overlay. The event path of an isolated-world scrollTo never reaches that canvas's event path. Smooth-scroll engines maintain their target within a requestAnimationFrame loop. After reaching the top, they interpolate back to their original position.

The initial seconds of a capture show the page sliding away from y = 0. The counter freezes: continuous forcing of top is executed until the preparation delay ends, then it is released. At the bottom, scrolling stops sending deltas. Footer widgets and WebGL listeners can bounce if the ticking continues. Aligning the engine's internal offset to the actual scrollY is a final adjustment, not an additional scroller.

Scroll snap is a feature until it becomes a battle. CSS scroll-snap will compete with linear scrollBy on each frame. The page appears to be possessed: asking for two pixels may result in jumping to a different section, asking again may cause it to jump back. For ordinary auto-scroll, I disable scroll-snap-type on the entire tree.

The recording represents a continuous motion. That is the objective. For site-based structures, this disabling action is the bug. These pages are the real source of the snap. I employ a separate driver: a solitary synthetic wheel impulse, followed by periods of silence, then another impulse. Snap remains active. If the wheel event produces no DOM progress, a deferred viewport-sized scrollBy runs on a subsequent frame - never in the same frame as the wheel event, or you will apply two scrolls in one go.

If multiple impulses result in no DOM movement, I cease the process. A capped impulse count exists to prevent a malfunctioning page from running indefinitely. This second driver is not a 'smarter linear scroll'; it is a distinct mechanism: wait → impulse → wait. Integrating it into the rAF pixel loop creates both snap interference and random jumps.

Height can be misleading, both upwards and downwards. Lazy loading can increase scrollHeight after the 'finished' signal. If the stop mechanism is already active, I refrain from canceling it - pages that continuously append content never conclude. If we are still within the settle window, I unlock and continue. A small threshold is used to disregard animation jitter that isn't actual growth.

Banners can shrink the document, potentially creating a false sense of a bottom that's never truly reached. Unlocking is necessary if we are no longer near the new bottom. The concept of "near the bottom" as a percentage fails for long pages; a few pixels of footer represent a tiny fraction of the page, yet it's still unaccounted for.

I utilize a tight pixel tolerance, followed by a stagnation filter: if we request a movement but scrollY doesn't change, the browser might have encountered a wall due to overestimated height (sticky chrome). Snap to max and then trust the wall. Smart pacing - pausing on headings, waiting for in-view images to decode - is additional policy layered on top of this, not a substitute.

A real document height is still essential. For canvas-only scenes, there's nothing to attach to. Blind pages: do not auto-stop. If the measurable scroll range is minimal compared to the viewport, the page is considered blind. Constant speed, wheel events for the WebGL path, and no automatic end are employed. The user initiates a stop; this isn't a missing feature.

Auto-stop would abruptly halt a three-minute WebGL journey at second zero because the maxScroll was only 40 pixels. Impulse mode utilizes the same blindness test: no 'we reached the bottom' indication, as there is no reliable bottom to trust. It still halts on no-progress or a maximum impulse count, preventing a dead page from spinning aimlessly.

Cinema cannot save this scenario. A map requires a height; a mere 40-pixel document yields a poster of the initial screen, followed by a camera without a destination. I will not claim to drive cross-origin iframes. They represent separate documents, and I do not inject into every frame. If the product resides within an embed, outer-page auto-scroll is merely an act.

Vertical overflow is the sole concern; the driver operates along the vertical axis. Hidden tabs can halt painting. The capture goes black, even though scrolling may still be 'working' in a dimension you cannot perceive. Overlay cleanup that hides fixed nodes must preserve canvases, main landmarks, and near-fullscreen layers. Otherwise, you inadvertently 'clean' the WebGL scene you intended to record.

The series, summarized: a screenshot is akin to a scan. Live video corresponds to a compositor diary. Cinema represents a camera focusing on a poster. Auto-scroll is a negotiation with the entity that truly controls the scroll. ScrollFlow was developed as this negotiation on the user's machine. Despite facing challenges from iframes, hidden tabs, sideways layouts, and scenes without visible scrollbars, these limitations are inherent to the specification, not the backlog.

Feedback is welcome; if a site disregards window.scrollTo, I would appreciate a compatible engine. I have transformed this experiment into ScrollFlow, a Chrome extension enabling local auto-scroll captures of websites. It is freely available, and I would value feedback from developers encountering complex pages.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

More from Wednesday 2 September →