Preventing Mobile Browser Scroll Snap from Remembering Position When Changing Views

305 Views Asked by At

During the development of my web app, I encountered a strange issue that is subtle and challenging to debug. This issue is particularly troublesome in terms of user experience and occurs exclusively on mobile browsers.

My website is composed of several views, with each view containing multiple "snaps" - full display sections that users can swipe up and down, similar to TikTok or Instagram Reels.

To implement this, I am using pure HTML and CSS:

html {
    scroll-snap-type: y mandatory;
    overscroll-behavior-y: none;
}

.section {
    position: relative;
    max-width: 100vw;
    max-height: 100vh !important;
    scroll-snap-align: start;
    overflow: hidden;
}
<div class="view">
    <div class="section"></div>
    <div class="section"></div>
    <div class="section"></div>
</div>
<div class="view"></div>
<div class="view">
    <div class="section"></div>
    <div class="section"></div>
    <div class="section"></div>
</div>

Only one view is visible at a time. I use Vue.js, so the DOM is updated dynamically, and there's only one .view element present. However, I encountered the same issue while using pure JavaScript and calling .show()/.hide() to manage displaying the desired view.

The problem is as follows:

  1. The user scrolls (using snaps) to a certain section of the current view.
  2. The view changes. The new view can, but does not necessarily have to, contain snap sections.
  3. The user lands at the same position they exited at in step 1. So, if they were at the third section, and the new view also has sections, the page will be positioned at the third one.

This behavior occurs even if there is a view without any scrolling in between. For example, the user is at view 1, section 3, goes to view 2 without sections, and then goes to view 3 with sections, and is placed at section 3 again.

Another interesting factor is that if the new view has fewer sections than the number of the section the user was at last time, they will start at the top.

I imagine this is somehow a desired behavior meant to provide a consistent UX for navigating through a website.

However, I need a solution to start at the top for each new view that is displayed to the user.

I would greatly appreciate any help in finding a way to deal with this issue.

EDIT Several answers mention scrollTo function. It does not work. No matter how it's called, it gets overwritten by the snap position.

4

There are 4 best solutions below

1
Aninsi Sasberg On

I tried recreating it on Chrome, as I am not able to test it on iOS Safari, and suspect that it has to do with you not having any sections in your 2nd container. As scroll-snap-align is set on the sections and not the containers it searches for the next scroll point to align itself to which is the first section in the 3rd container.

    html {
        scroll-snap-type: y mandatory;
        overscroll-behavior-y: none;
    }

    .section {
        position: relative;
        min-width: 100vw;
        min-height: 100vh !important;
        scroll-snap-align: start;
        overflow: hidden;
    }
<div class="container">
    <div class="section">1.1</div>
    <div class="section">1.2</div>
    <div class="section">1.3</div>
</div>
<div class="container section">2</div>
<div class="container">
    <div class="section">3.1</div>
    <div class="section">3.2</div>
    <div class="section">3.3</div>
</div>

1
M.R.Ciphered C.E.M.T On

Try using j-Query:

$('.container').on('show', function() {
  $(this).scrollTop(0);
});
html {
  scroll-snap-type: y mandatory;
  overscroll-behavior-y: none;
}

.section {
  position: relative;
  min-width: 100vw;
  min-height: 100vh !important;
  scroll-snap-align: start;
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
  <div class="section">1.1</div>
  <div class="section">1.2</div>
  <div class="section">1.3</div>
</div>
<div class="container section">2</div>
<div class="container">
  <div class="section">3.1</div>
  <div class="section">3.2</div>
  <div class="section">3.3</div>
</div>

and, overscroll-behavior-y is supported in safari 16.0 or newer

1
vals On

looking at the specs, it states:

Scroll snapping happens after all scroll operations including those initiated by script. When you are using APIs like Element.scrollTo, the browser will calculate the intended scroll position of the operation, then apply appropriate snapping logic to find the final snapped location. Thus, there is no need for user script to do any manual calculations for snapping.

So, I believe that the solution would be to get the first section element, and call

element.scrollTo(0, 0)

To make the scroll position to be at the top most position

1
Phil joseph On

The behavior you're describing where the user returns to the same scroll position when transitioning between views is indeed a result of browser behavior designed to provide a consistent user experience. To ensure that each new view starts at the top, you can programmatically reset the scroll position when the new view is displayed.

Here's a Vue.js-based solution to achieve this:

  1. In your Vue component that handles the view transitions, listen for the event when the view changes. You can use Vue's built-in $watch to detect changes in the active view.

  2. When the view changes, use JavaScript to reset the scroll position to the top of the view container. You can use the scrollTop property of the container element to achieve this.

Here's a sample implementation:


<template>
  <div>
    <!-- Your view content here -->
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeView: 1, // Initialize with the active view index
    };
  },
  watch: {
    activeView(newValue, oldValue) {
      // When the active view changes, reset the scroll position to the top
      if (newValue !== oldValue) {
        this.resetScrollPosition();
      }
    },
  },
  methods: {
    resetScrollPosition() {
      // Replace '.view-container' with the actual selector for your view container
      const viewContainer = document.querySelector('.view-container');
      if (viewContainer) {
        viewContainer.scrollTop = 0; // Reset scroll position to the top
      }
    },
  },
};
</script>

In this example, we use a watcher to detect changes in the activeView data property. When the active view changes, it calls the resetScrollPosition method, which resets the scroll position to the top of the view container.

Make sure to replace '.view-container' with the actual CSS selector that targets your view container.

This approach ensures that whenever a new view is displayed, the scroll position is reset to the top, providing the desired behavior you're looking for.