10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

vue-youtubeを使用して、複数のモーダル上で動画を再生させる

Posted at

実装結果

git映像

動画.gif

実装サイトURL

実装コード

  • ディレクトリ

src
├── App.vue
├── assets
│   └── scss
│       └── main.scss
├── components
│   ├── MovieButton.vue
│   └── MovieModal.vue
├── main.js
└── plugins
    └── vue-youtube.js
  • バージョン
"@vue/cli": "4.4.6",
"vue": "^2.6.11",
"vue-youtube": "^1.4.0"
コードの内容を見る
  • src/App.vue
<template>
  <div id="app">
    <movie-button
      :btn-num="1"
      :movie-id="movieData[0]"
      @modal-open="modalOpen($event)"
    />
    <movie-button
      :btn-num="2"
      :movie-id="movieData[1]"
      @modal-open="modalOpen($event)"
    />
    <movie-button
      :btn-num="3"
      :movie-id="movieData[2]"
      @modal-open="modalOpen($event)"
    />
    <movie-modal
      :modal-content="modalContent"
      :is-open="isOpen"
      @modal-close="modalClose()"
    />
  </div>
</template>

<script>
import MovieButton from "./components/MovieButton";
import MovieModal from "./components/MovieModal";
export default {
  name: "App",
  components: {
    MovieButton,
    MovieModal
  },
  data() {
    return {
      isOpen: false,
      modalContent: {},
      movieData: ["r8bECyGsw6Q", "2MqjzMeD3Uo", "4Vsi174LRgg"]
    };
  },
  methods: {
    modalOpen(event) {
      console.log(event);
      this.modalContent = event;
      this.isOpen = true;
    },
    modalClose() {
      this.isOpen = false;
    }
  }
};
</script>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
  • src/components/MovieButton.vue
<template>
  <transition name="fade" mode="out-in">
    <div class="modal" v-if="isOpen">
      <div class="modal__overlay" @click="onClick()"></div>
      <div class="modal__body">
        <p>モーダル</p>
        <div class="youtube__wrapper">
          <youtube :video-id="modalContent.movieId" ref="youtube"></youtube>
        </div>
        <button type="button" @click="onClick()">ボタン閉じる</button>
      </div>
    </div>
  </transition>
</template>

<script>
export default {
  props: {
    modalContent: {
      type: Object
    },
    isOpen: {
      type: Boolean
    }
  },
  methods: {
    onClick() {
      this.$emit("modal-close");
    }
  }
};
</script>

<style lang="scss" scoped>
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.modal__overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #0008;
}
.modal__body {
  position: relative;
  top: 50%;
  right: 0;
  bottom: 0;
  left: 0;
  max-width: 1000px;
  margin: auto;
  background: #fff;
  transform: translateY(-50%);
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
</style>
  • src/components/MovieModal.vue
<template>
  <button type="button" @click="onClick()">動画{{ btnNum }}</button>
</template>

<script>
export default {
  props: {
    btnNum: {
      default: 0,
      type: Number
    },
    movieId: {
      type: String
    }
  },
  methods: {
    onClick() {
      this.$emit("modal-open", {
        movieId: this.movieId
      });
    }
  }
};
</script>

<style lang="scss" scoped></style>

  • src/plugins/vue-youtube.js
import Vue from "vue";
import VueYoutube from "vue-youtube";

Vue.use(VueYoutube);
  • src/main.js
import Vue from "vue";
import App from "./App.vue";

import "./plugins/vue-youtube.js";

Vue.config.productionTip = false;

require("@/assets/scss/main.scss");

new Vue({
  render: h => h(App)
}).$mount("#app");
  • src/assets/main.scss
.youtube__wrapper {
  position: relative;
  width: 100%;
  margin: 0 auto;
  height: 0;
  padding-bottom: 56.25%;
  overflow: hidden;
  background: #aaa;

  iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
  }
}

実装手順

1. vue-youtubeインストール

vue-youtube - npm

$ npm install vue-youtube // or yarn add vue-youtube

