vuejs3refvue-options-api

How can I toggle visibility and scroll to a target section in one click in Vue.js?


When switching between sections I want to toggle between hide / show and scroll the target section into view.

This code works but it takes two clicks instead of one to complete the process: first click toggles visibility and it takes a second click to actually scroll the target element into view. Try here.

Why is that? How to solve?

Furthermore: advice on how to accomplish this without a separate show variable for each section and without having to pass source section ref is also welcome.

Vue.js:

<template>
  <section id="1" ref="1" v-show="show1" style="margin-bottom: 500px">
    <p>Section 1</p>
    <button @click="showScrollInto('1', '2')">Goto section 2</button>
    <button @click="showScrollInto('1', '3')">Goto section 3</button>
  </section>
  <section id="2" ref="2" v-show="show2">
    <p>Section 2</p>
    <button @click="showScrollInto('2', '1')">Goto home</button>
  </section>
  <section id="3" ref="3" v-show="show3">
    <p>Section 3</p>
    <button @click="showScrollInto('3', '1')">Goto home</button>
  </section>
</template>

<script>

export default {
  name: "App",
  data() {
    return {
      show1: true,
      show2: false,
      show3: false
    }
  },
  methods: {
    showScrollInto(from, to) {
    switch(from) {
      case "2":
        this.show2 = false
        break
      case "3":
        this.show3 = false
        break
      default:
        this.show1 = true
      }
    
    switch(to) {
      case "2":
        this.show2 = true
        break
      case "3":
        this.show3 = true
        break
      default:
        this.show1 = true
      }
      this.$refs[to].scrollIntoView({ behavior: 'smooth'})
    }
  }
}

</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
section {
  height: 400px;
  border: solid 1px black;
}
</style>

Solution

  • After you have changed the data controlling visibility, the DOM still needs to update. When you call scrollIntoView() immediately, there is still no node to scroll to.

    You can wait until the update is finished using nextTick():

    await this.$nextTick()
    this.$refs[to].scrollIntoView({ behavior: "smooth" })
    

    or using a callback:

    this.$nextTick(() => this.$refs[to].scrollIntoView({ behavior: "smooth" }))
    

    Now the scroll event happens after the update when the element is available.