v150 · Web APIs · Scrolling

Programmatic scroll promises

Chrome 150 makes element.scrollTo(), element.scrollBy(), and element.scrollIntoView() return Promise<void> when called with behavior: 'smooth'. The promise resolves when the scroll animation completes, enabling await-based scroll sequences and eliminating the need for scroll event listeners or arbitrary timeouts.

concepts

  1. Scroll Promise Demo

    Interactive buttons that await scroll completion before reporting done. Compare with the old approach using scroll events. Shows how the promise correctly resolves after the smooth scroll animation ends.

  2. Scroll Sequence

    Chains multiple smooth scrolls using await — scroll to item 1, wait, then scroll to item 2, wait, then back. Without promises, this required fragile setTimeout guesses or complex scroll-end detection.

  3. Parallel Scroll Race

    Two scrollable lanes, scrolled simultaneously. Promise.all() waits until both finish before triggering a shared action — a pattern that previously required ad-hoc counter state or dual event listeners.

  4. Lazy Reveal

    "Load more" appends new feed items hidden, scrolls to them, and only after the promise resolves fades them in. The reveal animation is sequenced on scroll completion — no setTimeout guess needed.

  5. Virtual Tour

    A guided 5-stop virtual tour that uses await viewport.scrollTo({ behavior: 'smooth' }) to visit each stop in sequence — scroll, wait for animation to complete, show arrival callout, pause, advance. An event log timestamps each scroll start and arrival. Without promises, the pause timing was a fragile setTimeout guess.

  6. Interrupted scroll promise

    When a smooth scroll is interrupted — by a second scrollTo() or a user gesture — Chrome 150 resolves the promise at the new settled position, not rejects. Try both interruption sources and confirm the await chain continues without throwing, regardless of where the scroll stopped.

why it shipped

Smooth scrolling via scrollTo({ behavior: 'smooth' }) starts an asynchronous animation, but the call returned undefined — there was no way to know when it finished. Common workarounds were: using setTimeout with an estimated duration (fragile), or watching the scroll event with an idle timer (complex). Neither handled cases where the user interrupted the scroll. Chrome 150 returns a proper Promise that resolves when the scroll settles, including interrupted scrolls that settle at a new position.

the API

// Before Chrome 150: returns undefined
element.scrollTo({ top: 500, behavior: 'smooth' });

// Chrome 150+: returns Promise<void>
await element.scrollTo({ top: 500, behavior: 'smooth' });
console.log('Scroll complete');

// Chain scroll sequences
await element.scrollTo({ top: 0, behavior: 'smooth' });
await element.scrollTo({ top: 500, behavior: 'smooth' });
await element.scrollTo({ top: 250, behavior: 'smooth' });

// scrollIntoView also returns Promise
await heading.scrollIntoView({ behavior: 'smooth', block: 'start' });
console.log('Heading is now in view');

// Instant scroll still returns Promise (resolves immediately)
await element.scrollTo({ top: 0, behavior: 'instant' });

// If scroll is interrupted, promise resolves at new settled position
// (does not reject)

references

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