r-yanyoのブログ

アイコン画像

html新要素「portals」による新しい無限スクロール

  • html
  • chrome-extension
  • portals

Qiitaに挙げた記事です。 https://qiita.com/r-yanyo/items/f9907163bf2ac23f08cb

動作デモ

google検索にて、ページをスクロールすると、次のページが下からニュッっと出てきます。出てきたページをそのままスクロールし続けると、そのページに遷移します。 ezgif-5-f94ab8347027.gif

この記事の目的

本記事では、google検索でportalsによる無限スクロールを試し、portalsの可能性と現在の問題点を共有出来れば良いなと思っています。 portals流行ってくれ!!

portalsとはなんぞや

Webページの中に別のWebページを埋め込むためのhtml要素として、iframeがあります。portalsではiframeのように、埋め込んだWebページにシームレスに遷移することが出来ます。事前にページをロードすることが出来るので、例えば、遷移する際にページ全体が一瞬真っ白になる現象を防げます。SPAのページ遷移でも同じようなことが出来ますが、htmlの標準仕様として実装出来る点が優れていると思います。

現在portalsはドラフト状態です。現在の仕様はWICGの仕様書[^1]で見れます。 今はChrome Canaryでしか動きませんし、まだ仕様も決まりきっていませんが、今後HTMLの標準仕様となれば、他のhtml要素と同じように一般的に使用されることが予想されます。

portalsについては、こちらの記事[^2]が詳しいです。私もこの記事でportalsを知りました。

動作の前提

今回は、以下の条件でportalsによる無限スクロールを動かします。

  • portalsは現状Chrome Canaryでのみ動く。
  • google検索でのみ動く。汎用的に動くわけでは無い。
  • chrome拡張機能として作った。

また、portalsを動かすにはCanary版でフラグをつけて起動する必要があります。詳しくはこの記事[^2]を参考にして下さい。 以下引用

Mac: open -a Google\ Chrome\ Canary --args --enable-features=Portals Windows: ショートカットを右クリック、リンク先に --args -enable-features=Portals のオプションを付けて起動。 Linux: Canary は Linux ではサポートされていません。代替として Chromium をご利用ください。

無限スクロールの実装

ソースコードはgithubに上げています。git cloneして、Chrome Canaryに拡張機能として追加すると動かせます。

主にスクロールの位置を判定して、portalの表示・非表示を行っているだけです。表示方法はちょっと工夫していて、下からニュッっと出すために、空の領域(emptyArea)をportalの上に置いています。

window.onload = function() {
  // もし Portal が利用できるプラットフォームであれば...
  if (!'HTMLPortalElement' in window) return alert('portalが無効です。')

  // google検索でだけ動かす
  if (!window.location.href.includes('google')) return

  main()
}

function main() {
  // portalをページに追加
  nextPagePortal = document.createElement('portal')
  nextPagePortal.classList.add('portal')
  nextLink = document.getElementsByClassName('cur')[0].nextElementSibling
  nextPagePortal.src = nextLink.getElementsByTagName('a')[0].href

  // portalとemptyAreaをwrapする、スクロール領域
  scrollArea = document.createElement('div')
  scrollArea.classList.add('scroll')

  // portalの表示を下にずらすためのempty領域
  emptyArea = document.createElement('div')
  emptyArea.classList.add('empty')

  // 一番下までスクロールしたらportalがappendされる
  window.onscroll = function() {
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
    if (scrollTop >= document.body.offsetHeight - window.innerHeight) {
      document.body.appendChild(scrollArea)
      scrollArea.appendChild(emptyArea)
      scrollArea.appendChild(nextPagePortal)
      scrollArea.style.display = 'block'
    }
  }

  scrollArea.onscroll = function() {
    // 上に戻ったらportal非表示
    if (this.scrollTop <= 0) {
      this.style.display = 'none'
    }
    // スクロールし終えたらactivate
    if (this.scrollTop >= window.innerHeight) {
      nextPagePortal.activate()
    }
  }
}

.portal {
  width: 100vw;
  height: 100vh;
}

.scroll {
  position: fixed;
  top: 0;
  height: 100vh;
  width: 100vw;
  z-index: 10000;
  overflow-y: scroll;
}

.empty {
  height: 100vh;
  width: 100vw;
}

portalsの現在の問題点

無限に再帰する

portalはsrc属性で参照するhtmlを指定しますが、それ自身が追加されているページを指定することも可能です。 例えば、A.html<portal src="A.html">を追加することが出来ます。すると、DOMツリーは以下のように無限に再帰します。

<html>
  ...
  <portal src="A.html">
    #document
      <html>
        ...
        <portal src="A.html">
          #document
            <html>
              ....

上の例は自己参照ですが、相互参照(A.htmlとB.htmlがportalsで参照し合うような場合)や、今回行った無限スクロールの場合でも同じ現象が起きます。

今回はこれを防ぐために、ページを一番下までスクロールしてからDOMツリーにportalを追加しています。portalsの売りはシームレスな遷移なので、初めはwindow.onloadのタイミングで追加していましたが、上記の問題にぶつかったので追加タイミングを変えました。[^4]

また、これはiframeでも起きうる問題ですが、iframeでは特定回数以上の入れ子になるとストップしてくれるようです。^3

Activateした後、historyが消える

activateされた後、戻るボタンなどで前のページに戻れない。ちなみに今回実装した無限スクロールも、前のページに戻ることは出来ない。これを解決するには、仕様の変更を待つしかない、、、かな。

おわりに

portals流行ってくれ!

[^1]: Portals https://wicg.github.io/portals/ draft spec。暫定の仕様書。

[^2]: 話題の Portals を使った画面遷移 UX の未来 https://blog.uskay.io/article/002-hands-on-portals これを見てportalsを知りました。具体的な実装があり、非常に参考になりました。

[^4]: portalsにはメッセージをやり取りするためのインターフェースが用意されています。window.portalHostで自身がPortal として利用されているかの判定が可能らしいです。「自身がportalsとして利用されている場合は、新たなportalをDOMツリーに追加しない」とすれば、無限に再帰するのを防げるかもしれません。