2. vue-youtube記載

  • src/plugins/vue-youtube.js
import Vue from "vue";
import VueYoutube from "vue-youtube";

Vue.use(VueYoutube);
  • src/main.js
import "./plugins/vue-youtube.js";

※Nuxt.jsの場合、pluginsに記載

export default{
  plugins: ["~plugins/vue-youtube.js"]
}

参考リンク

3. ボタン追加

  • src/components/MovieButton.vue
<template>
  <button type="button" @click="onClick()">動画{{ btnNum }}</button>
</template>

<script>
export default {
  props: {
    btnNum: {
      default: 0,
      type: Number
    },
    movieId: {
      type: String
    }
  },
  methods: {
    onClick() {
      this.$emit("modal-open", {
        movieId: this.movieId
      });
    }
  }
};
</script>

<style lang="scss" scoped></style>
  • src/App.vue
<template>
  <div id="app">
    <movie-button
      :btn-num="1" 
      :movie-id="movieData[0]"
      @modal-open="modalOpen($event)"
    />
    <movie-button
      :btn-num="2"
      :movie-id="movieData[1]"
      @modal-open="modalOpen($event)"
    />
    <movie-button
      :btn-num="3"
      :movie-id="movieData[2]"
      @modal-open="modalOpen($event)"
    />
  </div>
</template>

<script>
import MovieButton from "./components/MovieButton";
export default {
  components: {
    MovieButton
  },
  data() {
    return {
      isOpen: false,
      modalContent: {},
      movieData: ["r8bECyGsw6Q", "2MqjzMeD3Uo", "4Vsi174LRgg"]
    };
  },
  methods: {
    modalOpen(event) {
      this.modalContent = event;
      this.isOpen = true;
    }
  }
};
</script>

movie-buttonについて

  • btn-num
    ボタンの番号を識別させるためのもの(今回の実装では必要なし)
  • movie-id
    動画のidで動画の内容を識別させるためのもの(管理しやすくするため、data内のmovieDataに登録)
  • @modal-open="modalOpen($event)"
    子コンポーネントのクリックイベントを検知して、実行(モーダルを開くためのもの)

参考

4. モーダル実装

  • src/components/MovieModal.vue
<template>
  <transition name="fade" mode="out-in">
    <div class="modal" v-if="isOpen">
      <div class="modal__overlay" @click="onClick()"></div>
      <div class="modal__body">
        <p>モーダル</p>
        <div class="youtube__wrapper">
          <youtube :video-id="modalContent.movieId" ref="youtube"></youtube>
        </div>
        <button type="button" @click="onClick()">ボタン閉じる</button>
      </div>
    </div>
  </transition>
</template>

<script>
export default {
  props: {
    modalContent: {
      type: Object
    },
    isOpen: {
      type: Boolean
    }
  },
  methods: {
    onClick() {
      this.$emit("modal-close");
    }
  }
};
</script>

<style lang="scss" scoped>
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.modal__overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #0008;
}
.modal__body {
  position: relative;
  top: 50%;
  right: 0;
  bottom: 0;
  left: 0;
  max-width: 1000px;
  margin: auto;
  background: #fff;
  transform: translateY(-50%);
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
</style>
  • src/App.vue
<template>
  <div id="app">
    <movie-modal
      :modal-content="modalContent"
      :is-open="isOpen"
      @modal-close="modalClose()"
    />
  </div>
</template>

<script>
import MovieModal from "./components/MovieModal";
export default {
  name: "App",
  components: {
    MovieModal
  },
  data() {
    return {
      isOpen: false,
      modalContent: {},
      movieData: ["r8bECyGsw6Q", "2MqjzMeD3Uo", "4Vsi174LRgg"]
    };
  },
  methods: {
    modalClose() {
      this.isOpen = false;
    }
  }
};
</script>
  • src/assets/main.scss
.youtube__wrapper {
  position: relative;
  width: 100%;
  margin: 0 auto;
  height: 0;
  padding-bottom: 56.25%;
  overflow: hidden;
  background: #aaa;

  iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
  }
}

