This proposal is an early design sketch by Blink Interactions Team to describe the problem below and solicit feedback on the proposed solution. It has not been approved to ship in Chrome.
- Blink Interactions Team
Web developers currently have no way to know when a programmatic smooth-scroll has completed. A solution to the problem that has already been resolved in the CSS WG: make the programmatic scroll methods return Promise
objects that get resolved on scroll completion. This explainer provides details of the solution, for spec updates and implementation.
We have six scroll methods available through both Element
and Window
interfaces. These methods return immediately with the value undefined
, which was fine during the early days of the web when scroll was assumed to be instant. This behavior no longer seems adequate from a web developer's perspective today: there is widespread support for smooth-scroll
(see browser_compatibility for the CSS property), and it is not easy for the developers to determine when a particular call for a smooth-scroll has completed.
This explainer elaborates how to make those methods return Promise
objects to solve this problem, as per the CSS WG resolution in w3c/csswg-drafts#1562.
- Alternatives to returning
Promise
objects. - Details of smooth-scroll behavior or chaining of nested scrollers.
Imagine a custom scroller that relies on programmatic scroll and that its buttons get disabled while a smooth-scroll is ongoing.
The CSS WG discussion at w3c/csswg-drafts#1562 resolved that the scroll methods in Element
and Window
would return Promise
objects (instead of undefined
). For that we would modifiy the IDL for Element
as follows:
partial interface Element {
Promise<undefined> scrollIntoView(optional (boolean or ScrollIntoViewOptions) arg = {});
Promise<undefined> scroll(optional ScrollToOptions options = {});
Promise<undefined> scroll(unrestricted double x, unrestricted double y);
Promise<undefined> scrollTo(optional ScrollToOptions options = {});
Promise<undefined> scrollTo(unrestricted double x, unrestricted double y);
Promise<undefined> scrollBy(optional ScrollToOptions options = {});
Promise<undefined> scrollBy(unrestricted double x, unrestricted double y);
}
We would need a very similar change in the IDL for Window
too.
This change would allow developers do things very easily at the completion of a scroll, like this:
element.scrollTo(0, 100).then(() => {
// Do something at the end of the scroll.
});
or this:
await element.scrollTo(0, 100);
// Do something at the end of the scroll.
For the use-case above, disabling a button during programmatic smooth-scroll would be as simple as:
button.onclick = async () => {
button.classList.add("dimmed");
await container.scrollTo(0, 0);
button.classList.remove("dimmed");
}
Here is a demo (the button dims only when the feature is enabled).
To maintain the backward compatibility for the scroll methods, we would avoid rejecting the returned Promises (as much as possible). This is because unhandled Promise rejections are treated as exceptions, which could fail any JS callers that assume that those methods succeed unconditionally.
To be precise about the Promise rejection behavior, if a scroll is not needed because the scroll position is already correct, the returned Promise would be resolved immediately. If an ongoing programmatic scroll is interrupted by either a user gesture or by another invocation of a programmatic scroll, at the moment of the interruption the (first) scroll would be considered complete and the Promise would be resolved.