Proposal: New events for overscroll and scrollend

#1

Problem

With CSS overscroll behavior property developers can prevent user-agent defined default boundary actions. Overscroll affordance (e.g. rubber-banding or glow effect), pull to refresh, and history swipe navigation are examples of default boundary actions.

Overscroll behavior property lets developers to prevent default aformentioned boundary actions. However in order to implement customized behaviors, they still need to register a combination of scroll, touch, wheel event listeners to find out when overscrolling has started and trigger their customized scroll boundary behavior. This approach has the following disadvantages/limitations:

  • Needs to be implemented for different methods of scrolling separately (touchscreen, wheel, keyboard, etc.)
  • If the developer wants to trigger overscroll animation at the end of the scroll gesture, touchend event listener can be used for touchscreen scrolling. However for other methods of scrolling (e.g. touchpad scrolling) this won’t be possible using the currently existing events.
  • In presence of a cross origin iframe the embedder that might get scrolled might not get the touchmove/wheel events at all.

Proposal

Dispatch the overscroll and scrollend events in addition to the scroll event to provide more states to the javascript code. Note that overscroll event needs a delta as while the user is overscrolling nothing changes (in terms of offsetX/Y) and hence the script will need the delta in the event to get more information.

Here is the full proposed API and specification.

#2

@smaug @BoCupp-Microsoft @travisleithead

Could you add other folks if you know they might be interested in this feature?

#3

@sahel-sh

#4

Do all the platforms give enough information to implement scrollend? Gecko has internally WheelOperationEnd event, but because of OS limitations (at the time it was added, which was quite long ago), it isn’t supported everywhere. And what does scrollend means in case of keyboard scrolling?

#5

I agree that this will be implementation dependent in some input modalities. For example in Chrome we used to have one begin, update, and end for every single wheel scrolling tick. But we added the scroll latching and now batch them with some timeout. But it is much more clear for something like a touch scrolling. We wanted to leave that detail up to the implementation for the time being. WDYT?

#6

@ChumpChief

#7

Couple questions to start, though I’ll need some time to digest further:

  • Are you generally envisioning that this is designed to be used with overscroll-behavior: none or contain? E.g. is the developer removing and taking over the responsibility for a reasonable overscroll-behavior, or are they augmenting the scrolling behavior (e.g. observing the distance overscrolled to decide whether to refresh content, but not altering the visual effect).
  • Is deltaX/Y the right values to provide? Most of the scenarios I’m thinking of want the total overscrolled distance I think (e.g. watch for when it passes 50px to pull-to-refresh, or stretch the rubber band by a function of the total distance overscrolled) rather than deltas.
  • Today in Chromium, reversing direction from an overscroll immediately starts panning the opposite direction. However, most pull-to-refresh style UI that I’ve seen instead wants to “relax” the spring first before starting into normal panning. Is this “relaxing” behavior intended to be a supported scenario, and if so how would a developer implement it (does it require a change in the default overscroll behavior first)?
#8

I agree with Navid that knowing when wheel scrolling has finished will be generally implementation dependent. However during touchpad scrolling on Mac, ChromeOS, and Windows (with precision touchpad) we can get this additional information from wheel events received from OS. Thus similar to touchscrolling scrolling, touchpad scrolling on these platforms will have a clear end as well.

#9

Navid asked me if it’d be fine to move this repo to the WICG org.

Do folks on this thread agree that the use-cases this tackles are interesting ones, and it’s worthwhile to further explore them?

#10

