コードノート

コードノートはwebを中心にテクノロジー・クリエイティブを伝えるWebメディアです。コードリンク代表、片岡亮が執筆しています。

Youtubeのiframeが複数あるページの高速化。画像クリックで読み込み&スマホではLazyLoad

ページの読み込み速度が遅いと、SEO的にもユーザー体験的にも良くないよねと言われる昨今、外部リソースの読み込みは頭の痛い問題ですね。

頑張って高速化を施したページに、広告タグやYouTubeを設置した瞬間、5秒10秒も読み込み速度が追加された瞬間の悲しみは忘れられません。

その問題を解決するための記事が世の中では多数公開されており、たとえば以下の記事があります。

www.rectus.co.jp

qiita.com

これらに書かれている、最初はサムネイル画像を設置しておき、その画像がクリックされたらYouTubeのiframeへ置き換える方法で基本問題ないかと思います。

ですが参考記事中にも書かれているとおり、スマホ(iOSのみ?)だとautoplayが効かないため、動画の再生に2クリック必要となってしまう問題があります。

■PC(たぶんAndoridも)
・サムネイル画像クリック

・autoplayが効いたYouTubeのiframeが読み込まれる。再生開始。

■スマホ(iOS)
・サムネイル画像クリック

・autoplayが無効のYouTubeのiframeが読み込まれる。

・YouTubeの再生ボタンクリック。再生開始。

以上のような違いがあります。

スマホでautoplayがonになっていると、外出先でページを開いた時にいきなり音が流れてしまう可能性があるので致し方ないですね。

Andoridに関しては、僕の手元の実機だとAPIからplayVideoを叩けばautoplay的な処理を加えられるので問題ないですが、バージョンによっては効かない場合もあるかもしれません。

※追記
Andoridはというか、厳密にはAndroidChomeならってかんじっぽい。
メーカーにもよりますが、Chromeを標準ブラウザとしてないAndroid端末もあるはず(数%程度)なので、AndroidもiOSと同様に処理した方が安全かも。
【YoutubeAPI】 iPhoneやAndroidでplayVideo()が再生されず、読み込み中のままになる | へっぽこ開発室
Chromeが依然としてトップ - 1月モバイルブラウザシェア | マイナビニュース

ともあれ、2クリック必要な状態は好ましくないため、こんな形式を考えてみました。

・PC&Andorid
当初の想定どおり、サムネイルクリックでautoplayのYouTube読み込み。

・iOS
いわゆるLazyLoad(該当のオブジェクト位置までスクロールしたら読み込む方法)っぽい処理にする。

こうすれば、どの環境でも1クリックでYouTubeを再生でき、iOSの場合でもある程度は高速化が効いた状態になるはずです。

というわけで、書いたコードをアップします。
https://github.com/ryryo/youtubeMultiSpeedup

See the Pen Youtube Iframe clickLoad & lazyload by Ryo Kataoka (@ryryo) on CodePen.


codepenでは画像がアップできてないので、再生ボタンなど一部表示が足りない部分があります。動作もちょっと違うかも。

js部分はjQueryで書いてます。今風じゃないですがWordPressなどでも気軽に使えて良いですね。また対したボリュームでないので、html上に書いてしまってます。

一応下記にもjs部を記載します。修正があったらGitHubだけ更新するので最新版はそちらをご覧ください。

<script type="text/javascript">
// 各プレーヤーの格納
var ytPlayer = [];
// プレーヤーのサイズ
var ytWidth = 640;
var ytHeight = 390;

var ua = navigator.userAgent;
if (ua.indexOf('iPhone') > 0 || ua.indexOf('iPad') > 0 || ua.indexOf('iPod') > 0) {
  var os = "ios";
} else if (ua.indexOf('Android') > 0) {
  var os = "android";
} else {
  var os = "pc";
}
logmes("OS:" + os);

// iOSはlazyload的に処理
if (os == "ios") {
  var thisOffset = [];
  $(window).on('load', function () {
    //要素の位置取得
    $(".ytPlayerReady").each(function (i, elm) {
      thisOffset[i] = [];
      thisOffset[i]["playerId"] = $(elm).attr("id");
      thisOffset[i]["movieId"] = $(elm).data("movieid");
      thisOffset[i]["ytWidth"] = ytWidth;
      thisOffset[i]["ytHeight"] = ytHeight;
      thisOffset[i]["height"] = $(elm).offset().top + $(elm).outerHeight();
      thisOffset[i]["lazyStatus"] = false;

      logmes("位置:" + $(elm).attr("id") + ":" + thisOffset[i]["height"]);
    });

    //スクロール開始前にも、ファーストビュー中に動画があったらYouTube起動処理
    youtubeLazy(ytPlayer, thisOffset);
  });

  //lazyload的YouTube起動処理
  $(window).scroll(function () {
    youtubeLazy(ytPlayer, thisOffset);
  });
}

