Vue.jsで要素がスクロール画面に現れたらアニメーションさせる方法

Vue.js

Vue.js(Vue v3)で要素がスクロール画面に現れたらアニメーションさせる方法を紹介します。

IntersectionObserverを使います。

Vue.js(Vue v3)で要素がスクロール画面に現れたらアニメーションさせる方法

App.vue

IntersectionObserverをAbout.vueに導入したいとします。
まずは、App.vueでその他もコンポーネントを含めてtemplateに差し込みます。


<template>
  <Home></Home>
  <Portfolio></Portfolio>
  <About></About>
  <Qualification></Qualification>
  <Services></Services>
  <Projects></Projects>
  <Contact></Contact>
  <Footer></Footer>

</template>

上のAboutコンポーネントに訪れたときに、アニメーションさせたいとします。

About.vue/template

<div class="about__performance bd-grid">
  <h3 class="about__title">実績</h3>
  <div class="about__performance-data">
    <div v-for="(data, index) in performanceData" :key="index">
      <span class="about__number" ref="scrollTarget">{{ data.start }}<small>円</small></span>
      <span class="about__year">{{ data.year }}</span>
    </div>
  </div>
</div>

まず、アニメーションを適用させたい部分に ref="scrollTarget"とrefを指定します。

About.vue/script

<script>
import { reactive, onMounted, ref } from 'vue';
export default {
  setup() {
    const performanceData = reactive([
      {start: 0, number: 5000000, year:'2019年売上'},
      {start: 0, number: 10000000, year:'2020年売上'},
      {start: 0, number: 15000000, year:'2021年売上'},
    ])
    const speed = 5000;

    const scrollTarget = ref()

    const updateCount = () => {
      performanceData.forEach(data => {
        const target = data.number
        const inc = target / speed

        if(data.start < target) { data.start = Math.ceil(data.start += inc) let timeout = setTimeout(updateCount, 10) timeout = window.setTimeout(updateCount,10) window.clearTimeout(timeout) } else { data.start = target.toLocaleString() return } }) } onMounted(() => {
      let options = {
        rootMargin: '0px',
        threshold: 1.0
      }

      const observer = new IntersectionObserver(function(entries) {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
           updateCount()
           observer.disconnect()
          }
        })
      }, options)
      observer.observe(scrollTarget.value)
    })

    return {
      performanceData,
      speed,
      scrollTarget,
    }
  }

}</script>

アニメーションの時間を指定する

まずは、speedという変数を用意します。
ここでは5000ms、5秒を指定しています。アニメーションの持続時間に関係してきます。

スクロールのターゲットをリアクティブにする

templateで指定したrefのターゲットへ移動できるように、scrollTargetという変数を作成します。
リアクティブになるようにref()を使います。

onMountedにIntersectionObserverで監視する

mountされた時点でIntersectionObserverによる監視を開始させます。
IntersectionObserverは、コールバックとオプションを取ります。
IntersectionObserverの基本的な使い方は、JavaScriptのIntersectionObserverAPIでスティッキーナビを実装する方法で確認できます。

コールバック部分は、では、entriesをとるので、それをforEachでループさせます。
それぞれをentryとすることで、IntersectionObserverのいろいろなメソッドやプロパティを扱えるようになります。

たとえば、entry.isIntersectingとすれば、指定した要素が交差しているかどうかをtrue/falseで知ることができます。

このentry.isIntersectingがtrueのとき、アニメーションを実行させることで、ターゲットとしている要素までスクロールしたときに発火させることができます。

そのため、上の例では、updateCount()という関数を実行しています。
上のケースでは一度実行すればもう監視する必要がないアニメーションのため、observer.disconnect()で監視を止めています。

IntersectionObserverのインスタンスをobserverとしています。

observerに監視を指示する

忘れずに、
observerに、監視をさせるためには、

observer.observe(scrollTarget.value)

とします。
引数には、ターゲットとなる値を指定します。
document.querySelectorなどを直接書いてもOKです。

交差したときのアニメーションの関数を作成する

上の例では、updateCount()という関数です。
0から目的の数字(target)までカウントアップされるアニメーションです。

incという変数では、3つの値が同時に終わるように、targetをspeedで割っています。
そうすることで、数字が異なっていても、それぞれに合わせた増加量で、最終的には同時にtargetの数字までカウントアップされます。

targetの数字までsetTimeoutを繰り返し発動させたいので、
if文でそれを条件にして、0の数字(data.start)にincを足します。
端数がでるので、Math.ceil()を使って切り上げます。
数値の操作方法は、JavaScriptのMathメソッドと数値と文字列の変換や確認方法のまとめを参考にしてみてください。

clearTimeoutが効かないとき

setTimeoutを使う場合、clearTimeoutが効かないときは、windowを前につけてみるなどします。

ターゲットの数字をカンマ区切りにする

target.toLocaleString()

とtoLocaleStringを使います。
上のケースでは、targetと同じ数字になったときに、付与しています。

Vue.js

Posted by devsakaso