一部のWebサイト高速表示に一役買っている「InstantClick」について

# 前書き
こちらは[AteamFinergyのアドベントカレンダー3日目](https://qiita.com/advent-calendar/2019/ateam-finergy)の記事になります。
内容としてはInstantClickの説明が大半を締めており
目的としては、InstantClickの認知を増やす事です

# InstantClickとは
> Webサイトを劇的に高速化するJavaScriptライブラリ」
-- from [instantclick.io](http://instantclick.io/)

JavaScriptのライブラリにInstantClickというものがあり、Webサイトに対して適用すると高速になる...というものがあり
そのInstantClickを実際に使用している例を上げると「[dev.to](https://dev.to)」や「[FC2爆速テンプレート](https://fc2information.blog.fc2.com/blog-entry-1989.html)」などで使用されています

特に[dev.to](https://dev.to)については[阿部寛のホームページ](http://abehiroshi.la.coocan.jp/)と比較している記事が多く見られ、ご存知のかたは多いのではないでしょうか

InstantClickでは[pjax(pushState + ajax)](https://github.com/defunkt/jquery-pjax#pjax--pushstate--ajax)を使用して高速化を図っており
リンク先のHTMLを[preload](http://e-words.jp/w/%E3%83%97%E3%83%AA%E3%83%AD%E3%83%BC%E3%83%89.html)しておき、リンク先へ遷移するタイミングに「`head`, `body`, `title`」を入れ替え、ユーザーに対してとても素早いページ遷移の体験を提供している様です。
>pjaxは、ajaxを介してサーバーからHTMLを取得し、ページ上のコンテナ要素のコンテンツを読み込まれたHTMLに置き換える
-- from [jquery-pjax](https://github.com/defunkt/jquery-pjax#pjax--pushstate--ajax)

ブラウザ上のページを変更することはないがInstantClickによってHTML要素が変更され、結果SPAに近しい挙動となります

## 仕組みについて
リンク先のHTMLをpreloadすると言っても、Webサイトには無数のリンクがあるため、「どのタイミング」で「どのくらい」取得する事が大事になるはずです

InstantClickのデフォルトの設定では、遷移先のリンクに対して「**マウスを重ねた瞬間**」に「**リンク先の要素**」を取得するようです

実際にユーザーがリンクにマウスオーバーしてからクリックするまでの間に200~300ms経過するため、その間にリンク先をpreloadしておく事でスムーズな遷移を提供しています

実際に[こちら](http://instantclick.io/click-test)で、「MouseOverイベントが走ってからクリックするまでの秒数」と「MouseDownイベントが走ってからクリックするまでの秒数」を確認する事ができるので一度見てみると良さそうです

また、モバイルについてはmouseoverではなく、`touchstart`イベントが対象となっているようです

実際にこちらを見ていただくと検証画面のInitiatorの箇所がInstantClickによって遷移先のHTMLを取得している事が確認できます
![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199819/0da870c9-38ba-789c-b0ca-ce6d45e60613.gif)
*FC２爆速テンプレートdemoページより*

## 導入方法
導入の仕方についてはとても簡単で
「InstantClickをダウンロード」「ファイルを任意の箇所に配置」
「読み込みの`script`タグと初期化の`script`タグを記述」の３ステップで出来ます。

1. [こちら](http://instantclick.io/download)からProduction Versionをダウンロード
2. InstantClickの読み込みをページの下部で実施

```html
  <script src="instantclick.min.js" data-no-instant></script>
  <script data-no-instant>InstantClick.init();</script>
</body>
```

## 注意点
InstantClickを使用する際にはいくつか注意する事があります

### JSで取得する事ができるイベントについて
InstantClickはpjaxの技術でHTMLを入れて表示を切り替えるので
`DomContentLoaded` jQueryの`ready()` などのイベントが発火しなくなります。

そのため、InstantClickではいくつかの既存のライフサイクルをフックする事で解決します。

#### 解決法： 既存ライフサイクルのフック
InstantClickでは下記の4つが提供されており、それぞれ元のイベントの代替になります。

- change
    - `DomContentLoaded`の置き換えとなり、InstantClickが読み込まれていない最初の遷移時にも発火
- fetch
    - マウスオーバーした時に、遷移先のHTMLをpreloadするタイミングで発火
- recieve
    - preloadが完了したタイミングで発火
- await
    - ユーザーがクリックしたがpreloadが完了していない待ち時間の間に発火

導入した際には
上記の様なイベントに既存のイベントが置き換わるため注意が必要です

### サーバーへのリクエスト数
デフォルトの設定のままだと、マウスオーバーするだけでサーバーへリクエストを送る事になり、場合によってはサーバーへの負荷が高まることやブラウザ側では使用しないムダなHTMLを取得する事になります

そのため、InstantClickでは2つの解決策を用意しています。

1. マウスダウンイベントへの変更
2. マウスオーバー時のリクエスト遅延

#### 解決法①: マウスオーバーからマウスダウンへの変更
InstantClickを初期化するタイミングで`mouseover`イベントから`mousedown`イベントに変更する事ができます。

Webサイトによっては、マウスオーバー程効果を得る事が出来ない可能性はありますが
それでもマウスダウンからクリックするまでに80~120ms程度かかるのでその間に普段のページ遷移より体感としては早く行えます。
計測は[こちら](http://instantclick.io/click-test)

初期化の記述に`mousedown`を引数として渡してあげるだけで変更できるようです

```html
 <script data-no-instant>InstantClick.init('mousedown');</script>
```

実際に下記のコードを確認すると
初期化のタイミングでこの引数を渡すと`document`に対して`mouseover`か`mousedown`かのどちらのイベントをリッスンするか変更する事が確認できます
https://github.com/dieulot/instantclick/blob/master/src/instantclick.js#L804
https://github.com/dieulot/instantclick/blob/master/src/instantclick.js#L830

#### 解決法②: マウスオーバー時のpreloadリクエストの遅延
デフォルトとマウスダウンへ変更した時とのちょうど間にある選択肢として
「マウスオーバー時にリクエストは送るけれど、カーソルが触れただけの時とかは無しにしたい」というケースでは
マウスオーバー時のリクエストを遅延させる事で解決できます

こちらを適用するには、マウスダウンへ変更した時と同様に、初期化のタイミングで何ms遅延させたいかを渡してあげるだけで大丈夫です
下記の例は75ms遅延させる時

```html
 <script data-no-instant>InstantClick.init(75);</script>
```

実際のコードを確認すると
初期化で渡された引数が保存され、`setTimeout`によって設定した時間分遅れてpreloadを実行するようです。
https://github.com/dieulot/instantclick/blob/master/src/instantclick.js#L454

ただ何も渡さない場合は65msにpreloadするようです。
https://github.com/dieulot/instantclick/blob/master/src/instantclick.js#L30

### リンク毎のpreload制御
初期設定だと一部を除いてほとんどのリンクがpreloadの対象となります。

具体的には下記の様なリンクがpreloadの対象外となります
- `target`または`download`属性を所持している
- 現在のWebサイトとは別の`domain`または`protocol`
- 同じページへのanchor

```html
<a href='https://qiita.com/' target='_blank'>not preload</a>
<a href='https://qiita.com/' download>not preload</a>
<a href='https://github.com/'>not preload</a>
<a href='http://qiita.com/'>not preload</a>
<a href='#InstantClickとは'>not preload</a>
```

ただ、使用する際に「ほとんどのリンクは適用したいけれど、このリンクはpreloadしたくない」などのケースが出てくる時があるでしょう
その時は「このリンク適用しない」と設定が可能な、**ブラックリスト機能**があります

#### 解決法: ブラックリスト・ホワイトリスト機能

設定方法は簡単で、preloadして欲しくないリンクに対して`data-no-instant`属性を付与するだけです。

```html
<a href='https://qiita.com/' data-no-instant>not preload</a>
```

また、該当箇所が複数あった場合については親の要素に`data-no-instant`属性を付与する事ですべての子要素に対してpreloadが走らなくなります。

```html
<div data-no-instant>
  <a href='https://qiita.com/'>not preload</a>
  <a href='https://qiita.com/'>not preload</a>
  <a href='https://qiita.com/'>not preload</a>
</div>
```

複雑化した際の方法としては
1. 親要素に`data-no-instant`を記述して子要素をまとめてブラックリスト追加
2. その子要素の任意のものだけ`data-instant`属性を追加する
とする事でホワイトリストに追加し簡易的な対応ができます

```html
<div data-no-instant>
  <a href='https://qiita.com/'>not preload</a>
  <a href='https://qiita.com/' data-instant>preload</a>
</div>
```

## まとめ
- InstantClickはpjaxで遷移先の値をpreloadし、入れ替える
- 遷移先を取得するのはmouseoverイベントのタイミングなのでユーザーに素晴らしい体験を提供できる
- サーバーへの負荷が高まる可能性がある
- javascriptの元々のイベントを取得できなくなる

いかがだったでしょうか、InstantClick自体が少し古い技術かと思いますが「dev.toめっちゃはやいやんけ！」という仕組みの片鱗を知って、興味を持っていただけたら幸いです



## 引用元
http://instantclick.io/
https://developer.mozilla.org/ja/
https://dev.to/
https://fc2information.blog.fc2.com/
https://github.com/defunkt/jquery-pjax