function youtubeLazy(ytPlayer, thisOffset) {
  var nowScrollTop;
  for (var i = 0; i < thisOffset.length; i++) {
    nowScrollTop = $(window).scrollTop() + $(window).height();

    if (nowScrollTop > thisOffset[i]["height"] && !thisOffset[i]["lazyStatus"]) {
      logmes("lazyload:" + thisOffset[i]["playerId"]);

      $("#" + thisOffset[i]["playerId"]).removeClass("ytPlayerReady");
      $("#" + thisOffset[i]["playerId"]).parent().parent().find(".controller").show();

      youTubeIframeAPIReady(ytPlayer, thisOffset[i]["playerId"], thisOffset[i]["movieId"], thisOffset[i]["ytWidth"],
        thisOffset[i]["ytHeight"]);

      thisOffset[i]["lazyStatus"] = true;
    }
  }
}

//画像クリックでのYouTube起動処理
$(".ytPlayerReady").click(function () {
  var playerId = $(this).attr("id");
  var movieId = $(this).data("movieid");
  $(this).removeClass("ytPlayerReady");
  $(this).parent().parent().find(".controller").show();

  logmes("起動クリック:" + playerId + ":" + movieId);

  youTubeIframeAPIReady(ytPlayer, playerId, movieId, ytWidth, ytHeight);

});

$(".y_start").click(function () {
  var playerId = $(this).parents('.controller').data("playerid");
  videoControl("playVideo", playerId);
});
$(".y_pause").click(function () {
  var playerId = $(this).parents('.controller').data("playerid");
  videoControl("pauseVideo", playerId);
});

// 各プレーヤーの埋め込み
function youTubeIframeAPIReady(ytPlayer, playerId, movieId, ytWidth, ytHeight) {
  ytPlayer[playerId] = new YT.Player(playerId, {
    width: ytWidth,
    height: ytHeight,
    videoId: movieId,
    playerVars: {
      rel: 0
    },
    events: {
      'onReady': onPlayerReady,
      'onStateChange': onPlayerStateChange
    }
  });

}

// 各プレーヤー準備完了後の処理
function onPlayerReady(event) {
  playerId = event.target.getIframe().id;
  logmes(playerId + ":onPlayerReady");

  if (os != "ios") {
    // videoControl("mute", playerId);
    videoControl("playVideo", playerId);
  }

}

function onPlayerStateChange(event) {
  playerId = event.target.getIframe().id;
  logmes(playerId + ":onPlayerStateChange");
  playerStateView(event, playerId);
}

function playerStateView(event, playerId) {
  var ytStatus = ytPlayer[playerId].getPlayerState();

  if (ytStatus == 1) {
    logmes('ステータス:再生中');
  } else if (ytStatus == 0) {
    logmes('ステータス:終了');
  } else if (ytStatus == 2) {
    logmes('ステータス:一時停止中');
  } else if (ytStatus == 3) {
    logmes('ステータス:バッファリング中');
  } else if (ytStatus == 5) {
    logmes('ステータス:頭出し済み');
  } else if (ytStatus == -1) {
    logmes('ステータス:未開始');
  } else {
    logmes('ステータス:該当なし?ログ確認');
    console.log(ytStatus);
  }
}

function videoControl(action, playerId) {
  // playVideo or pauseVideo or mute or unMute

  logmes("操作:" + playerId + ":" + action);
  var $playerWindow = $("#" + playerId)[0].contentWindow;
  if ($playerWindow) {
    $playerWindow.postMessage('{"event":"command","func":"' + action + '","args":""}', '*');
  }
}

function logmes(log) {
  if (log != "") {
    var mes = $("#mes").html();
    mes = mes + "<p>" + log + "</p>";
    $("#mes").html(mes);

    var scrollPoint = $('#mes')[0].scrollHeight;

    // $(".mesBox").scrollTop(scrollPoint);
    $('.mesBox').animate({
      scrollTop: $('#mes')[0].scrollHeight
    });
  }
}
</script>

ログをhtml上に出るようにしているのは、スマホでの動作確認に難儀した名残です。
LazyLoad的処理はざっくり書いたので、重くなったりしないか不安な部分。

また諸々の処理には、「IFrame Player API」 が使われています。ついでに再生や一時停止ボタンも動作確認がてらに設置しています。APIのドキュメントはこちらから。
https://developers.google.com/youtube/iframe_api_reference?hl=ja

テスト的にざっくり書いたもので、しっかり検証が済んだコードではないので、安定した動作は保証しかねます。ご利用の際にはお気をつけください。

では👋👋