上毛印刷株式会社

【JavaScript】モーダルウィンドウを開いて、YouTubeを再生する

【JavaScript】モーダルウィンドウを開いて、YouTubeを再生する

2025年09月16日
WEBサイト制作
  • #HTML
  • #css
  • #JavaScript

こんにちは!
上毛印刷WEB制作担当のソーヤです。

今回は需要が多いであろう、モーダルウィンドウとYouTube埋め込み動画の合せ技tipsを紹介します。

実装例が少し特殊なので、デモページを作成しました。
アクセスしてみてください。
デモページはこちら(別タブで開きます)

HTML例

今回はYouTube Player APIを使います。
◆公式ドキュメントはコチラ(外部サイトに遷移します)
まず<head>内に下記を記述してください。

<script src="https://www.youtube.com/iframe_api"></script>

そうしたら、準備完了。
HTMLを記述してください。

<div class="item">
  <img class="thumb js-movie" src="./thumbnail01.jpg" data-video="mp-auQGeJEE" alt="thumb1">
  <div class="c-modal">
    <div class="c-modal__container">
      <div class="c-modal__inner">
        <div class="c-modal__contents">
          <div class="js-player movie-player"></div>
          <div class="c-modal__button__wrap">
            <button class="c-modal__button" type="button" aria-label="閉じる">✕</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
<div class="item">
  <img class="thumb js-movie" src="./thumbnail02.jpg" data-video="7bKrMeZRbgw" alt="thumb2">
  <div class="c-modal">
    <div class="c-modal__container">
      <div class="c-modal__inner">
        <div class="c-modal__contents">
          <div class="js-player movie-player"></div>
          <div class="c-modal__button__wrap">
            <button class="c-modal__button" type="button" aria-label="閉じる">✕</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

data-video属性に動画のIDを記述してください。

CSS例