movie-modalについて

  • modal-content
    モーダルに送る情報(今回はmovieIdのみ)
  • is-open
    モーダルが開いているか判定させるもの
  • @modal-close="modalClose()"
    子コンポーネントのクリックイベントを検知して、実行(モーダルを閉じるためのもの)

参考リンク

1. モーダルを開いたときに自動で再生を行う

PCのみ自動再生を行いたい場合 autoplay = 1を追加

<template>
  <youtube
    :video-id="modalContent.movieId"
    ref="youtube"
    :player-vars="playerVars"
  ></youtube>
</template>
<script>
  export default {
    data() {
      return {
        playerVars: {
          autoplay: 1
        }
      };
    },
  }
</script>

参考リンク

autoplay = 1のみだとSPなどのデバイスで対応することができないため、SPでも対応させたい場合、無音でインライン再生に変更を行うことで自動再生を行うことができる。

<template>
  <youtube
    :video-id="modalContent.movieId"
    ref="youtube"
    :player-vars="playerVars"
    @ready="ready"
  ></youtube>
</template>
<script>
export default {
  data() {
    return {
      playerVars: {
        playsinline: 1
      }
    };
  },
  methods: {
    async fetchYoutube() {
      await (this.isOpen = ture);
      this.$refs.youtube.fetchData();
    },
    ready() {
      const youtubePlayer = this.$refs.youtube.player
      youtubePlayer.mute()
      youtubePlayer.playVideo()
    }
  }
};
</script>

参考リンク

2. 字幕を自動で追加を行う

cc_lang_pref = 1に設定を行うことで字幕を追加できる

スクリーンショット 2020-08-09 17.52.34.png
export default {
  data() {
    return {
      playerVars: {
        cc_lang_pref: 1 // cc_lang_prefを1に行う
      }
    };
  },
}

参考リンク

詰まった部分について

1. youtubeのスタイルが一部ずれる

  • vueコンポーネント内に
.youtube__wrapper {
  position: relative;
  width: 100%;
  margin: 0 auto;
  height: 0;
  padding-bottom: 56.25%;
  overflow: hidden;
  background: #aaa;

  iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
  }
}

のような記載を行うと iframe内のコンポーネントにスタイルが当たらないため以下のような状態になる。
スクリーンショット 2020-08-09 16.05.30.png

また、スタイルを当てていないとモーダルを閉じる時、動画の高さがなくなり、以下のような状態になる。

スクリーンショット 2020-08-09 16.06.07.png

なので、グローバルcssの場所に以下を記載

.youtube__wrapper {
  position: relative;
  width: 100%;
  margin: 0 auto;
  height: 0;
  padding-bottom: 56.25%;
  overflow: hidden;
  background: #aaa;

  iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
  }
}

参考リンク

2. v-ifを用いるとthis.$refsが取得できない

v-ifを用いるとコンポーネントが描画されていない状態でthis.$refsの内容を取得しようとするので、取得できないエラーになる。
なので、async/awaitを用いることになり、コンポーネント描画 → $refs取得を行うことができるようにする

async fetchYoutube() {
  await (this.isOpen = ture); // isOpenフラグがtrueになったら
  this.$refs.youtube.fetchData(); // this.$refs.youtubeの取得を行う
},

参考リンク

3. モーダルが上下中央によらない

.modal__body {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  max-width: 1000px;
  margin: auto;
  background: #fff;
}

のような状態で配置した場合、

スクリーンショット 2020-08-09 17.42.06.png

のような状態で、高さが取得されないため、 position: relativetop: 50%などで変更を行う

.modal__body {
  position: relative; /* relativeに変更 */
  top: 50%; /* 50%に変更 */
  right: 0;
  bottom: 0;
  left: 0;
  max-width: 1000px;
  margin: auto;
  background: #fff;
  transform: translateY(-50%); /* -50%を追加 */
}
10
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?