I have an input range
in a div
contained in an outer div
that is smaller than the inner div
. The final result is that my inner div
scrolls horizontally (because the outer dive has overflow: scroll
), and the input range
is its child.
To customize the range, I removed the appearance in CSS with appearance: none
. Now, here is what happens. When I check it in Chrome developer's tool (actually I use Brave, but I'm guessing it is the same in Chrome as I tested in Chromium, and it is the same) with the smartphone option active, 99% of the time if I try to move the range handle it moves the whole div
with it. Now, if I disable the smartphone option, it works just fine. Also, if I keep the smartphone option and remove the appearance: none
from CSS, it also works just fine, but my customizations to the range disappear. Does anyone know what is going on?
PS.: in Firefox, the input range
doesn't work as long as I keep the smartphone option on (no matter if I have the appearance property or not).
Here is an animated gif of what I mean:
First, I have the described above with the input range
with no appearance. It works fine, I can move the scrollable div
and move the input range
handle independently. Then I put the appearance: none
to the input range
(notice the formatting of the input range changes), now I can't move the input range
handle independently from the scrollable div
anymore. Finally, remove the appearance from the input range
, and everything goes back to normal (but my customizations are gone)
Here is the code, but you can only simulate by using the developer's tool with the smartphone option active, where you can simulate the touch.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta charset="UTF-8">
<style>
#outerframe {
height: 310px;
width: 300px;
border: 2px solid red;
padding: 8px;
margin: auto;
overflow: scroll;
}
#scrollarea {
height: 300px;
width: 400px;
border: 1px dashed green;
padding: 16px;
}
input[type=range] {
appearance: none;
background: linear-gradient(90deg, #aaa, #ccf1ff 30%, #ccf1ff 70%, #aaa);
outline: none;
opacity: 0.7;
border-radius: 4px;
height: 14px;
width: 200px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.5) inset, 0 -5px 4px rgba(0, 0, 0, 0.5) inset;
vertical-align: middle;
margin: 16px;
}
input[type=text] {
width: 50px;
}
</style>
<title>Testing range in scroll</title>
</head>
<body>
<div id="outerframe">
<div id="scrollarea">
<input type="range" id="rangescroll">
<input type="text" value="50">
</div>
</div>
<script>
scrollarea.firstElementChild.addEventListener('input', evt => {
evt.target.nextElementSibling.value = evt.target.value;
})
</script>
</body>
</html>
EDIT: FIX:
Oleg Barabanov, suggested to make the overflow: hidden
while moving the input handle (add a class for that on touchstart and removing the class on touchend). While that works I thought about a more elegant solution. I remembered about event.preventDefault() and how it allows us to cancel browser handling of subsequent events. The idea was, if I can allow one event, the input
event and block the rest (the scrolling event done by the browser automatically) then it could work.
This is the additional code I first tried for the input range:
document.querySelector('.scrollarea').firstElementChild.addEventListener("touchstart", (evt) =>
document.querySelector('.outerframe').classList.add("overflow-hidden");
evt.preventDefault();
});
It didn't work here (which is surprising for me as I successfully used that in the past to stop default events). But then I remembered that when I studied JS events there was another way to prevent default events and that was to return false for the event:
document.querySelector('.scrollarea').firstElementChild.addEventListener("touchstart", (evt) =>
document.querySelector('.outerframe').classList.add("overflow-hidden");
return false;
});
To be fair, that article mentions that return false
would only work for when the event is configured as an attribute like ontouchstart
. But I used with the addEventListener()
method and it also worked.
I hope it helps more people in the future.
Maybe this is not the best solution, but it seems to work. Just using the touchstart
and touchend
events + css overflow
:
scrollarea.firstElementChild.addEventListener("input", (evt) => {
evt.target.nextElementSibling.value = evt.target.value;
});
scrollarea.firstElementChild.addEventListener("touchstart", (evt) => {
outerframe.classList.add("overflow-hidden");
});
scrollarea.firstElementChild.addEventListener("touchend", (evt) => {
outerframe.classList.remove("overflow-hidden");
});
#outerframe {
height: 310px;
width: 300px;
border: 2px solid red;
padding: 8px;
margin: auto;
overflow: scroll;
}
#scrollarea {
height: 300px;
width: 400px;
border: 1px dashed green;
padding: 16px;
}
input[type=range] {
appearance: none;
background: linear-gradient(90deg, #aaa, #ccf1ff 30%, #ccf1ff 70%, #aaa);
outline: none;
opacity: 0.7;
border-radius: 4px;
height: 14px;
width: 200px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.5) inset, 0 -5px 4px rgba(0, 0, 0, 0.5) inset;
vertical-align: middle;
margin: 16px;
}
input[type=text] {
width: 50px;
}
.overflow-hidden {
overflow: hidden !important;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta charset="UTF-8">
<title>Testing range in scroll</title>
</head>
<body>
<div id="outerframe">
<div id="scrollarea">
<input type="range" id="rangescroll">
<input type="text" value="50">
</div>
</div>
</body>
</html>
It is not clear to me exactly why, but when you are in the mobile test view, disabling the height
and width
from #scrollarea
in dev tools fixes the problem. The #scrollarea
in the mobile view is handled by moving everything within the parameters.
Two other solutions, which avoid having to remove your parameters are setting position=fixed
or position=absolute
on #rangescroll
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With