.c-modal {
  -webkit-overflow-scrolling: touch;
  -webkit-backface-visibility: hidden;
  display: none;
  z-index: 9999;
  position: fixed;
  top: -10px;
  right: 0;
  bottom: -10px;
  left: 0;
  overflow: hidden;
  overflow-y: auto;
  backface-visibility: hidden;
  background-color: rgba(0, 0, 0, 0.45);
}
.c-modal__container {
  display: table;
  min-width: 500px;
  min-height: 500px;
  width: 100%;
  height: 100%;
  padding: 10px 0;
}
.c-modal__inner {
  display: table-cell;
  padding: 2.7em 2em;
  vertical-align: middle;
}
.c-modal__contents {
  -webkit-box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
  position: relative;
  max-width: 1300px;
  margin: 0 auto;
  padding: 30px;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
  text-justify: inter-ideograph;
  padding-bottom: 53.5%;
}
.js-player{
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.c-modal__contents::after {
  display: table;
  clear: both;
  content: '';
}
.c-modal__contents > p {
  line-height: 1.7;
  text-indent: 1em;
}
.c-modal__button__wrap {
  position: absolute;
  top: 20px;
  right: 20px;
}
.c-modal__button {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  display: inline-block;
  position: relative;
  width: 24px;
  height: 24px;
  border: none;
  outline: none;
  background: transparent;
  line-height: 1;
  text-indent: -9999px;
  vertical-align: middle;
  cursor: pointer;
}
.c-modal__button::before {
  position: absolute;
  top: -45px;
  left: 10px;
  width: 35px;
  height: 3px;
  -webkit-transform: rotate(45deg);
  -ms-transform: rotate(45deg);
  transform: rotate(45deg);
  background-color: #fff;
  content: '';
  cursor: pointer;
}
.c-modal__button::after {
  position: absolute;
  top: -45px;
  left: 10px;
  width: 35px;
  height: 3px;
  transform: rotate(-45deg);
  background-color: #fff;
  content: '';
  cursor: pointer;
}

JavaScript例

(function(){
  const players = {};
  let activeModal = null;
  function whenYouTubeReady(cb) {
    if (window.YT && window.YT.Player) return cb();
    if (!window.__ytApiHooked) {
      const orig = window.onYouTubeIframeAPIReady;
      window.onYouTubeIframeAPIReady = function() {
        if (typeof orig === 'function') orig();
        document.dispatchEvent(new Event('youtube-api-ready'));
      };
      window.__ytApiHooked = true;
    }
    document.addEventListener('youtube-api-ready', function handler() {
      document.removeEventListener('youtube-api-ready', handler);
      cb();
    });
  }
  document.addEventListener('DOMContentLoaded', function() {
    const html = document.documentElement;
    const body = document.body;
    const header = document.querySelector('header');
    document.querySelectorAll('.js-movie').forEach(function(img){
      img.addEventListener('click', function() {
        const youtubeId = this.dataset.video;
        if (!youtubeId) return;
        const item = this.closest('.item');
        if (!item) return;
        let modal = item.querySelector('.c-modal');
        if (!modal) return;
        let playerContainer = modal.querySelector('.js-player');
        if (!playerContainer) {
          const contents = modal.querySelector('.c-modal__contents') || modal;
          playerContainer = document.createElement('div');
          playerContainer.className = 'js-player movie-player';
          contents.insertBefore(playerContainer, contents.firstChild);
        }
        openModal(modal, youtubeId, playerContainer);
      });
    });
    document.querySelectorAll('.c-modal').forEach(function(modal){
      modal.addEventListener('click', function(e){
        if (!e.target.closest('.c-modal__contents')) {
          closeModal(modal);
        }
      });
      const btn = modal.querySelector('.c-modal__button');
      if (btn) {
        btn.addEventListener('click', function(){ closeModal(modal); });
      }
    });
    function openModal(modal, youtubeId, playerContainer) {
      if (activeModal && activeModal !== modal) closeModal(activeModal);
      activeModal = modal;
      const sbw = window.innerWidth - document.documentElement.clientWidth;
      if (sbw) html.style.paddingRight = sbw + 'px';
      html.style.overflow = 'hidden';
      body.style.overflow = 'hidden';
      if (header) header.style.display = 'none';
      modal.style.display = 'block';
      requestAnimationFrame(function(){ modal.style.opacity = '1'; });
      let touchStartY = 0;
      function onTouchStart(e){ touchStartY = e.changedTouches[0].screenY; }
      function onTouchMove(e){
        const currentY = e.changedTouches[0].screenY;
        const height = modal.offsetHeight;
        const isTop = touchStartY <= currentY && modal.scrollTop === 0; const isBottom = touchStartY >= currentY && (modal.scrollHeight - modal.scrollTop === height);
        if (isTop || isBottom) e.preventDefault();
      }
      modal._touchStart = onTouchStart;
      modal._touchMove = onTouchMove;
      modal.addEventListener('touchstart', onTouchStart, {passive:false});
      modal.addEventListener('touchmove', onTouchMove, {passive:false});
      if (!playerContainer.id) playerContainer.id = 'player-' + youtubeId;
      if (!players[youtubeId]) {
        whenYouTubeReady(function(){
          players[youtubeId] = new YT.Player(playerContainer.id, {
            height: '100%',
            width: '100%',
            videoId: youtubeId,
            playerVars: { autoplay: 1, rel: 0, playsinline: 1 },
            events: {
              onReady: function(e){ e.target.playVideo(); }
            }
          });
        });
      } else {
        try {
          const iframe = players[youtubeId].getIframe();
          if (iframe && iframe.parentNode !== playerContainer) {
            playerContainer.appendChild(iframe);
          }
        } catch (err) {
          console.warn('iframe move failed', err);
        }
        try { players[youtubeId].playVideo(); } catch(e){}
      }
    }
    function closeModal(modal) {
      if (!modal) return;
      const item = modal.closest('.item');
      const img = item ? item.querySelector('.js-movie') : null;
      const youtubeId = img ? img.dataset.video : null;
      if (youtubeId && players[youtubeId] && typeof players[youtubeId].pauseVideo === 'function') {
        try { players[youtubeId].pauseVideo(); } catch(e){}
      }
      if (modal._touchStart) modal.removeEventListener('touchstart', modal._touchStart);
      if (modal._touchMove) modal.removeEventListener('touchmove', modal._touchMove);
      delete modal._touchStart; delete modal._touchMove;
      modal.style.opacity = '0';
      setTimeout(function(){ modal.style.display = 'none'; }, 300);
      html.removeAttribute('style');
      body.removeAttribute('style');
      if (header) header.style.display = '';
      if (activeModal === modal) activeModal = null;
    }
  });
})();

まとめ

今回もjQueryではなく、Vanilla.jsで実装してみました。

コピペしてガンガン使ってください!

WEB制作担当ソーヤ

ソーヤ

上毛印刷WEB制作担当
東証プライム企業の本社WEB受託チームにてフロントエンドエンジニアの経験あり。


この記事に対するご意見・ご感想・ご質問等ありましたら、
ぜひ下記フォームにてお送りください。

    お名前必須
    メールアドレス必須
    お問い合わせ内容必須
    PAGE TOP