star back image
people4
電飾 電飾
moon
men
【Youtube】スクロールで見えたら自動再生、出たら停止したい

【Youtube】スクロールで見えたら自動再生、画面外に出たら停止したい

BLOG WEBログYOUTUBE
読了約:39分

ホームページにYoutube動画を沢山並べたい。
そんな気分の時はありますでしょうか。

そんな気分って普通はないと思いますよ。

みんな大好きYoutube、イェ-!

どうやって動画を貼り付けるの?
その方法はYoutubeのオフィシャルにあるので参照ください。

動画と再生リストを埋め込む:Youtubeオフィシャル
https://support.google.com/youtube/answer/171780?hl=ja

動画の下の[→共有]ボタンから、[埋め込む]ボタンが出るので押すと、HTML コードがあります。そのコードをコピーして自分のウェブサイトに貼り付けます。

この記事は、ページを下にスクロールしていく段階で、youtube動画を自動で再生させようという趣旨の記録になります。

何を言ってるの?どうやるの?
サンプルを用意しましたのでご確認ください。

【共有】ソースコード

スクロールしたら自動再生したい、共有サンプルは以下になります。

共有サンプルページ
https://astrowave.jp/amnesia_record/scroll_youtube_play.php

要件は以下です。
・スクロールで見えたら自動再生したい。
・表示範囲外に足突っ込んだら停止したい。
・クリックしたらモーダルして音声ありで再生したい。
・表示範囲外に行っても自動再生が停止しないでほしい。【オプション】
・Loopして再生されっぱなしにしたい。【オプション】

埋め込み以外に何か特殊なことをしたい、アクションを起こしたい場合はYoutubeの IFrame Player APIを利用します。

※また、自動再生を使うということは「mute」を使う必要があります。
(スクロール中にいきなり音が鳴るのは問題事項となります。音はクリックのようなユーザーアクションが必須です。)

なんで自動再生にする必要あるんですか?

動いてたら触ってみたいでしょ?

ただアニメーションするだけのバナーと違い、動画は情報量がリッチなので、雰囲気作りとかに利用されがちです。が、通信パケット量がかさみます。

沢山動画があると超格安プランの人にはちょっとギガ的に辛いページ、だよね。

html

<section class="main">
  <p>●スクロール再生</p>
  <div class="youtube-wrap">
    <div class="cont01 portrait-sp">
      <div class="video-container">
        <div id="youtube_player01" class="youtube_player" data-video-id="Gyh6ccKiu7c" data-video-id-sp="aV4BRjcMg2U" data-video-orientation="landscape" data-video-orientation-sp="portrait"></div>
        <a href="javascript:void(0);" class="thumbnail">
          <picture>
            <source media="(min-width: 768px)" srcset="./img/landscape_movie_cover.webp">
            <img src="./img/portrait_movie_cover.webp" alt="">
          </picture>
        </a>
      </div>
    </div>
    <div class="cont02">
      <div class="video-container">
        <div id="youtube_player02" class="youtube_player" data-video-id="_XmvErwJJHM" data-video-id-sp="_XmvErwJJHM" data-video-orientation="landscape"></div>
        <a href="javascript:void(0);" class="thumbnail">
          <img src="./img/landscape_movie_cover.webp" alt="">
        </a>
      </div>
    </div>
    <div class="cont03 portrait">
      <div class="video-container">
        <div id="youtube_player03" class="youtube_player" data-video-id="aV4BRjcMg2U" data-video-id-sp="aV4BRjcMg2U" data-video-orientation="portrait"></div>
        <a href="javascript:void(0);" class="thumbnail">
          <img src="./img/portrait_movie_cover.webp" alt="">
        </a>
      </div>
    </div>
    <div class="cont04">
      <div class="video-container">
        <div id="youtube_player04" class="youtube_player" data-video-id="Gyh6ccKiu7c" data-video-id-sp="Gyh6ccKiu7c" data-video-orientation="landscape"></div>
        <a href="javascript:void(0);" class="thumbnail">
          <img src="./img/landscape_movie_cover.webp" alt="">
        </a>
      </div>
    </div>
  </div>
</section>

<div class="youtube-modal" id="videoModal">
  <div class="youtube-modal__overlay js-video-modal-close"></div>
  <div class="youtube-modal__close js-video-modal-close"></div>
  <div class="youtube-modal__inner">
    <div class="youtube-modal__player" id="videoModalPlayer"></div>
  </div>
</div>

基本:
パソコンの時、横長タイプの動画とします。

それを踏まえて以下の設定をしました。
・portrait-sp(4行目)を入れたら、スマホの時は縦タイプ。
・portrait(23行目)を入れたら、パソコンでもスマホでも縦タイプ。

