Hi, after showing this issue on Slack I created this simple program to show what’s happening.
This shows a very basic scrollbar, with a handler represented by a div with yellow background. This handler should move by following the onMouseMove event.
When the onMouseMove event handler is triggered, the model (just an Int) should increment/decrement, depending on the direction of the offsetPos Y value.
BUT, as I move the mouse over the scrollbar I see its position goes back to 0, generating a “flickering” effect.
I hope someone can help me with this.
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Html.Events.Extra.Mouse as Mouse
type alias Model = Int
init : Model
init =
0
type Msg =
Increment
| Decrement
| MouseDown Mouse.Event
view : Model -> Html Msg
view model =
div []
[
div [ style "background-color" "darkgray"
, style "width" "20px"
, style "height" "200px"
, style "position" "relative"
, Mouse.onMove (MouseDown)
]
[ div [
style "background-color" "yellow"
, style "width" "100%"
, style "height" "20px"
, style "position" "absolute"
, style "top" (String.fromInt model ++ "px")
, Mouse.onDown (MouseDown)
] [ ]
]
, div [ style "font-size" "20pt"]
[ button [onClick Increment] [ text " + " ]
, text (String.fromInt model)
, button [onClick Decrement] [ text " - " ]
]
]
main =
Browser.sandbox { init = init, update = update, view = view }
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
MouseDown e ->
let
_ = Debug.log "e" e.offsetPos
y = Tuple.second e.offsetPos |> round
in
y
I made an Ellie of your problem, and added one line that makes the flickering go away but probably doesn’t solve your problem: https://ellie-app.com/rDd9T7gwVYTa1
This is what is happening. You have the grey tall bar. As a child of that bar, you have the yellow box that moves. The mousemove listener is on the grey bar element.
provides the offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node.
What is the target node? It’s the top-most element under the cursor. When you hover the grey bar, the grey bar is the target. You get the mouse position in pixels from the top of the grey bar. You then move the yellow box there. To be precise, the top of the yellow box is placed at the mouse pointer position. As you keep moving the mouse, sometimes you then actually hover the yellow box. Which makes it the target. And you’re going to hover the very top of it, which is 0 pixels from its top. That makes it jump up to the top. Which means that you hover the grey bar again, which moves the yellow box, and so on, resulting in flicker.
I don’t know what the solution is though. But now you should know what is happening
What I did in the Ellie was adding pointer-events: none; to the yellow box. It means you cannot hover or click it. Which makes it not flicker, but I see that you have Mouse.onDown on it, which is not going to work then.
(Note: This has little to do with Elm specific things, so when searching for information you might get a lot more search results if you search for plain JavaScript and CSS solutions that you can adapt.)
Here’s what I would do: On mouse down on the yellow box, store the mouse position where you started pressing, and add a mouse move listener on the entire page. On mouse move, compare the current mouse position and the starting position. That’s how much the yellow box should move (you need to clamp it though, so it doesn’t go outside the grey box). On mouseup, lock the yellow box in its new place, and remove the mouse move listener on the entire page again. (If you want to go the extra mile, you can handle what happens if you go outside the browser window while dragging, and maybe even release the mouse outside it.)
That has a couple of advantages over your current solution:
You can only start dragging on the yellow box, not anywhere (like you mentioned).
If you accidentally go outside the grey bar while dragging, that’s ok, the yellow box still moves.
If you mouse down near the bottom of the yellow box, it won’t jump when you start moving. In other words, we remember where inside the yellow box you “grabbed it”.
If you play around with the OS scrollbars you’ll notice they work like that.
You shouldn’t put pointer-events: none;. That was just the quickest way to demonstrate what was going on. With the “listen on entire document” approach, it shouldn’t matter what the current target under the mouse is – the only thing that we’re interested in is how many pixels we have moved since the drag start.
It works when you drag OUTSIDE of the scrollbar (over the blue background), but if you do it over the gray background it shows the original (position goes to 0) behavior.