〽️基本形。 〽️往復。 〽️100%表示で発生。 〽️画面中央で発生。 〽️複数要素。 〽️横並び要素を同時発火。
⬆︎SVGついてのまとめページ、CSSアニメーションの基本コーナーはこちら。
let options = { bbrootMargin:"「「5〇〇」」", bbthreshold:[「「5〇〇」」], bbroot:「「5〇〇」」 bb }; const observer = new IntersectionObserver(callback,options); observer.observe(「「5element」」); function callback(entries) { bb「「3//処理」」 bb}
こんにちは、「ふ」です。
ユーザのスクロールに合わせたアニメーションが実装できる、IntersectionObserver API。
今回は「実践編」ということで、発生位置の調整や複数の要素への対応など、さまざまなケースにおいてのサンプルを紹介していきます。
サンプルコードがすぐに見たい方は、⬆︎のクイック目次からジャンプしてください。
通常の目次は↗︎のメニューアイコンから。
IntersectionObserver、「理解編」はこちら⬇︎。
実装に必要なオブジェクト、プロパティについて解説しています。
IntersectionObserverを使いこなす(理解編)。
2022.01.30
「動いているだけのアニメーション」の先へと進もう。
当記事では「理解編」の内容をふんだんに使って実装していきます。是非参考にしてください。
先づは基本形として、要素が画面に入ったらアニメーションが発動するものを作ってみます。
■ target.svg
■ HTML
<img id = "「「1gazou」」" src = "target.svg">
■ CSS
「「5.turn」」 { animation:turn 1s; } @keyframes turn { to { 「「3/* Y軸に対して1回転させる */」」 transform:rotateY(1turn); } }
imgと、アニメーションさせるためのCSSを用意しておきます。セレクタは「.turn」としていますが、この時点ではimgにclass名は付与していません。
IntersectionObserverでrootと交差したときに「classList.add」メソッドを使ってimg要素にclass名を付与します。
■ JavaScript
「「3//画像要素を取得」」 const gazou = document.getElementById("「「1gazou」」"); 「「3//インスタンス生成」」 let observer = new IntersectionObserver(callback); 「「3//targetを指定」」 observer.observe(gazou); 「「3//callback関数」」 function callback(entries) { 「「3//targetが交差しているなら」」「「1 ....①」」 if(entries[0].isIntersecting) { gazou.classList.add("「「5turn」」") } }
Jsのコードは結構単純です。これを実行してみましょう。
枠内⬆︎をゆっくりスクロールしてみてください。画像要素が枠内に入ってくると、アニメーションを開始します。
先ほどのJavaScriptコードの①の部分。
「「3//targetが交差しているなら」」「「1 ....①」」 if(entries[0].isIntersecting) { ...
entries[ 0 ](交差情報)にアクセスし、
「isIntersecting(targetがrootと交差しているか否か)が「true」なら....」
と条件分岐を行っています。
この1文はなぜ必要なのでしょうか??
実はページロード時にinterSectionObserverはrootとtargetの位置を見て、「交差していない」と判定を下します。即ち、1度発動してcallback関数を呼び出してしまっているのです。
⬆︎のような状態でcallbackが呼び出されてアニメーションが開始されたら、何の意味もありません。
そのため対策が必要です。
ページロード時の発動において、isIntersectingはもちろん「false」です。
ユーザが画面をスクロールして実際にtargetが表示されたときは「true」。
初回発動時にアニメーションが開始してしまわないよう、isIntersectingでの条件分岐が必要となるのです。
サンプルで使っているアニメーションは360°回転という内容なので、開始前も終了後も同じ状態です。
実装したいアニメーションの内容が開始/終了時の状態が違う場合で、尚且つ「終了時の状態をキープしたい場合」には、animation-directionを「forwards」に指定しましょう。
■ CSS
.turn { animation:turn 1s 「「1forwards」」; }
これでOKです。
さっきの基本セットの実装では、要素は「1度アニメーションしたらそれっきり」という内容でした。
場合によっては「上から要素が戻ってきたときもアニメーションを発動したい」ということもあるでしょう。
実装は簡単です。
targetがrootから外れたときにもIntersectionObserverは発動します。その際のisIntersectingプロパティの値はもちろん「false」。
「false」の場合にアニメーションのCSSを外してやればOKです。
すでに記述済みのif文に、条件不成立の場合の処理として付け加えましょう。
「「3//callback関数」」 function callback(entries) { if(entries[0].isIntersecting) { bbbbgazou.classList.add("turn") bb} else { bbbb「「1gazou.classList.remove("turn");」」 bb} }
上下にスクロールさせてみてください。
targetが画面に入るとアニメーション発火。その後画面から消えても、また戻ってきた時にアニメーションします。
これまではtargetが少しでもrootに重なったら、つまりちょっとでも画面に入ったらアニメーションが発動していました。
今後はoptionsオブジェクトを使って、画面内でのアニメーション発動位置を調整してみましょう。
要素全体が画面に表示されてから、アニメーションを開始させたい。それにはthresholdプロパティを使用します。
optionsのthresholdは、「targetの何%が重なったら交差したとみなす」プロパティでしたよね。これをmax値の「1」に指定しましょう。
let 「「5options」」 = { threshold:「「11」」 } let observer = new IntersectionObserver(callback,「「5options」」); 「「3// 〜 以下同文 〜」」
optionsオブジェクトを作成し、「threshold : 1」とします。
IntersectionObserverのインスタンス生成の際に、第2引数で参照させましょう。
!! optionオブジェクトの指定はインスタンス生成よりも先に記述しましょう。でないとエラーを吐きます。
例によって、上下にゆっくりとスクロールさせてみてください。
targetが画面に少しだけ入っている状態では、アニメーションしません。完全に入り切ったときにアニメーションが開始されます。
さらに発動位置を限定します。
要素が画面中央でアニメーションするよう、今度はrootMarginプロパティを調整してみましょう。
rootMarginは、root側の交差有効領域を操作するものでした。
マイナス側にマージンを取り、有効範囲を中央に絞ります。
let options = { rootMargin:"「「1-45% 0%」」" }
targetが中央付近でアニメーションするようになりました。
アニメーションの開始位置を自在にコントロールできる、便利な「rootMargin」と「threshold」ですが、指定の際は注意が必要です。値によっては「交差が有効となる領域」を打ち消してしまうことになるからです。
例えばthresholdを「1」にしていた場合、targetは自身の100%がrootに重なったときはじめて「交差した」と判定されます。
いっぽうrootMarginをマイナスに取ると、root側の交差有効領域は次第に狭まっていきます。
もしそれがtargetの高さより小さくなってしまったとき。targetの100%がrootに重なりきることができなくなってしまいます。
そうすると未来永劫、アニメーションが発動しません。
またスクロールの往復対応にしていると、targetの交差がはなれたときにアニメーションは解除されます。
rootMarginとthresholdの交差有効領域が小さすぎるバヤイ、発動したアニメーションが完走する前に、ユーザが画面をスクロールしてしまう場合があります。
そうすると途中で交差が外れ、アニメーションを最後まで見せることができません。先ほどの中央付近で発動させる場合などでは、注意が必要です。
classList.removeをやめて1回キリのアニメーションにするか、余裕を持ったrootMarginを取るか。はたまた2重にobserverを仕込むか(← ちょっと面倒くさいけど可能です)。
アニメーションの継続時間も考慮の上、rootMarginやthresholdを調整するようにしましょう。
target.svg
target2.svg
target3.svg
扨(さて)これまではobserveする要素は1つ、というケースで解説してきました。
実際のサイトにおいてはボタンやサムネイル画像など、複数の要素それぞれにアニメーション発火を仕込みたい場合も多々あるでしょう。その方法について紹介します。
■ HTML
<img class = "plural" src = "target.svg"> <img class = "plural" src = "target.svg"> <img class = "plural" src = "target.svg">
各要素に同じclass名を指定しておきます。
(*コード⬆︎には表記していませんが、要素間には適度な余白を設けています。)
■ JavaScript
const plural = document.「「1querySelectorAll(".plural")」」;「「3 ..①」」 let observe = new IntersectionObserver(callback); 「「1plural.forEach」」(function(value) { bbobserve.observe(value); })「「3 ..②」」 function callback(entries) { if(entries[0].isIntersecting) { bb「「1entries[0].target」」.classList.add("turn"); bb} else { bb「「1entries[0].target」」.classList.remove("turn"); bb}「「3 ..③」」 }
① querySelectorAllですべての「.plural」要素を取得。
② 取得した要素はNodeList(配列の親戚みたいなもの)になっています。
forEach( )を使って、各要素を監視対象に指定。これでtargetのセッティングはOKです。
③ これまでは単一の要素を対象としていたため、turnクラスの付与先は定数「gazou」を使っていました。
しかし今回は3つのターゲット、それぞれにclassを追加するため「entries[0].target」とプロパティを追うことで付与先を指定しています。
3つの要素がそれぞれ、画面に表示されるたびにアニメーションしています。
記事リンクのサムネイル画像など、横並びになった複数の要素をIntersectionObserverさせたいとき。
■ target4.svg*2
この場合rootである画面に対し、複数のtargetが同時に交差します。
そのときcallback関数の第1引数には、同時に交差した要素の数だけ、IntersectionObserverEntryオブジェクトが配列に格納された形で渡されます。
それぞれの要素に対してturnクラスを付与するためには、callback関数内でもforEach( )メソッドを利用しましょう。
「「3//コールバック関数」」 function callback(entries) { 「「1entries.forEach」」(function(value) { bbif(value.isIntersecting) { bbbbvalue.target.classList.add("turn"); bbbb} else { bbbbvalue.target.classList.remove("turn"); bbbb} bb}); }
複数要素が同時に交差した場合でも、それぞれに対してアニメーションを発動させることができました(rootMarginを" ー40% 0% "としています)。
最後までお読みくださり、ありがとうございます。
2記事にわたり、IntersectionObserverについて紹介してきました。このAPIを使いこなすことによって、webアニメーションの表現幅も大きく広がっていくことでしょう。
これからはobserverありきのアニメーションサンプルも紹介していきたいと思います。もし「?」の部分があれば、こちらに戻ってきて参考にしてください。
ではまた〜 ♪
JavaScriptの矢印「=>」〜これはアロー関数というものです。
2022.01.09
使い方とメリットについて解説。
SVGでWeb Animations API。
2020.11.12
〽️ ネイティブJavaScriptでのアニメーション。
swift、web、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。