Qiitaに挙げた記事です。 https://qiita.com/r-yanyo/items/f9907163bf2ac23f08cb
動作デモ
google検索にて、ページをスクロールすると、次のページが下からニュッっと出てきます。出てきたページをそのままスクロールし続けると、そのページに遷移します。
この記事の目的
本記事では、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ツリーに追加しない」とすれば、無限に再帰するのを防げるかもしれません。