1-scrollend event is likely to get used independent from overscroll-behavior in addition to cases that the developer wants to wait for the scroll gesture to finish before completing actions like history navigation or pull to refresh. Overscroll is more likely to get used with overscroll-behavior:none or contain unless for some reason a developer is interested in double handling. 2-I don’t have any preference between deltaX/Y or accumulatedDeltaX/Y. Developers can calculate either pair using the other pair. If you think that most use cases would be based on accumulated values we can send those instead of raw deltas. 3- Chromium handles local and non-local boundary actions separately: i.e. for pull to refresh and history swipe navigation which are non-local if the user triggers them, for the rest of the scroll sequence the events are not sent to the renderer and the overscroll controller “relaxes” the pull to refresh or history swipe navigation, thus no scrolling would happen during the rest of the current scroll sequence. and the action will be triggered at the end of the gesture sequence. You are right about the glow effect though. The animation is timer-based and changing scroll direction within the same scroll sequence might cause scrolling in the opposite direction instead of relaxing. Making the two cases consistent might improve the user experience. nzolghadr@ WDYT? If we want to support the relaxing behavior sending the accumulative delta makes more sense.

#11

It might be easier to land on API shape with some concrete shipping UIs that we are looking to enable on the web. For example, I’m wondering if the point we’re discussing about reversal (#3) can/should be implemented by the UA to track the total overscrolled delta and return to normal scrolling, or if the app should be driving the handoff between the customized overscroll behavior and native scrolling (which would require some new/different API shape).

I’m looking at the iOS Mail app and the iOS Outlook app as examples that I think do a good job of reversing the action and allowing entry into normal panning. By contrast, mobile.twitter.com does not allow reversal into normal panning if an overscroll is started.

#12

@ChumpChief Right now the plan was for the event to be very passive not something along the line of full scroll customization. Regarding reversal of the scroll we intentionally left it up to UA to decide whether it wants to gradually reduce the overscroll delta or immediately send a delta=0 and start scrolling. I feel like either way the app should behave reasonably if they react to the given delta on the overscroll events. Either way they should never rely on how much a delta is different from the last delta they got. WDYT?

#13

I proposed a similar API in pointerevents#211, which would provide a touch-action value that preferred browser scrolling, but dispatched PointerEvents if scrolling was not happening.

I prefer that model over this one for 2 reasons:

  1. More event types make the web platform more complex. Getting touch right is already really hard. You have to remember to:

    • set touch-action: none,
    • handle each of {Pointer,Mouse,Touch}Events, and
    • call preventDefault() on dragstart events that happen during the capture phase.

    touch-action is in CSS while event listening is in JavaScript, so you have to touch two separate stacks to handle touch. Getting touch-action wrong breaks browser scrolling, so it’s not a good candidate for a library to handle for you (leaves more complexity for the developer).

    Making the PointerEvent API work in more places seems like a better approach than making developers learn to handle yet another edge case with yet another API.

  2. The down:move*:up|cancel state machine is relatively easy to reason about, compared to a stateless API like scroll. Developers end up relying on timers to infer state from stateless APIs. It’s unclear to me when scrollend is expected to be dispatched. It’s probably either at the start or end of an overscroll, which means the other state (start|end) is missing.

Overloading scrolling as a proxy for touch is a hack that native developers use because scroll easing is a black box in native UI APIs, and they often want flinging their UI to feel consistent with flinging a scroll pane. A common overscroll interaction is pull-to-reload. Do mice/trackpad users expect to be able to overscroll to reload? I’m skeptical that they do.

Given the choice between minting a bespoke API for overscroll versus extending the current PointerEvent API to include overscroll, I’m inclined to extend PointerEvents.

#14

For those of you who don’t know me, I build prototypes for the Material Design team at Google.

I’ve been building gestural prototypes on the Web for years, and I’m the author of the Material Motion library.

One of those prototypes is one of the backdrop pattern that allows the front layer to contain a scroll pane. The user can always drag the front layer by grabbing the subheader. If the backdrop is open, there’s a scrim over the scroll pane that disables scrolling, and makes the whole front layer draggable. When the backdrop is closed, the scroll pane prefers scrolling. If its scroll position is at 0 and the user overscrolls, the front layer drags instead. I’ve accomplished this with some hacky TouchEvent code. I’d have preferred to use PointerEvents for consistency and cross-device portability.

That prototype currently relies on an internal API, so it isn’t publicly accessible. If it would benefit this discussion, I could make a new version with hardcoded data to give you a feel for one interaction that an overscroll solution would enable.