Is it possible to handle scroll events in Elm without the use of ports?

I have this Ellie example where I implemented a sticky menu. Is it possible to modify this example to not use ports?
Is there a simpler way to do it (one that doesn’t involve two messages)?

1 Like

Maybe just position sticky in CSS could do? (If you can live with the browser support, which is not bad) it also degrades gracefully to non-stickiness if it is not supported.

See and the example there.

For this particular usecase, that would work for a nav that doesn’t change but, how would you change the background color once the nav reaches top?

Then you definitely need to use ports with scroll events right now like you were doing, unfortunately. Until we have a onScroll subscription parallel to onResize.

I would set it up in JS with async throttling for better perf (see example), to avoid potential performance issues. Scroll and resize events can trigger rather quickly.

Very similar to what you are already doing.

I have a couple of helpers in a gist here:

I believe (hope) they should work well.

If you don’t necessarily need your scroll to happen on <body>, you can maybe create a wrapper <div> and attach a scroll event to that:


#app {
      margin: 0;
      padding: 0;
      width: 100vw;
      height: 100vh;
      overflow: auto;


onScroll : msg -> Attribute msg
onScroll msg = on "scroll" (Json.succeed msg)

wrapView : Html Msg -> Html Msg
wrapView actualContents =
    div [ id "app", onScroll SyncViewport ] [ actualContents ]

updateViewport : Viewport -> Cmd Msg
updateViewport previous =
    Browser.Dom.getViewportOf "app"
        |> Task.onError (\_ -> Task.succeed previous)
        |> Task.perform UpdateViewport


Thank you!

I have updated the example to use this solution.

1 Like

Oh very nice! I missed that on the MDN docs, it is a nice workaround.

I tried the Ellie on an iOS device and with two or three flicks I generated 200 messages and the corresponding 200 getViewportOf calls too. It was pretty janky scrolling, probably letting css with position sticky handle the position, using debounced scroll events for toggling the menu style, and HTML.Lazy to avoid renders would give the best UX.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.