I am creating a web app for young children, where they can scroll a very wide picture horizontally. Young children are likely to swipe a touch screen wildly. I want to allow inertial scrolling (because it's fun) but limit it so that a child user will not scroll rapidly backwards and forwards to the very ends, missing all the good stuff in the middle.
During inertial scrolling, the mousewheel
event is generated. I had hoped to use event.preventDefault()
on this event, in order to stop the inertial scroll at a particular point. However, it seems that if you don't run preventDefault()
on the very first mousewheel
event in a particular action, none of the following events are prevented.
The code snippet below provides a demonstration. It contains an element which can be scrolled until its scrollLeft
value reaches 5000. The JavaScript contains a mousewheel
event listener which explicitly calls event.preventDefault()
when the value for scrollLeft
is greater than 2500.
I had hoped that this would stop inertial scrolling halfway. But no.
Run the snippet and then use your mouse pad (or touch screen) to swipe scrollable element to the left. You'll see that:
event.preventDefault()
have no effect.mousewheel
events continue to be emitted for up to 5 seconds (depending on the energy you use to swipe). They continue to be emitted even after the element has been fully scrolled to the left.However, if you scroll left past 2500, and then try to swipe back to the right, nothing will happen. The default action for the first mousewheel
event is prevented, so inertial scrolling never starts.
My question is: Is there a way to stop mousewheel
events from being emitted at all, after the scroll reaches a certain point? Bonus question: Is there a way to determine how much inertia there is in a particular swipe gesture?
I have a workaround that limit the value of scrollLeft
, but it means that the child will not be able to swipe again until the last mousewheel
event has been emitted.
const output = document.getElementById("output");
const container = document.getElementById("container");
container.addEventListener("mousewheel", mousewheel, false);
let startTime = 0
let timeOut = 0
function mousewheel(event) {
const time = getTime()
const scrollLeft = Math.round(container.scrollLeft * 10) / 10;
output.innerText = time + "s\n" + scrollLeft;
clearTimeout(timeOut)
timeOut = setTimeout(() => startTime = 0, 100)
if (scrollLeft > 2500) {
event.preventDefault();
return false;
}
}
function getTime() {
const time = + new Date()
if (!startTime) {
startTime = time;
}
return (time - startTime) % 10000 / 1000
}
body {
margin: 0;
height: 100px;
}
div#container {
width: 500px;
height: 100%;
background-color: green;
overflow: auto;
}
div#content {
width: 5500px; /* Allows scrollLeft up to 5000 */
height: 100%;
background-color: red;
clip-path: polygon(0 0, 100% 0, 100% 100%)
}
<div id="container">
<div id="content"></div>
</div>
<pre id="output">0</p>
I do not think that you can stop the browser from emitting events. That is why one has to use preventDefault
. Therefore, to answer your questions,
mousewheel
event had a wheelDelta
value which is larger the more inertia there is. It is important to notice that mousewheel
is deprecated. You should use the wheel
event. In this case, the event will have deltaX
and deltaY
values that you can use.With these factors in mind, you can easily build your own behavior. I have simplified the code to emphasize the strategy. Briefly, you set the center
of the picture and the interesting
area around that center. You always prevent the default behavior (highjack the event). If the scrollLeft
property is outside the interesting
area around the center
, you move the scroll depending on deltaX
(with an optional multiplier
). If it is inside the area, you can make the change in scrollLeft
much lower by dividing it. Notice that in the example the divisor is probably too large to emphasize the effect.
const output = document.getElementById("output");
const container = document.getElementById("container");
container.addEventListener("wheel", mousewheel, false);
const multiplier = 2;
const center = 2500;
const interesting = 500;
function mousewheel(event) {
event.preventDefault();
let dx = event.deltaX * multiplier;
if (Math.abs(container.scrollLeft - center) < interesting){
dx /= 25;
}
container.scrollLeft += dx;
}
body {
margin: 0;
height: 100px;
}
div#container {
width: 500px;
height: 100%;
background-color: green;
overflow: auto;
}
div#content {
width: 5500px; /* Allows scrollLeft up to 5000 */
height: 100%;
background-color: red;
clip-path: polygon(0 0, 100% 0, 100% 100%)
}
<div id="container">
<div id="content"></div>
</div>
<pre id="output">0</p>