無限スクロールを作成する:JavaScriptのIntersection ObserverにFetchを組み込んだプログラム 結城永人 -2019年6月25日 サイトの無限スクロールのプログラムのアイデアは大きく二つの部分に分けられる。一つは要素判定で、今のページが画面のどこまで来たら次のページを表示するように動作するかを決める。もう一つは生成処理で、次のページをAjax通信でサーバーから読み込んで今のページに実際に追加する。JavaScriptのプログラムで前者に正しく画面と要素の交差を監視できて最適なIntersection Observer API、後者にAjax通信の時間差の難点も克服できて便利なFetch APIを使って無限スクロールを作成する方法を取り上げる。 無限スクロールのプログラムの基本的な流れ Brown grasses under white clouds at daytime by Glenn Carstens-Peters / Unsplash 通常、同じデザインのページがページネーションのリンクで幾つも続いて行くのを無限スクロールのデザインに置き換える。 HTMLはメインコンテンツ、ページネーション、サブコンテンツという順番に並ぶように記載する。 サブコンテンツをIntersection Observerの起動に指定してメインコンテンツの後に画面が到達したらFetchで次のページを自動的に読み込むようにする。 そしてメインコンテンツだけを取り出したら今のページのメインコンテンツの下/サブコンテンツの上に追加して表示する。 メインコンテンツとサブコンテンツの間のページネーションはあれば消してもしも最初のページで次のページへのリンクがなかったら無限スクロールを停止する。 ページネーションは無限スクロールを設置すればもはやサイトに不要だけれどもブラウザのJavaScriptがオフでも移動できるとかSEO対策に検索エンジンのクローラーがリンクを解析するなんて場合に備えて残しておくと良いと思う。 参考サイト検索エンジンとの相性を考慮した無限スクロールのベストプラクティス 無限スクロールのサンプルのソースコード HTMLのメインコンテンツとページネーションとサブコンテンツが順番に配置されたマークアップを対象としてJavaScriptのIntersection ObserverにFetchを組み込んだプログラムで無限スクロールを実行する。 HTML <main> <div class="posts"> <article> メインコンテンツ① </article> <article> メインコンテンツ② </article> <article> メインコンテンツ③ </article> </div> <nav class="pager"> <a class="older-link" href="/page2.html"> 次のページ </a> </nav> </main> <aside class="isf"> サブコンテンツ </aside> JavaScript var isf = document.querySelector("aside.isf"); var mnp = document.querySelector("main"); var bpg = document.querySelector("nav.pager"); var npl = document.querySelector("a.older-link"); let infiniteScrollObserver = new IntersectionObserver(entries => { entries.forEach(function(entry) { if (npl !== null) { (async function() { await fetch(npl.getAttribute("href")).then((response) => response.text()).then((text) => { let parserh = new DOMParser(); let doch = parserh.parseFromString(text, "text/html"); let pst = doch.querySelector("div.posts"); let bpNode = document.adoptNode(pst); npl = doch.querySelector("a.older-link"); mnp.appendChild(bpNode); });})(); if (document.querySelector("nav.pager") !== null) { bpg.remove(); }} else { infiniteScrollObserver.unobserve(isf); }});}, { rootMargin: "120px" }); infiniteScrollObserver.observe(isf); 実行結果 ブログのトップページなどのインデックスページで共通のアイデアの無限スクロールを取り入れて使用している。 プログラムの詳解:Intersection ObserverへのFetchの組み込み方 冒頭の四つの変数 var isf = document.querySelector("aside.isf")サブコンテンツのclassの取得:無限スクロールを開始する要素判定用var mnp = document.querySelector("main")メインコンテンツ全体の要素の取得:次のページのコンテンツを追加する対象用var bpg = document.querySelector("nav.pager")ページネーション全体のclassの取得:無限スクロールが可能ならば不要なページネーション全体の消去用var npl = document.querySelector("a.older-link")次のページのリンクのclassの取得:サーバーから読み込む次のページのURLの取得用 Intersection Observerのコンストラクタ作成 new演算子で変数のinfiniteScrollObserverにIntersectionObserverのコンストラクタを作成して使用する。 forEachのループ処理で監視する対象を複数の状況で逐一と扱えるようにする。 最後のobserve()メソッドの引数に監視する対象のサブコンテンツの変数のisfを入れて起動する。 オプションに「rootMargin: "120px"」を指定している。監視対象のサブコンテンツが画面に入る前からIntersection Observerを起動するためだ。同時よりもコンテンツを追加する際の遅れを防げる。無限スクロールはサーバーと通信する分だけ時間が多くかからざるを得ないからIntersection Observerを実際に必要な場所よりも早めに開始してデータを取り込んで次のページのコンテンツの生成処理へ移った方が迅速に動作する。 一つ目のif文 最初のページに次のページのページネーションはないので、除外するため、または次のページのページネーションがあるかぎりでしか無限スクロールを行わないようにする。 条件式の「npl !=null」は取得済みのページネーションのリンクタグの変数nplの内容がなくはない(ある)かどうかを判定する。 もしも変数nplの内容があれば「fetch(npl.getAttribute("href"))」で次のページのURLをhref属性から取得してコンテンツを読み込み、なければ「infiniteScrollObserver.unobserve(isf)」で監視するのを停止して無限スクロールを行わない。 Fetchのasyncとawaitによる同期的な使用 次のページのコンテンツをfetch()メソッドで読み込むけれどもそのままだとAjax通信がスクロールに追い付かなくて同じコンテンツが繰り返して読み込まれて表示される可能性がある。 例えば三回目の監視から始まる次のページのコンテンツの取得が十分に終わらないうちにサイトがスクロールで四回目の監視に入ると次のページのコンテンツから次の次のページへのリンクからURLを取得するのが間に合わなくて一回目の次のページのリンクを使って同じコンテンツしか読み込んで表示できないかも知れない。 訪問者のスクロールがゆっくりか、無限スクロールの新しいコンテンツの表示が一回目か二回目くらいまでならば追い付き易いけれどもさもないと同じコンテンツが繰り返されるバグが起きてしまう。 これを避けて順番にfetch()メソッドが動作するように、いい換えると一つの読み込みが終わったら初めて次の読み込みへ移るように同期的に使用するにはasync関数とawait演算子を使用するのが相応しい。 async 関数は、 await 式を含むことができます。 await 式は、async 関数の実行を一時停止し、 Promise の解決を待ちます。そして async 関数の実行を再開し、解決された値を返します。 async function|MDN|Mozilla async関数にawait演算子を付けたfetch()メソッドと関連する処理を入れておくと一つの読み込みの途中から他の読み込みが画面の新しいスクロールの要素判定から非同期に始まって最終的にどちらも同じページの結果を返したりはしなくなる。 fetch()メソッドは引数に次のページのリンクタグを取得したnplのhref属性にgetAttribute()メソッドをかけてURLを取得して入れる。 そして受信できたデータの変数responseからtext()メソッドで、次のページのtextデータを変数textに取得する。 Fetchで手に入るデータはDocumentインターフェースに属さないからDOMParserによってHTMLDocumentへパースしてDOMツリーを操作できるようにする。 textデータのままだと文字列として扱わざるを得なくて次のページのコンテンツの取得や加工のプログラムが煩雑になってしまう。 Fetchの処理の四つの変数 let parserh = new DOMParser() HTMLDocumentへのパースの用意:DOMParserのコンストラクタ作成 let doch = parserh.parseFromString(text, "text/html") 次のページのHTMLDocumentの取得:textデータにDOMParserのコンストラクタをかけてパースを行う let pst = doch.querySelector("div.posts") 次のページのメインコンテンツの取得:無限スクロールで追加するためだ let bpNode = document.adoptNode(pst) 次のページのメインコンテンツの新しい文書への移動:今のページの一部に挿入する用意 さらに二つの変数の処理が続く。 npl = doch.querySelector("a.older-link")次のページのデータからページネーションの次のページのリンクを取得:今のページからすると次の次のページのコンテンツを取得するために必要になるmnp.appendChild(bpNode) 今のページのメインコンテンツの領域の最後に次のページメインコンテンツを挿入 何れもグローバル変数(無限スクロールのプログラムのfunctionの外側に置かれたvar)の値を変更している。 nplの内容は次のページのリンクから次の次のページのリンクに入れ替わるわけで、すなわちFetchが発動して新しいページが読み込まれる、無限スクロールが進むほどに次のページのリンクが変数の内容として上書きされて新たに取得され直す。 これで無限スクロールは次の次のページ以降もページネーションのリンクタグのURLを取得して読み込めるようになる。 mnpの内容は次のページのメインコンテンツを追加して入れ換わるわけで、すなわちFetchが発動して新しいページが読み込まれる、無限スクロールが進むほどに追加されるメインコンテンツが変数の内容として上書きされて新たに取得され直す。 これで無限スクロールは次の次のページ以降のメインコンテンツを前回の分を消さずに連続して載せられるようになる。 グローバル変数でなくても最初に取得されたそれぞれの変数を今のページから次のページの読み込みに合わせて更新する必要がある。 Fetchの同期的な使用のためにはfetch()メソッドにawait演算子を付けて当該のプログラムの全体をasync関数として処理しなくてはならない。 いつ起動するかは無限スクロールでIntersection Observerの要素判定に基づくので、プログラミングとしてFetchのためのasync functionは順番が来れば直ぐに起動するように記載して構わない。 どこかの変数にそれ自体で値を返すわけでもなく、また他のところで使用されるものでもないからその場で始まってその場で終わる即時関数として扱うのが相応しいと感じる。 async functionの部分――最初のfetch()メソッドから最後のappendChild()メソッドまでFetchに関連するプログラムの全体――を半角括弧(())に入れてさらに半角括弧で実行するようにしている。 二つ目のif文 サイトに新しく追加する次のページからはページネーションを表示しないプログラムを組んでいる。訪問者のブラウザで無限スクロールが可能ならばページネーションは必要ないので、処理を減らして表示速度を上げるためにもメインコンテンツだけ表示したい。 しかし初回のページで訪問者が閲覧する当初はページネーションが表示される状態になっているので、無限スクロールで次のページが読み込まれて追加されるときに消すためのプログラムを二つ目のif文に記載する。 この処理は次のページへのページネーションが付いてない最初のページには必要ない。一つ目のif文で、最初のページは無限スクロールを行わないと判定されてIntersection Observerを停止する方の条件に入る。ページネーションを消去する二つ目のif文のプログラムは反対側の無限スクロールを行うと判定されるFetchのプログラムと併置しておけば最初のページを除外できる。 条件式の「document.querySelector("nav.pager") != null」は取得済みのページネーションがサイト上になくはない(ある)かどうかを判定する。 取得済みのページネーションがサイト上にあれば不要なので、remove()メソッドでページネーションを取得した変数bpgを削除して表示しないようにする。 関連ページImaginary|Bloggerのテンプレートの提供 コメント 新しい投稿 前の投稿
エドガー・アラン・ポーの黒猫の原文と注解 アメリカの英語の小説、 エドガー・アラン・ポーの 黒猫 を日本語に翻訳 しながら分からない単語や熟語を調べたので、英語の学習用に原文に沿って段落毎に掲載する。 取り上げる言葉の語彙力としてはおよそ日本の高校を卒業した水準以上、すなわち英検二級かTOEIC730点くらいを越える...
ナサニエル・ホーソーンの若いグッドマン・ブラウンの原文と注解 アメリカの英語の小説、 ナサニエル・ホーソーンの 若いグッドマン・ブラウン を日本語に翻訳 しながら分からない単語や熟語を調べたので、英語の学習用に原文に沿って段落毎に掲載する。 取り上げる言葉の語彙力としてはおよそ日本の高校を卒業した水準以上、すなわち英検二級かTOEIC7...
ナサニエル・ホーソーンの若いグッドマン・ブラウンの日本語訳 十九世紀のアメリカの作家、小説家の ナサニエル・ホーソーン の小説の 若いグッドマン・ブラウン (1835)の日本語訳を行った。一つの文学作品として人間の洞察力に富んだ優れた内容を持つだけではなく、表現も意義深いから外国語の英語の聞き取りと読み取りの教材としても最適だと感じる。 ...
三浦春馬のたぶん本当の死因 三浦春馬は自殺した。その後、 芦名星と竹内結子の自宅のクローゼットでの首吊りという同じ仕方での連続自殺 があり、他殺の陰謀論も思い浮かぶけど、しかし違うのではないか。今の 日本の芸能界には死にたくなる気持ち悪さがある し、芸能人が裕福な生活でも自殺することはさほど不思議ではないと...
芦名星のたぶん本当の死因 芦名星は自殺した。その前後、 三浦春馬と竹内結子の自宅のクローゼットでの首吊りという同じ仕方での連続自殺 があり、他殺の陰謀論も思い浮かぶけど、しかし違うのではないか。今の 日本の芸能界には死にたくなる気持ち悪さがある し、芸能人が裕福な生活でも自殺することはさほど不思議ではない...
ヘンリー・ローソンの家畜追いの妻の日本語訳 十九から二十世紀のオーストラリアの作家、詩人で小説家の ヘンリー・ローソン の小説の 家畜追いの妻 (1892)の日本語訳を行った。一つの文学作品として人間の洞察力に富んだ優れた内容を持つだけではなく、表現も意義深いから外国語の英語の聞き取りと読み取りの教材としても最適だと感じる...
ジャパネットたかたの丸尾詩織の商品説明に気持ちが入っていて素晴らしい理由 ジャパネットたかた のテレビショッピングを観ていて社員の丸尾詩織の商品説明の言葉が力強くてどんどん前に出て来るように聞こえるのが凄いと感じた。声が立体的に表現されているのは飛び出す絵本とか3D映像なんて印象まで与えるから他では経験できないほどの聴覚芸術が新たに生み出されたとも過言...
アドセンスの個人のアメリカの税務情報/W-8BEN納税フォームの書き方 アドセンスに利用者の税務情報が必要となり、2021年6月1日以降、YouTubeのサイト広告などで収益を得る場合には税務情報を提出しないとアドセンスの支払いからアメリカの源泉所得税が米国内国歳入法の第三章により、控除される。 Google は、YouTube パートナー プロ...
ヘンリー・ローソンの家畜追いの妻の原文と注解 オーストラリアの英語の小説、 ヘンリー・ローソンの 家畜追いの妻 を日本語に翻訳 しながら分からない単語や熟語を調べたので、英語の学習用に原文に沿って段落毎に掲載する。 取り上げる言葉の語彙力としてはおよそ日本の高校を卒業した水準以上、すなわち英検二級かTOEIC730点...
オスカー・ワイルドの星の子の日本語訳 十九世紀のアイルランドの作家、詩人で小説家で劇作家の オスカー・ワイルド の小説の 星の子 (1891)の日本語訳を行った。一つの文学作品として人間の洞察力に富んだ優れた内容を持つだけではなく、表現も意義深いから外国語の英語の聞き取りと読み取りの教材としても最適だと感じる。 ...
コメント