スマホ対応の優れもの!クリック・タップの直前にページを先読み、高速表示する超軽量スクリプト -instant.page
Post on:2019年2月12日
ユーザーがリンクをクリックする際のhoverとタップする際のtouchstartに合わせて、クリック・タップする直前にページのHTMLをプリロードし、高速に表示するわずか1kBの超軽量スクリプトを紹介します。
ページに外部スクリプトを1つ加えるだけの簡単実装です。
instant.page
instant.page -GitHub
instant.pageの特徴
instant.pageはprefetchを使用し、ユーザーがクリック・タップする直前にページをプリロードして、ページを高速に表示します。
ユーザーがリンクをクリックする際には、そのリンクの上にマウスを移動します。そのリンクをユーザーが65ミリ秒間ホバーした場合、そのリンクをクリックする確率は1/2であるため、この時instant.pageはプリロードを始め、ページのプリロードには平均300ミリ秒かかります。スマホではユーザーはページを解放する前にディスプレイに触れ始め、ページをプリロードするのに平均90ミリ秒かかります。
人間の脳は100ミリ秒未満の行動を「瞬間」として知覚するため、instant.pageでページをプリロードすると即座に感じられるようになります。
instant.pageではユーザーが訪問する可能性が高い場合にのみページがプリロードされ、ユーザーとサーバーの帯域幅とCPUを考慮して、そのページのHTMLのみがプリロードされます。
スクリプトはわずか1kBで、MITライセンスです。
prefetchとは
prefetchとは先読みする機能で、3種類あります。
- Link Prefetching(prefetch)
リンク先のキャッシュ可能なリソースを先読みします。 - DNS Prefetching(dns-prefetch)
リソースに使用されている外部URLの名前解決を先に実施します。 - Page Prefetching(prerender)
指定URLのCSSを適用してJavaScriptを実行し、ページ全体の可視バージョンを作成します。
参考: Lightning Fast Websites with Prefetching
Amazon.co.jpやWordPressでは、dns-prefetchが使用されており、他にも多くのサイトで既に使用されています。
Amazonのコード
instant.pageでのプリロードは<link rel="prefetch" href="url">で行われます。
ブラウザのサポート
instant.pageはプログレッシブエンハンスメントで、サポートしていないブラウザには影響はありません。
- Chromeは、<link rel="prefetch">をサポートしています。
- Firefoxは、<link rel="prefetch">をサポートしていますが、キャッシュされていない場合はページを再ダウンロードします。
- Safariは、<link rel="prefetch">をサポートしていません。
- Edgeは、<link rel="prefetch">をサポートしていませんが、Chromeのエンジンを採用する予定です。
instant.pageのデモ
instant.pageのプリロードのデモではありませんが、リンクをホバーしてからクリックするまでの時間を数字で確認できます。
instant.pageの使い方
Step 1: 外部ファイル
当スクリプトを外部ファイルとして、</body>の上に記述するだけです。
1 2 3 4 5 6 |
<body> ... コンテンツ ... <script src="//instant.page/1.1.0" type="module" integrity="sha384-EwBObn5QAxP8f09iemwAJljc+sU+eUXeL9vSBw1eNmVarwhKk2F9vBEpaN9rsrtp"></script> </body> |
ソースコード
外部ファイル「instantpage.js」のソースコードは、下記の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
/*! instant.page v1.1.0 - (C) 2019 Alexandre Dieulot - https://instant.page/license */ let urlToPreload let mouseoverTimer let lastTouchTimestamp const prefetcher = document.createElement('link') const isSupported = prefetcher.relList && prefetcher.relList.supports && prefetcher.relList.supports('prefetch') const allowQueryString = 'instantAllowQueryString' in document.body.dataset if (isSupported) { prefetcher.rel = 'prefetch' document.head.appendChild(prefetcher) const eventListenersOptions = { capture: true, passive: true, } document.addEventListener('touchstart', touchstartListener, eventListenersOptions) document.addEventListener('mouseover', mouseoverListener, eventListenersOptions) } function touchstartListener(event) { /* Chrome on Android calls mouseover before touchcancel so `lastTouchTimestamp` * must be assigned on touchstart to be measured on mouseover. */ lastTouchTimestamp = performance.now() const linkElement = event.target.closest('a') if (!linkElement) { return } if (!isPreloadable(linkElement)) { return } linkElement.addEventListener('touchcancel', touchendAndTouchcancelListener, {passive: true}) linkElement.addEventListener('touchend', touchendAndTouchcancelListener, {passive: true}) urlToPreload = linkElement.href preload(linkElement.href) } function touchendAndTouchcancelListener() { urlToPreload = undefined stopPreloading() } function mouseoverListener(event) { if (performance.now() - lastTouchTimestamp < 1100) { return } const linkElement = event.target.closest('a') if (!linkElement) { return } if (!isPreloadable(linkElement)) { return } linkElement.addEventListener('mouseout', mouseoutListener, {passive: true}) urlToPreload = linkElement.href mouseoverTimer = setTimeout(() => { preload(linkElement.href) mouseoverTimer = undefined }, 65) } function mouseoutListener(event) { if (event.relatedTarget && event.target.closest('a') == event.relatedTarget.closest('a')) { return } if (mouseoverTimer) { clearTimeout(mouseoverTimer) mouseoverTimer = undefined } else { urlToPreload = undefined stopPreloading() } } function isPreloadable(linkElement) { if (urlToPreload == linkElement.href) { return } const urlObject = new URL(linkElement.href) if (urlObject.origin != location.origin) { return } if (!allowQueryString && urlObject.search && !('instant' in linkElement.dataset)) { return } if (urlObject.hash && urlObject.pathname + urlObject.search == location.pathname + location.search) { return } if ('noInstant' in linkElement.dataset) { return } return true } function preload(url) { prefetcher.href = url } function stopPreloading() { /* The spec says an empty string should abort the prefetching * but Firefox 64 interprets it as a relative URL to prefetch. */ prefetcher.removeAttribute('href') } |
sponsors