Saving and restoring scroll position

In my photo album (source code), try scrolling down to the bottom of the “Western US” album and clicking on a picture. Then click the “X” in the upper-right to go back to the thumbnails.

It puts you back at the top of the thumbnail page, not in the position where you left off. So I’d like to add a feature that saves & restores your position on the thumbnail page.

However, I’m struggling with a couple of things:

  1. How do I get the current scroll position? Is Dom.Scroll.y what I want, or will I have to use a port to call out to native JS?

  2. When do I grab the current scroll position? Is the update that moves you from the thumbnail page to the full-image page too late, do I have to track every scroll event before that update instead?

    1. In general, if I fire off a Cmd that queries the state of the page in an update, will it see the state just before or just after the corresponding re-evaluation of the view?


1 Like

I would actually go a different route and have clicking an image bring up the carousel/image viewer on top of the list of images, as a full-screen modal. When the user closes it they would be right back where they were, using the scroll position can be unreliable in cases where the user resizes the browser between pages (granted, this is a minor inconvenience).

If you do want to use Dom.Scroll.y you can chain the tasks together return a Cmd, which will tell you when it’s safe to transition to the next view. Check out this recent post for more info on chaining tasks

For your final question, I looked through the source, and it looks like the View function is given the updated model before any Cmds are executed.

The stepper there is the view rendered passed in the VirtualDom (

1 Like

Ah, chaining the tasks together is the insight I was missing – thanks!

I’ll consider your idea of a full-screen modal, too. That would have the additional benefit of not needing to replay the fade-in animation for all the thumbnails either.

In addition to @pateh’s answer, I should point out that the view rendering is queued before dispatching effects, however rendering currently happens asynchronously through requestAnimationFrame so may not have finished rendering by the time a command is executed.

The tasks exposed through the DOM module all handle this gracefully: they queue their callbacks using requestAnimationFrame. In practice, this means they will see the updated view.

When dealing with ports, the same can be achieved by using requestAnimationFrame in the port callback:

app.ports.somePort.subscribe(function (arg) {
  window.requestAnimationFrame(function () {
    // When code here is triggered, the view will have rendered with the `model`
    // returned from the `update` that also caused this port to be called.

Great additional info, thanks. I think my take-away is that it’s best to avoid depending on these kinds of low-level ordering details whenever possible, though, and the two-stage update seems like it’ll do that nicely:

Msg = RequestFullImage
    | GotScrollPosition Int -- the scroll position received
    | BackToThumbs Int --the scroll position to restore

Model = ViewingThumbs
      | GettingThumbScrollPos Image -- the image we'll show once we get the scroll position
      | ViewingFull Image Int -- int is the scroll pos we'll restore
1 Like