-
Notifications
You must be signed in to change notification settings - Fork 746
Description
My first issue here. I'll be making a suggestion for CSSOM View Module Events.
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 towheel
- 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 thescroll
event emissions (which also lag one frame)
You can call preventDefault()
to prevent scrolling, but not any following beforescroll
events. In Chrome, this code:
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. With beforescroll
, you should be able to do this:
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:
- 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 a UX pattern for carousel-like content. - 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 usebeforescroll
to prevent scrolling, optionally animate something inside the section withdeltaY
, and usedocument.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. - 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.
- 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 ascroll
handler.
Edit: This event would also open possibilities for carousel libraries on mobile. They could use overflow:auto
and control the behavior with beforescroll
, instead of setting overflow:hidden
and manually scrolling the target by monitoring touch events.