Zamora's Blog

Scroll to an element in Vue

December 13, 2021

Introduction

Scrolling to an HTML element in Vue is fairly straightforward but doing so with an offset is not. This guide will discuss how to do both. Please note examples below will be using Vue.js version 2.

Create a scrollable container

<div
  id="app"
  style="height:300vh; background:green; display: flex; flex-direction: column; justify-content: space-between; align-items: center;"
>
  <button style="font-size: 60px; cursor:pointer;" @click="scrollToBottom">
    Scroll to Bottom Element
  </button>
  <h1 ref="bottom">Bottom</h1>
</div>

The markup above uses height: 300vh; to create a scrollable container. The h1 element should be below your screens view. Adding ref="bottom" will expose the h1’s DOM methods that would normally be available if we were not using Vue.

Trigger scrolling

const app = new Vue({
  el: "#app",
  data: {},
  methods: {
    scrollToBottom() {
      this.$refs["bottom"].scrollIntoView({ behavior: "smooth" })
    },
  },
})

In the scrollToBottom method this.$refs['THE_ELEMENT_YOU_WANT_TO_TARGET] instead of document and a selector is used to call scrollIntoView (Passing {behavior: "smooth"} as options allows for smooth scrolling). Clicking the button now will scroll to the bottom element.

Adding an Offset

So far scrolling in Vue is pretty simple with the scrollIntoView method. The problem is that if there is an element like a nav bar or header that push down the element that you are trying to scroll to, it’s going to get cut off. scrollIntoView has several options we can pass it but none of them will allow for offsetting the position that it scrolls to. In the code below pressing the “Scroll To Element with No Offset” button demonstrates an offset issue caused by a header element.

<header
  style="position: fixed; height: 50px; background: purple;width: 100%;text-align:center"
>
  HEADER
</header>
<div
  id="app"
  style="height:300vh; background:green; display: flex; flex-direction: column; align-items: center;padding-top: 50px;"
>
  <button
    style="font-size: 1em; cursor:pointer; background: red;"
    @click="scrollToElement"
  >
    Scroll to Element with No Offset
  </button>
  <button
    style="font-size: 1em; cursor:pointer; background: green;"
    @click="scrollToHiddenElement"
  >
    Scroll to Element with an Offset
  </button>
  <div style="position: relative">
    <div
      ref="hiddenElement"
      style="height: 50px; position: absolute; top: -50px; pointer-events: none;"
    ></div>
    <h1 ref="element">Element You Want To Scroll To</h1>
  </div>
</div>
const app = new Vue({
  el: "#app",
  data: {},
  methods: {
    scrollToElement() {
      this.$refs["element"].scrollIntoView({ behavior: "smooth" });
    },
    scrollToHiddenElement() {
      this.$refs["hiddenElement"].scrollIntoView({ behavior: "smooth" });
    }
  }
});

One way to handle this issue would be to use Window.ScrollTo() instead of scrollIntoView. If you have the access to the Window object this works well. The code example above is a good workaround if you can’t access the Window object. The h1 element is wrapped in a parent element which is positioned relative. This allows for hiding a positioned absolute element above the h1. This “hidden” element’s top and height styles are set to how much of an offset is needed without making the page look visually taller to the user. Then by adding ref="hiddenElement" to the “hidden” element, scrollIntoView will scroll to it. But to the user, they will be scrolled to their desired element without having it cut off. Clicking the “Scroll to Element With Offset” button above will demonstrate this.

Conclusion

In Vue.js use a ref andscrollIntoView to scroll to an HTML Element. But scrollIntoView doesn’t have options for an offset. Instead use Window.ScrollTo() for an offset if you have access to the Window Object. If not use a ref and scrollIntoView to scroll to a positioned absolute element that is the same size as the offset you want.


© Andrew Zamora 2023