「YouTubeを埋め込んでも重くならないようにする方法」がだいぶ微妙だった(ので書き直した)

公開日:

ウェブページに大量のYouTube動画を埋め込むと読み込みが遅くなるのを改善する方法が話題になっています。

考え方としては、元々 iframe 要素が埋め込まれているところを img 要素によるサムネイル表示に変更して、「クリックされたら iframe 要素に置き換え、かつ autoplay オプションで再生を始める」というやり方ですね。

この発想自体は良いものだと思うのですが、元記事も、その改良版のいずれも実装方法には問題があります。

  • HTML としては単なるサムネイル画像のみなので、スクリプトが無効だとなにも起こらない
  • img 要素に click イベント(のみ)を設定しているため、クリックやタップでは発火するが、キーボード操作ではなにも起こらない(Tabキーでフォーカスが当たらない)
  • img 要素に代替テキストが設定されていないため、画像が表示されない環境やスクリーンリーダー利用者には、そもそも動画が存在することすら分からない

ということで改良してみました。

スクリプト無効でも動作するように

たとえ iframe 要素を1,000個埋め込もうとも、 HTML としては文法的にも、考え方としても正当なものなので、そこには手を加えません。 YouTube の「埋め込みコード」そのままにします。

<iframe width="(動画の幅)" height="(動画の高さ)" src="https://www.youtube.com/embed/(動画ID)" frameborder="0" allowfullscreen></iframe>

ただし、 frameborder 属性は HTML 4.01 時代のもので、現行のHTMLには存在しません(html.spec.whatwg.org)。これは抜いてしまって、必要であればスタイルシートで枠線を消せば良いでしょう。

また、ページ内には動画以外でも iframe 要素を使っているかもしれません。 JavaScript 側から区別が必要になるので、クラスを設定します。

<iframe width="(動画の幅)" height="(動画の高さ)" src="https://www.youtube.com/embed/(動画ID)" allowfullscreen class="iframe_youtube"></iframe>

img要素によるサムネイル画像へ置き換え

JavaScript で「ページロード時に img 要素へ置換する」処理を行います。なお、元記事だと jQuery を使っていますが、とくに必要性を感じないのでプレーンな JavaScript で書きます。

document.addEventListener('DOMContentLoaded', function() {
  var youTubeIframeElements = document.querySelectorAll('.iframe_youtube');
  for (var i = 0, len = youTubeIframeElements.length; i < len; i++) {
    (function(youTubeIframeElement) {
      var youTubeEmbedUrl = youTubeIframeElement.src; // 埋め込みURL
      var youTubeId = youTubeEmbedUrl.substring(youTubeEmbedUrl.lastIndexOf('/') + 1); // 動画ID(URLの最後の "/" 以降がID)

      var youTubeThumbImageElement = document.createElement('img');
      youTubeThumbImageElement.src = '//img.youtube.com/vi/' + youTubeId + '/mqdefault.jpg';
      youTubeThumbImageElement.alt = '動画を再生';
      youTubePlayButtonElement.appendChild(youTubeThumbImageElement);

      /* iframe 要素をサムネイル画像に置き換える */
      youTubeIframeElement.parentNode.replaceChild(youTubeThumbImageElement, youTubeIframeElement);
    })(youTubeIframeElements[i]);
  }
}, false);

結果として img 要素になるなら、最初から直接 <img> と書くのと変わらないのでは? いいえ、まったく違います。このようにすることで、スクリプトが動作しない環境では iframe 要素による表示が行われ、重いながらも動画にアクセスすることができるのです。

また、サムネイル画像には「動画を再生」という代替テキストを設定しています。

埋め込みURLが https://www.youtube.com/embed/XXX 形式なこと前提のコードとしています。最初からパラメータが付いている場合(XXX?rel=0 等)はこのままでは動かないので、もう少し改良する必要があります。

click イベントは button 要素に設定する

サムネイル画像をクリックしたら動画を再生できるようにしなければなりませんが、 img 要素は単なる画像なので、そこに click イベントを設定するのは不適切です。

この機能は要するに「再生ボタン」なので、 button 要素が適切でしょう。最終的にはこうします。

document.addEventListener('DOMContentLoaded', function() {
  var youTubeIframeElements = document.querySelectorAll('.iframe_youtube');
  for (var i = 0, len = youTubeIframeElements.length; i < len; i++) {
    (function(youTubeIframeElement) {
      var youTubeEmbedUrl = youTubeIframeElement.src; // 埋め込みURL
      var youTubeId = youTubeEmbedUrl.substring(youTubeEmbedUrl.lastIndexOf('/') + 1); // 動画ID(URLの最後の "/" 以降がID)

      var youTubePlayButtonElement = document.createElement('button'); // 再生ボタン
      youTubePlayButtonElement.type = 'button';
      youTubePlayButtonElement.addEventListener('click', function() {
        /* ボタンをクリックしたら iframe 要素に戻す */
        youTubeIframeElement.src = youTubeIframeElement.src + '?autoplay=1'; // URLに ?autoplay=1 を付与することで自動再生する
        youTubePlayButtonElement.parentNode.replaceChild(youTubeIframeElement, youTubePlayButtonElement);
      }, false);

      var youTubeThumbImageElement = document.createElement('img');
      youTubeThumbImageElement.src = '//img.youtube.com/vi/' + youTubeId + '/mqdefault.jpg';
      youTubeThumbImageElement.alt = 'YouTube';
      youTubePlayButtonElement.appendChild(youTubeThumbImageElement);

      /* iframe 要素をサムネイル画像に置き換える */
      youTubeIframeElement.parentNode.replaceChild(youTubePlayButtonElement, youTubeIframeElement);
    })(youTubeIframeElements[i]);
  }
}, false);

そのままだと button 要素の枠や灰色背景が見えてしまってみっともないので、リセットスタイルを適用します。

.iframe_youtube button {
  margin: 0;
  padding: 0;
  border: none;
  color: inherit;
  background: transparent;
  font-family: inherit;
  line-height: inherit;
}

.iframe_youtube button::-moz-focus-inner {
  padding: 0;
  border: none;
}

.iframe_youtube button:focus {
  outline: 1px dotted;
}

.iframe_youtube button img {
  display: block;
}

::-moz-focus-inner 疑似要素についてや、 :focus で outline を設定する理由はbutton要素で画像ボタンを作るで詳しく解説しています。

このほかの問題として、サムネイル画像と実際の動画サイズが異なるので、「サムネイル画像をクリックしたらガタッとする」現象が起こってしまいます。これもスタイルシート等で調整できますが、主題と外れてくるので具体的なコード例は省略します。