data属性:(4つ)
・data-video-id=”” ←パソコン動画Youtube ID(横長タイプ)
・data-video-id-sp=”” ←スマホ動画Youtube ID(縦タイプ)
・data-video-orientation=”” ←モーダル時のパソコン動画タイプ(横長/縦?)
・data-video-orientation-sp=”” ←モーダル時のスマホ動画タイプ(横長/縦?)

基本パソコンの時は横長タイプを意識しながら、各箇所に手動でYoutubeの動画IDやportrait/landscape を適用してください。

※適用例:運用テクニックの記述
<div id="youtube_player01" class="youtube_player" data-video-id="Gyh6ccKiu7c" data-video-id-sp="aV4BRjcMg2U" data-video-orientation="landscape" data-video-orientation-sp="portrait"></div>

面倒ですか?

運用の作業でしょうか。
慣れるまでちょっと悩みそうかも。

何回かテストすればスグに慣れます。

css

.video-container {
  position: relative;
  width: 100%;
  height: 0;
  padding-top: 56.25%;
}
.youtube-wrap .video-container iframe {
  pointer-events: none;
}
.youtube_player {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.thumbnail {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.thumbnail img {
  width: 100%;
  height: 100%;
  opacity: 1;
  transition: opacity .3s;
  object-fit: cover;
}
.thumbnail.is-hidden img {
  opacity: 0;
  transition: opacity .3s;
}
.modal__bg {
  background: #00000080;
  height: 100vh;
  position: absolute;
  width: 100%;
  top: 0;
}
.scroll {
  overflow-y: auto;
  max-height: calc(100svh + -0px);
}
.modal_box {
  max-width: unset;
  margin: 40px 80px;
  background: #e0dfd6;
  position: relative;
}
.youtube-modal {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 1001;
  opacity: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  transition: opacity .3s;
  background: #000c;
}
.youtube-modal.is-visible {
  opacity: 1;
  pointer-events: all;
}
.youtube-modal__inner {
  position: relative;
  z-index: 2;
}
.youtube-modal__overlay {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
}
.youtube-modal__close {
  position: absolute;
  right: 0;
  top: 0;
  width: 50px;
  height: 50px;
  cursor: pointer;
  z-index: 3;
}
.youtube-modal__close:before {
  transform: translate(-50%, -50%) rotate(45deg);
}
.youtube-modal__close:after {
  transform: translate(-50%, -50%) rotate(-45deg);
}
.youtube-modal__close:before, .youtube-modal__close:after {
  content: "";
  display: block;
  position: absolute;
  left: 50%;
  top: 50%;
  width: 20px;
  height: 2px;
  background: #fff;
}
.youtube-modal__player {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  background: #000;
}
.youtube-modal__inner:before {
  content: "";
  display: block;
  width: 100%;
  padding-top: 56.25%;
}
.youtube-modal[data-video-orientation=portrait] .youtube-modal__inner {
  width: auto;
  height: 80vh;
  max-width: unset;
  z-index: 2;
}
.portrait {
  width: 100%;
  max-width: 56.375%;
  margin: 0 auto;
}
.portrait .video-container {
  padding-top: 177.777%;
}
@media (797px <= width) {
  .youtube-modal[data-video-orientation=portrait] .youtube-modal__inner {
    height: 80vh;
  }
  .youtube-modal[data-video-orientation=portrait] .youtube-modal__inner:before {
    width: auto;
    height: 100%;
    padding-top: 0;
    padding-left: calc(80vh* .56375);
  }
  .youtube-modal[data-video-orientation=portrait] .youtube-modal__inner:before {
    padding-left: calc(80vh* .56375);
  }
  .youtube-modal__inner {
    width: 80%;
    max-width: 130vh;
  }
}
@media (width < 769px) {
  .youtube-modal[data-video-orientation=portrait] .youtube-modal__inner {
    width: 100%;
    max-width: 56.375vh;
    height: auto;
  }
  .youtube-modal[data-video-orientation=portrait] .youtube-modal__inner:before {
    padding-left: 0;
    padding-top: 177.38%;
  }
  .portrait-sp .video-container {
    padding-top: 177.777%;
  }
  .youtube-modal__inner {
    width: 100%;
  }
}

動画のレスポンシブって、cssは記述が長くなりがちです。

あまり見ないcssが1つあります。
data属性の判別、.youtube-modal[data-video-orientation=portrait]と書かれている部分です。

portrait/landscape (縦/横向き)の、portraitということは縦タイプの動画に気を使っている感じですね。

こんなcss指定知ってました?
私は知らなかったです。

今回、そのdata属性(data-video-orientation)は、
モーダルをさせた時に働くcssとして設定しました。

下のscript(114行目)で、<div class="youtube-modal" id="videoModal">にdata属性が書き込まれるようにしています。

script

<script>
var ytPlayer = [];
var ytEvent = {
  "onReady": onPlayerReady,
  "onStateChange": onPlayerStateChange
};

var tag = document.createElement("script");
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

// 画面幅でPCかスマホかを判定する関数
function isMobile() {
  return window.innerWidth < 769;
}

function onYouTubeIframeAPIReady() {
  $(".youtube_player").each(function (index, element) {
    // 画面幅に応じてdata-video-idかdata-video-id-spを取得
    var videoId = isMobile() ? $(element).data('video-id-sp') : $(element).data('video-id');

    ytPlayer[index] = new YT.Player(element.id, {
      height: '100%',
      width: '100%',
      videoId: videoId,
      events: ytEvent,
      playerVars: { autoplay: 0, playsinline: 1, mute: 1 }
    });
  });
}

function onPlayerReady(event) {
  var playerId = event.target.getIframe().id;
  var container = $(`#${playerId}`).closest('.video-container');
  container.find('.thumbnail').removeClass('is-hidden');
  checkAndPlaySingleVideo(event.target);
}

function onPlayerStateChange(event) {
  var playerId = event.target.getIframe().id;
  var container = $(`#${playerId}`).closest('.video-container');
  var thumbnail = container.find('.thumbnail');

  if (event.data === YT.PlayerState.PLAYING) {
    thumbnail.addClass('is-hidden'); // 再生中はサムネイルを隠す
  } else if (event.data === YT.PlayerState.PAUSED || event.data === YT.PlayerState.ENDED) {
    thumbnail.removeClass('is-hidden'); // 一時停止または終了時にサムネイルを表示
  }
}

window.addEventListener("scroll", function() {
  checkAndPlayVideos();
});

function checkAndPlayVideos() {
  $(".youtube_player").each(function (index, element) {
    checkAndPlaySingleVideo(ytPlayer[index]);
  });
}

function checkAndPlaySingleVideo(player) {
  if (player && typeof player.getIframe === "function") {
    var iframe = player.getIframe();
    var iframeTop = iframe.getBoundingClientRect().top;
    var iframeBottom = iframe.getBoundingClientRect().bottom;

    // 画面内にプレイヤーがあるかを判定
    if (iframeTop >= 0 && iframeBottom <= window.innerHeight) {
      player.mute(); // 自動再生時にはミュート
      player.playVideo();
    } else {
      player.pauseVideo();
    }
  }
}

// ----------------------------
// モーダル用のYouTubeプレイヤー変数
// ----------------------------
var modalPlayer;

// モーダルの要素
var videoModal = document.getElementById('videoModal');
var videoModalCloseButtons = document.querySelectorAll('.js-video-modal-close');

// 画面幅でPCかスマホかを判定する関数
function isMobile() {
  return window.innerWidth < 769;
}

// サムネイルクリックでモーダル表示 & 動画再生
$(".thumbnail").on("click", function () {
  var container = $(this).closest('.video-container');
  var videoPlayer = container.find('.youtube_player');
  var videoIdSp = videoPlayer.data('video-id-sp');
  var videoId = videoPlayer.data('video-id');
  var orientationSp = videoPlayer.data('video-orientation-sp'); // スマホ用のorientation
  var orientation = videoPlayer.data('video-orientation'); // PC用のorientation

  // スマホかPCかで動画IDとorientationを決定
  var videoToPlay = isMobile() ? videoIdSp : videoId;
  var orientationToUse = isMobile() && orientationSp ? orientationSp : orientation;

  // モーダル表示
  showModal(videoToPlay, orientationToUse);
});

// モーダルを表示して動画を再生する関数
function showModal(videoId, orientation) {
  // モーダルに is-visible クラスを追加し、data-video-orientationを設定
  var videoModal = document.getElementById('videoModal');
  videoModal.classList.add('is-visible');
  videoModal.setAttribute('data-video-orientation', orientation);

  // プレイヤーが初期化されていない場合、初期化する
  if (!modalPlayer) {
    modalPlayer = new YT.Player('videoModalPlayer', {
      height: '100%',
      width: '100%',
      videoId: videoId,
      playerVars: {
        autoplay: 1,
        playsinline: 1
      }
    });
  } else {
    // 既存プレイヤーに新しい動画IDをロード
    modalPlayer.loadVideoById(videoId);
  }
}

// モーダルを閉じて動画を停止する関数
function closeModal() {
  // モーダルから is-visible クラスを削除
  var videoModal = document.getElementById('videoModal');
  videoModal.classList.remove('is-visible');
  
  // 動画を停止
  if (modalPlayer) {
    modalPlayer.stopVideo();
  }
}

// モーダルのクローズボタンにイベントを追加
var videoModalCloseButtons = document.querySelectorAll('.js-video-modal-close');
videoModalCloseButtons.forEach(function (closeButton) {
  closeButton.addEventListener('click', function () {
    closeModal();
  });
});

// モーダル外をクリックしても閉じる処理
var videoModal = document.getElementById('videoModal');
videoModal.addEventListener('click', function (e) {
  if (e.target === videoModal || e.target.classList.contains('js-video-modal-close')) {
    closeModal();
  }
});
</script>

スクリプトも長いです。すみません。
Youtubeの IFrame Player APIを利用していることがわかると思います。

・パソコンとスマホで、動画の種類を変えるyoutubeの動画idの変更(21行目)
・パソコンとスマホで、モーダルの時のyoutubeの動画idの変更(102〜103行目)

以上が気を使う部分でしょうか。

あまり複雑にしたくなかったので。
もしスマホ用の動画(data-video-id-sp="")がなかったら、という分岐は設けませんでした。パソコンもスマホも同じ横長タイプなら、data-video-id-sp=""にもパソコン用の動画idを仕込んでください。

※AIさんがすぐ改修対応できると言ってましたが、やめときました。

より詳しい解説はchatGPTさんに放り込んでください。

【オプション】ありそうな要件

【オプション】表示範囲外に行っても自動再生が停止しないでほしい。

サムネイル画像がパカパカと出たり消えたりするのがウザい。
そういうことを言われましたでしょうか。

動画プレイヤーがちょっとでも画面に入ったら自動再生したい場合。

checkAndPlaySingleVideo 関数を改修します。

function checkAndPlaySingleVideo(player) {
  if (player && typeof player.getIframe === "function") {
    var iframe = player.getIframe();
    var iframeRect = iframe.getBoundingClientRect();

    // プレイヤーの一部が画面に表示されているかを判定
    var isVisible = iframeRect.top < window.innerHeight && iframeRect.bottom > 0;

    if (isVisible) {
      player.mute(); // 自動再生時にはミュート
      player.playVideo();
    }
  }
}

ソースが長くて疲れます。

【オプション】Loopして再生されっぱなしにしたい場合。

以下の関数を変更します。

function onYouTubeIframeAPIReady() {
  $(".youtube_player").each(function (index, element) {
    // videoIdを取得し、ログに出力して確認
    var videoId = isMobile() ? $(element).data('video-id-sp') : $(element).data('video-id');
    console.log("videoId for player", index, videoId); // ログを追加して確認
    
    ytPlayer[index] = new YT.Player(element.id, {
      height: '100%',
      width: '100%',
      videoId: videoId, // videoIdが適切に設定されているか確認
      events: ytEvent,
      playerVars: { autoplay: 0, playsinline: 1, mute: 1, controls: 0, loop: 1, playlist: videoId }
    });
  });
}
ループを1にして、ループする動画指定を入れます。
playerVars: { autoplay: 0, playsinline: 1, mute: 1, controls: 0, loop: 1, playlist: videoId }
function onPlayerStateChange(event) {
  var playerId = event.target.getIframe().id;
  var container = $(`#${playerId}`).closest('.video-container');
  var thumbnail = container.find('.thumbnail');

  if (event.data === YT.PlayerState.PLAYING) {
    thumbnail.addClass('is-hidden'); // 再生中はサムネイルを隠す
  } 
  // 動画が終了したときにはサムネイルを表示しない(ループ再生のため)
  else if (event.data === YT.PlayerState.PAUSED) {
    thumbnail.removeClass('is-hidden'); // 一時停止時にサムネイルを表示
  }
}
YT.PlayerState.ENDED の処理変更: 動画がループする際に YT.PlayerState.ENDED が発生しますが、この時点でサムネイルを表示せず、再ループまで待ちます。

【AI】イラストを描いてもらった

今回の記事のキャッチ画像で使わせてもらいます「Memeplex.app」で作成した画像です。誰でもgoogleアカウントでログインして使えます。

この記事にピッタリなイラストのための考えたリクエストは、「ハイテクな部屋の壁一面に整然と敷き詰められたモニターへ映し出される各国の監視映像に驚く捜査官」です。

選んだモードは以下の3つです。
・特撮
・サイバーパンク風
・UnrealEngine5風

AIイラスト

参考サイト置き場

参考にさせていただきました。ありがとうございます。

動画と再生リストを埋め込む:Youtubeオフィシャル
https://support.google.com/youtube/answer/171780?hl=ja

Youtubeの IFrame Player APIを利用します。