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

## 動作デモ
google検索にて、ページをスクロールすると、次のページが下からニュッっと出てきます。出てきたページをそのままスクロールし続けると、そのページに遷移します。
![ezgif-5-f94ab8347027.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176467/9431668f-461a-ecc1-472d-582095684ade.gif)

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


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

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

portalsについては、[こちらの記事](https://blog.uskay.io/article/002-hands-on-portals)[^2]が詳しいです。私もこの記事でportalsを知りました。

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

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

また、portalsを動かすにはCanary版でフラグをつけて起動する必要があります。詳しくは[この記事](https://blog.uskay.io/article/002-hands-on-portals)[^2]を参考にして下さい。
以下引用
> Mac: open -a Google\ Chrome\ Canary --args --enable-features=Portals
Windows: ショートカットを右クリック、リンク先に --args -enable-features=Portals のオプションを付けて起動。
Linux: Canary は Linux ではサポートされていません。代替として Chromium をご利用ください。

## 無限スクロールの実装
ソースコードは[github](https://github.com/r-yanyo/portals-infinite-loading)に上げています。git cloneして、Chrome Canaryに拡張機能として追加すると動かせます。

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

```js:script.js
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()
    }
  }
}

```

```css:style.css
.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:A.html
<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を知りました。具体的な実装があり、非常に参考になりました。

[^3]: ブラウザの実装に依る。chromeで試したところ、iframeは3層以上でストップする仕様だった。この辺りは気になったので、WICGのgithubで質問してみました。https://github.com/WICG/portals/issues/108

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