blog.takurinton.dev

iframe のスクロール

2021-07-17

はじめに

こんにちは、どうも僕です。

Intersection Observer API を使ってスクロール率を用いてコンテンツの表示を操作するためにコードを書いていたのですが、ちょっとこけたのでまとめます。

Intersection Observer API とは

Intersection Observer API

とは、ターゲットとなる要素が指定した監視対象の要素が指定した viewport の範囲に入ったときに変更を非同期的に監視するための API です。

用途としては、

  • 画像などの要素をスクロール位置を用いて lazy loading する
  • ブログとかでスクロール位置によって見出しを管理するときに使える
  • scroll event
  • の代替

    などがあります。

    まだ実験的な機能ですが、非同期で監視をすることができ、コールバック関数を適宜呼び出すことができるので使い勝手がいいのかなと思います。

    例えば適当に作った箱の高さの2割が viewport に見えたときに console に出力するコード以下のようになります。

    html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="shortcut icon" href="https://www.takurinton.com/me.jpeg" type="image/x-icon">
        <title>Document</title>
    </head>
    <body>
    
        <div id="box1" class="hoge" style="margin: 100px; height: 500px; width: 300px; background-color: aqua;"><p class="content"></p></div>
        <div id="box2" class="hoge" style="margin: 100px; height: 500px; width: 300px; background-color: aqua;"><p class="content"></p></div>
        <div id="box3" class="hoge" style="margin: 100px; height: 500px; width: 300px; background-color: aqua;"><p class="content"></p></div>
        <div id="box4" class="hoge" style="margin: 100px; height: 500px; width: 300px; background-color: aqua;"><p class="content"></p></div>
        <script async defer src="./index.js"></script>
    </body>
    </html> 
    
    js
    // 渡すオプションを定義してる
    // root に null を渡すと viewport になる
    const options = {
      root: null,
      rootMargin: '0px',
      threshold: [0.2],
    }
    
    // observer を定義する
    const observer = new IntersectionObserver((targets) => {
        for (const target of targets) {
          if (target.isIntersecting) {
            console.log(`${target.target.id} is intersecting`) // ここで出力する
          }
        };
    }, options);
    
    // hoge クラスの要素を全て取得する
    const targets = document.querySelectorAll('.hoge');
    // observer を適用する
    targets.forEach(target => observer.observe(target)); 
    

    また、動作確認を codepen に作ったので興味がある人はぜひ。

    今回困ったところ

    で、今回困ったこととしては iframe に埋め込んだときにうまく動作しないということでした。

    そもそもやりたかったこととしては、スクロール率(body におけるスクロールの割合)が80%を超えたときに要素を表示したいというやつで、普通に書けば下の感じになります。(codepen でも動かなかったのでなしで)

    html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="shortcut icon" href="https://www.takurinton.com/me.jpeg" type="image/x-icon">
        <title>Document</title>
    </head>
    <body>
        <div id="box1" class="hoge" style="margin: 100px; height: 500px; width: 300px; background-color: aqua;"><p class="content"></p></div>
        <div id="box2" class="hoge" style="margin: 100px; height: 500px; width: 300px; background-color: aqua;"><p class="content"></p></div>
        <div id="box3" class="hoge" style="margin: 100px; height: 500px; width: 300px; background-color: aqua;"><p class="content"></p></div>
        <div id="box4" class="hoge" style="margin: 100px; height: 500px; width: 300px; background-color: aqua;"><p class="content"></p></div>
    <script>
    const scroll = 0.8;
    const options = {
        root: null,
        rootMargin: '-100% 0px 1000% 0px',
        threshold: 1 - scroll,
    }
    
    const observer = new IntersectionObserver(targets => {
      targets.forEach(target => {
          if (!target.isIntersecting) {
            console.log('here is 80%');
          } else {
              console.log('out of viewport');
          }
      });
    }, options);
    
    observer.observe(document.body)
    </script>
    </body>
    </html>
    

    rootMargin をずらすことで残りのスクロール率を計算してどこまでスクロールしているかを判定するようにしているのですが、これでは動きませんでした。

    解決方法

    解決方法はシンプルで、root に渡す値を null ではなくて document にするだけでした。

    (参考: https://w3c.github.io/IntersectionObserver/#intersection-observer-interface )

    root を null にすると viewport になるということは上のコメントで書いているのですが、root: null にしてると top level の viewport が observer の viewport になってしまっていて、iframe の中で指定したつもりがグローバルに適用されていて iframe の中の要素に効くわけではなくて iframe の外側の全体の要素に適用されてしまいうまく行かないといった感じでした。

    つまり、

    js
    const options = {
        root: document,
        rootMargin: '-100% 0px 1000% 0px',
        threshold: 1 - scroll,
    }
    

    のようにしてあげると動くようになりました。

    動くようになったコードはこれです。

    まとめ

    意外と時間がかかってしまった上に、友人からの指摘で気づいて解決したので日頃から web の仕様をしっかり読んで自分で解決したり柔軟に対応できるしたいなと感じました。頑張ります!!!!