Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Input range won't work in an scrollable div when you simulate a smartphone

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) Text

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.

like image 443
Adriano_Pinaffo Avatar asked Oct 10 '21 13:10

Adriano_Pinaffo


2 Answers

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>
like image 73
Oleg Barabanov Avatar answered Nov 15 '22 00:11

Oleg Barabanov


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.

like image 26
Aleksey S Avatar answered Nov 15 '22 01:11

Aleksey S