[csswg-drafts] [cssom-view][proposal] beforescroll event (#4172)

hdodov has just created a new issue for https://github.com/w3c/csswg-drafts:

== [cssom-view][proposal] beforescroll event ==
My first issue here. I'll be making a suggestion for [CSSOM View Module Events](https://www.w3.org/TR/cssom-view/#event-summary).

# Problem

We have solid control over various input events. For keyboard, we have `keydown`, `keyup`, `keypress` (deprecated). For mouse, we have `click`, `dblclick`, `mouseup`,`mousedown`... We don't have great control over scroll, though.

The `scroll` event is useful for implementing functionality that responds to scrolling, but is too limited for more advanced use cases. It's emitted _after_ the actual scrolling has taken place and therefore doesn't provide much control. You can't `preventDefault()` and prevent scrolling and if you use `document.scrollingElement.scrollTop` for scroll-based animations, for example, they can be glitchy since they would lag one frame.

Touch events and the `wheel` event might be used as an alternative. They fire before scrolling has taken place, but they don't fire continuously. They can only be used to know when scrolling is expected to start, which is not very helpful.

# Solution

A middle ground between the `scroll` and `wheel` events is what's needed. My suggestion is to have the `beforescroll` event which:

- is fired right before the element is actually scrolled
- has `deltaY`/`deltaX` properties, similar to `wheel`
- has `defaultPrevented` which prevents scroll

The delta properties specify with how much pixels the element will be scrolled in the current event loop. Or, in other words, `element.scrollTop + event.deltaY` in the `beforescroll` event should be equal to `element.scrollTop` in the `scroll` event. This is useful because:

- it allows you to predict the next scroll position of the target element, fixing the lag issue with `scroll` mentioned above
- you can easily know the difference in scroll between frames, accounting for the various animations/easings of UAs, without having to compare `scrollTop` between the `scroll` event emissions (which also lag one frame)

You can call `preventDefault()` to prevent scrolling, but **not** any following `beforescroll` events. In Chrome, this code:

```js
var delta = null
window.addEventListener('scroll', function (e) {
  var st = document.documentElement.scrollTop
  console.log(st - (delta || st))
  delta = st
})
```

...would log similar to the following after a single `wheel` event with `deltaY` of `100`:

```
0
5
7
10
13
14
15
12
10
8
4
1
```

Here's a [fiddle](https://jsfiddle.net/hdodov/5tvrc3u0/). With `beforescroll`, you should be able to do this:

```js
window.addEventListener('beforescroll', function (e) {
  console.log(e.deltaY)

  // animate some element with deltaX/deltaY
  e.preventDefault()
})
```

...and the scroll target should not be scrolled, while the logs should read:

```
5
7
10
13
14
15
12
10
8
4
1
```

Since the actual scroll was prevented, `scroll` events should not fire after the `beforescroll` handler.

# Why

Having this event would allow developers to implement advanced behaviors based on scrolling accurately. It would also give a reliable way to prevent scrolling, which is currently not easily achievable. You could prevent scrolling with the mouse wheel by calling `preventDefault()` on the `wheel` event, but you can still scroll with the scrollbar, arrow keys, `scrollTo()` or even clicking a link with a hash.

Use cases:

1. If the site visitor scrolls to a section with horizontal overflow, the developer could use `beforescroll` to prevent vertical scrolling and use the delta properties to scroll that section horizontally. When the section is fully scrolled, vertical scroll is no longer prevented and the user can continue scrolling the site. This is an emerging UX pattern for carousel-like content.
2. Better "scroll hijacking." Even though that's considered bad practice, `beforescroll` could allow for better hijacking which, in the right hands, can improve UX. For example, while the site visitor scrolls through a section, the developer could use `beforescroll` to prevent scrolling, optionally animate something inside the section with `deltaY`, and use `document.scrollingElement.scrollTop += Math.ceil(event.deltaY / 2)`. This would still scroll the page vertially, with the UA's expected scroll easing, only at half the speed. This way, the developer can easily implement dynamic visuals for a content section and then emphasize them by directing the user's attention with slower scrolling.
3. Similar to the previous use case, the developer could speed up (instead of slowing down) the scrolling of a page that has more whitespace as a part of it's design.
4. For use cases where you don't want to disturb the natural page scrolling, you can still use `beforescroll` to implement scroll-triggered animations via the delta properties. This would be easier and more accurate compared to manually measuring delta with a `scroll` handler.

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/4172 using your GitHub account

Received on Monday, 5 August 2019 07:14:27 UTC