見出し画像

IntersectionObserverを使って要素の交差を検知してアニメーションさせる

初めてIntersectionObserverを使ってみたのでnoteに残しておきたいと思います📝Vue.jsのコードですが、Vue.jsっぽい書き方していないので他のフレームワークでも一緒かと!

IntersectionObserverとは

スクロールして可視範囲に入ったらアニメーションさせたいってありますよね??ふわっとタイトルを表示させたり。

通常はJavaScriptのscrollイベントを使って実装すると思いますが、スクロールするたびにイベントが発火するのでパフォーマンスへの懸念があります・・😞

そこで、IntersectionObserverだと「要素と要素が交差したことを検知して処理を実行させる」という事が実現できます。

要素が交差したときだけ処理が実行されるので、わざわざスクロール量を計算する必要がなくなるわけですね!🙌

実際に作ったもの

画像1

画像とタイトルがルート要素と交差したときにアニメーションを追加することで、ふわっと表示できています。

アニメーションはCSSでtransform, transitionを使っています。下記に実際のコードを書いていきます!🌟

ベースのHTML

   <main class="contents">
     <div class="box">
       <img class="image" src="../assets/image01.jpeg">
       <h2 class="sub-title">サブタイトル</h2>
       <!-- 中略 -->
     </div>
     <div class="box">
       <img class="image" src="../assets/image02.jpeg">
       <h2 class="sub-title">サブタイトル</h2>
       <!-- 中略 -->
     </div>
     <div class="box">
       <img class="image" src="../assets/image03.jpeg">
       <h2 class="sub-title">サブタイトル</h2>
       <!-- 中略 -->
     </div>
   </main>

画像、サブタイトル、本文があるシンプルなHTMLです。(実際に動きのつける部分だけ記述)CSSを指定するため、画像とサブタイトルにはclassを付けておきます。

ベースのSCSS

   .image {
     opacity: 0;
     transform: translateY(40px);
     transition: opacity 1.4s, transform 0.8s;

     &.show {
       opacity: 1;
       transform: translateY(0);
     }
   }

   .sub-title {
     opacity: 0;
     transform: translateX(-40px);
     transition: opacity 1.4s, transform 0.8s;

     &.show {
       opacity: 1;
       transform: translateX(0);
     }
   }

imageクラス、sub-titleクラスにデフォルトの位置をtransformプロパティで指定し、showクラスが付いたら元の位置に戻るようにしています。

また、transitionプロパティでタイミング調整して、opacityプロパティで透明度を調整しています。

ここまではCSSの知識があれば理解しやすいと思います🙌

JavaScript部分を実装

ではIntersectionObserverを使っていこうと思います!

   const images = document.querySelectorAll('.image')
   images.forEach((target) => this.onIntersect(target))

   const subTitles = document.querySelectorAll('.sub-title')
   subTitles.forEach((target) => this.onIntersect(target))

ここでは下記の処理をしています。

1:document.querySelectorAllでHTMLを取得
2:取得したHTML要素をforEachして各要素を取り出す
3:各要素をonIntersectメソッドに渡す

3で指定したonIntersectメソッドではIntersectionObserverをnewして先ほど渡したHTML要素の監視をします。

    onIntersect(target, options = {}) {
     const observer = new IntersectionObserver(this.addShowClass, options)
     observer.observe(target)
   }

onIntersectの第二引数にはoptionsの指定がなかったときのために空のオブジェクトを指定しています。(options = {}の部分)

IntersectionObserver
第一引数・・・要素が交差したときの処理
第二引数・・・オプション

第一引数にはaddShowClassを指定しています。ここではメソッド名のとおりshowクラスを付与します。

    addShowClass(entries) {
     for(const e of entries) {
       if (e.isIntersecting) {
         e.target.classList.add("show")
       }
     }
   }

entriesにはこのようなプロパティが入っています。

スクリーンショット 2020-01-12 19.54.48

使っているプロパティは2つ!

・isIntersecting:要素が交差すればtrue
・target:ターゲットのHTML要素

isIntersecting判定でルート要素とターゲット要素が交差していればshowクラスを付与し、cssアニメーションを追加🧚🏻

これでほぼ完成です。

optionを指定して微調整する

IntersectionObserverをnewしたとき、第二引数にoptionを指定することができます!

   // rootからbottom:-150pxの位置で発火
   const options = {
     root: null,
     rootMargin: "0px 0px -150px",
     threshold: 0
   }
root:ターゲット要素と交差させる対象を指定(nullだとビューポート)
rootMargin:ルート要素からの距離
threshold:コールバック関数が呼ばれるタイミングを指定

rootMarginに"0px 0px -150px"を指定しているので、交差したあと下方向に150px移動したらコールバック関数が呼ばれます。

thresholdを0にしているので、すこしでもrootと交差するとコールバック関数が呼ばれます。0~1の間で指定ができます。

最終的なコード

script部分の最終的なコードはこちら!🎉

export default {
 name: 'animation',
 mounted() {
   // rootからbottom:-150pxの位置で発火
   const options = {
     root: null,
     rootMargin: "0px 0px -150px",
     threshold: 0
   }
   const images = document.querySelectorAll('.image')
   images.forEach((target) => this.onIntersect(target, options))
   const subTitles = document.querySelectorAll('.sub-title')
   subTitles.forEach((target) => this.onIntersect(target, options))
 },
 methods: {
   onIntersect(target, options = {}) {
     const observer = new IntersectionObserver(this.addShowClass, options)
     // 監視したい要素をobserveする。
     observer.observe(target)
   },
   addShowClass(entries) {
     for(const e of entries) {
       if (e.isIntersecting) {
         e.target.classList.add("show")
       }
     }
   }
 }
}

やりたかったことができて満足😊

DEMO

画像が大きくて重いかも・・。

対応ブラウザ

参考


この記事が気に入ったら、サポートをしてみませんか?気軽にクリエイターを支援できます。

note.user.nickname || note.user.urlname

息抜きのコーヒー代にさせて頂きます!!♡

ありがとうございます〜!
12
フロントエンドエンジニアです。 日々勉強しながらがんばってます₍ ᐢ. ̫ .ᐢ ₎
コメントを投稿するには、 ログイン または 会員登録 をする必要があります。