Nuxt3で作ってみた! - Yo●Tube 動画ページ
前回のあらすじ
前回の記事では、YouTubeのトップページを作りました。 といっても、 ・ヘッダー ・サイドメニュー ・トップページのメイン部分 と、全体で共通する部分を作りました。
今回できるもの
今回はYouTubeの動画のページ(https://www.youtube.com/watch?v=●●●)を、Nuxt3で実装してみました。 この記事を見れば、 ・リンクによって内容が変わるページってどうなっているのか ・Webサイトのページがどんなふうにできるのか ・Htmlから作るのとNuxtでやる時の違い ・10分で1ページできてしまうぐらいのNuxt ✖️ Bulmaの相性の良さ ...etc を紹介できればなと思います!
phase.0 まずは構造理解
まず、このページはURLの「v=●●●」の部分 https://www.youtube.com/watch?v=●●● で動画が特定されます。なのでこの●の部分(動画ID)からデータを引き出し、その内容を表示してます。
さらに、 ・ヘッダー(サイドメニューも) ・動画の場所 ・動画詳細 ・コメント欄 ・関連動画と広告 に分かれています。
さらに、動画系の部分が右、関連動画系が左でまとまっているのでそこもいい感じに配置します。
ヘッダー系は前回作ったのでそれを使い回せばOKです。
なのでそれ以外の4つの部分ごとにソースも含めて解説します。
※BulmaとPugを使用しています。

phase.1 ページの大枠決め
ページ全体を見ると、右の大部分が動画やその詳細とコメント欄に、残りの左で関連動画系が配置されています。 一般的に作るのであれば、この二つの要素を横並びにする(display: flex系)だけですが、今回はBulmaのtile(いい感じにflexBoxになる)を使いました。 コレを使えば、「cssで0からやってサイズが合わない」みたいなことはほぼなくなります。 使い方は簡単!まずは一番親(まとまりの中の一番外側)のクラスを「tile is-ancestor」として、 ・横か縦に複数並べる場合は、まずクラス「is-parent」を入れて、その子クラスに「is-child」にする。 →ですが、厳密にいうと「is-parent」をつけるとpadding: 0.75rem;(余白ができるイメージ)なのでつけなくてもいいかもです。。。笑 ・横に並べるなら子クラスに「tile」をいれる。 ・縦に並べるなら子クラスに「is-vertical」をいれて、その子クラスに「tile」をいれる。 ・横並びの時は、is-● でこのタイルの幅サイズ(全体を12とした時の●)を決めれる。(1≦●≦12)
↓使用例
.tile.is-ancestor
.tile.is-parent.is-vertical.is-3
.tile.is-child
〜〜〜〜〜
.tile.is-child
〜〜〜〜〜
.tile.is-vertical
.tile.is-child
〜〜〜〜〜
.tile.is-child
〜〜〜〜〜
.tile
〜〜〜〜〜
phase.2 動画の再生場所を作ろう
動画はシンプルに「video」で配置しました。これで標準的な再生機能が使えます。 v-on:play などで再生開始時にしたい処理を追加したりできます。 ※5秒スキップなどの機能はやってないです。。。泣 やるなら、「Video.js」という動画コンテンツの実装やスタイリングを扱えるプラグインを使用するといいかもです。
.content
video.contentRounded(
id="movieBox"
controls
v-if="movieData.movie != null && movieData.movie != ''"
v-on:loadedmetadata="loadingMovie()"
v-on:play="onPlay()"
v-on:ended="onEnded()"
controlsList="nodownload"
oncontextmenu="return false;"
muted
playsinline)
source(
:src="movieData.movie"
type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"')
Video.jsとは Video.js Video.jsは、htmlにおける動画コンテンツの実装やスタイリングを簡単に行えるJavaScript製のプラグインです。 jsとcssのCDNも提供しているので、簡単に導入することができます。 ですがドキュメントは基本英語となので注意です。
phase.3 動画詳細部分を作ろう
基本的に、上からまとまりごとに考えると、 ・タイトル部分 ・チャンネル情報と好評価たち ・概要欄
に分かれています。 タイトルはまんま入れて、 横並びの左右に分けて(justify-content: space-between;)、チャンネル情報と好評価たちを配置しました。 概要欄はグレーのところに入れるだけですね。 ※「...もっと見る」で開閉する部分忘れてました。するならv-ifで出し訳するのが一番シンプルですね。
よく、動画のタイトルや概要欄など文字サイズを変えてやりたい場合、一般的にはcssで「font-size: 〜〜;」と文字サイズを指定するのですが、一個一個やるのはめんどくさい。そんなあなたにBulmaで標準装備されている「is-size-●」を使うといいかも。 これは、is-size-1(クソでかい。H1タイトルで使うぐらい) から is-size-7(めちゃちっちゃい。よくある契約書の備考ぐらいちっちゃい)まであり、 基本これ使えば大体のやりたい文字サイズになります。 また、文字の省略は「text-overflow: ellipsis;」でしています。枠からはみ出た部分を省略して「...」になります。
.is-child.has-text-left.p-3
p.title.is-5 {{ movieData.title }}
.content-row-space-between
.content-row-space-left
figure.image.is-32x32.m-1
img.is-rounded(:src="movieData.channel.thumbnail" alt="Channel image")
.has-text-left
p.title.is-size-7.cutMaxLength#movieChannelName {{ movieData.channel.name }}
p.subtitle.is-size-7 チャンネル登録者数 {{ $common.millBillUnit(movieData.channel.subscribers) }}人
button.button.is-black.is-rounded.p-2 チャンネル登録
.content-row-space-right
button.button.leftRounded
span.icon
i.fa-lg.fa-regular.fa-thumbs-up
span {{ $common.millBillUnit(movieData.goods) }}
button.button.rightRounded
span.icon
i.fa-lg.fa-regular.fa-thumbs-down
button.button.is-rounded.m-1
span.icon
i.fa-lg.fas.fa-share
span 共有
button.button.is-rounded.m-1
span.icon
i.fas.fa-lg.fa-solid.fa-ellipsis
.is-child.has-text-left.has-background-light.contentRounded.p-3.m-3
.content-row-space-left
p.subtitle.is-size-7.m-0.pr-2 {{ $common.millBillUnit(movieData.views) }}回再生
p.subtitle.is-size-7.m-0.pr-2 {{ $common.dateAgo(movieData.publishedAt) }}
p.subtitle.is-size-7.has-text-grey.m-0.pr-2(v-for="tag in movieData.hashTags") {{ `#${tag}` }}
p.subtitle.is-size-7.cutMaxLength.pt-2 {{ movieData.description }}
phase.4 コメント欄を作ろう
コメント欄の構造も、 ・何件のコメントか、並び替えボタン ・コメント入力欄 ・みんなのコメント に分かれています。
特記すべき部分はみんなのコメント部分でしょうか。 横並びに見てみると、 ・チャンネルアイコン ・コメント部分 ・ホバー(カーソル合わせる)するとメニューアイコン出てくる
この三つで、その中の「コメント部分」を縦に見て ・チャンネルIDの部分 ・コメントの内容 ・高評価とか と配置しています。
.is-child.has-text-left.p-3
.content-row-space-left
p {{ sumCommentCount.toLocaleString() }} 件のコメント
button.button.is-white
span.icon
i.fa-solid.fa-arrow-down-wide-short
span 並び替え
.content-row-space-left
figure.image.is-32x32
img.is-rounded(:src="movieData.channel.thumbnail")
.flex-leftover
input.commentInput(v-model="commentText" placeholder="コメントする...")
.content-row-space-between
button.button.is-white
span.icon
i.fa-solid.fa-regular.fa-face-laugh-beam
.content-row-space-right
button.button.is-rounded.is-white キャンセル
button.button.is-rounded(:class="[commentText != '' ? 'is-link' : 'is-light']") コメント
//- コメント
CommentCard(v-for="cm in commentList" :key="cm.commentID" :cm="cm")
<template lang="pug">
//- CommentCard(コメントカード)
.content-row-space-left-start.p-1
figure.image.is-32x32
img.is-rounded(:src="cm.channel.thumbnail")
.flex-leftover
.content-row-space-left.p-2
p.title.is-size-7.cutMaxLength.m-0 {{ `@${cm.channel.channelID}` }}
p.subtitle.is-size-7.cutMaxLength.m-0 {{ $common.dateAgo(cm.publishedAt) }}
p.subtitle.is-size-7.cutMaxLength.m-0(v-if="cm.publishedAt == cm.updatedAt") (編集済み)
p.subtitle.is-6.pl-2.m-0 {{ cm.comment }}
.content-row-space-left
button.button.is-white
span.icon
i.fa-regular.fa-thumbs-up
span {{ $common.millBillUnit(cm.goods) }}
button.button.is-white
span.icon
i.fa-regular.fa-thumbs-down
button.button.is-white 返信
button.button.is-small.is-white#hiddenBtn
span.icon
i.fas.fa-solid.fa-ellipsis-vertical
</template>
<script setup lang="ts">
const props = defineProps<{
cm: any;
}>();
</script>
<style lang="scss" scoped>
#hiddenBtn {
color: rgba(255, 255, 255, 0);
:hover {
color: black;
}
}
</style>
phase.5 関連動画と広告を作ろう
といっても、やっていることは ・一番上に広告 ・それ以降は関連動画を並べている です。MovieCard は前回記事で解説していますのでそちらをご覧ください。
.is-child.card
.card-image
img#adCard(:src="adData.thumbnail" alt="Ad thumbnail")
.content-row-space-between-center
.content-row-space-left
figure.image.is-32x32.m-3
img.is-rounded(:src="adData.sponsor.thumbnail" alt="Ad sponsor image")
.has-text-left
p.subtitle.is-6 {{ adData.title }}
p.title.is-size-7 スポンサー・
| {{ adData.sponsor.link }}
button.button.is-link.is-rounded.p-2.m-3 詳細を確認
.is-child.pt-2
ul.columns.is-multiline
li.column.is-full(v-for="mv in TopMovieList")
MovieCard(:movie="mv")
phase.6 まとめ
今回はYouTubeの動画詳細ページ解説しました。 他にも、チャンネルページやShortsなども作ってみる予定ですのでお楽しみに!
全体のソースはこちら! https://github.com/yamu-studio/Nuxt3-YouTube/tree/phase_1
右を参照
右を参照
Nuxt3で作ってみた! - Yo●Tube 動画ページ
前回のあらすじ 前回の記事では、YouTubeのトップページを作りました。 といっても、 ・ヘッダー ・サイドメニュー ・トップページのメイン部分 と、全体で共通する部分を作りました。
今回できるもの 今回はYouTubeの動画のページ(https://www.youtube.com/watch?v=●●●)を、Nuxt3で実装してみました。 この記事を見れば、 ・リンクによって内容が変わるページってどうなっているのか ・Webサイトのページがどんなふうにできるのか ・Htmlから作るのとNuxtでやる時の違い ・10分で1ページできてしまうぐらいのNuxt ✖️ Bulmaの相性の良さ ...etc を紹介できればなと思います!
phase.0 まずは構造理解 まず、このページはURLの「v=●●●」の部分 https://www.youtube.com/watch?v=●●● で動画が特定されます。なのでこの●の部分(動画ID)からデータを引き出し、その内容を表示してます。
さらに、 ・ヘッダー(サイドメニューも) ・動画の場所 ・動画詳細 ・コメント欄 ・関連動画と広告 に分かれています。
さらに、動画系の部分が右、関連動画系が左でまとまっているのでそこもいい感じに配置します。
ヘッダー系は前回作ったのでそれを使い回せばOKです。
なのでそれ以外の4つの部分ごとにソースも含めて解説します。
※BulmaとPugを使用しています。

phase.1 ページの大枠決め ページ全体を見ると、右の大部分が動画やその詳細とコメント欄に、残りの左で関連動画系が配置されています。 一般的に作るのであれば、この二つの要素を横並びにする(display: flex系)だけですが、今回はBulmaのtile(いい感じにflexBoxになる)を使いました。 コレを使えば、「cssで0からやってサイズが合わない」みたいなことはほぼなくなります。 使い方は簡単!まずは一番親(まとまりの中の一番外側)のクラスを「tile is-ancestor」として、 ・横か縦に複数並べる場合は、まずクラス「is-parent」を入れて、その子クラスに「is-child」にする。 →ですが、厳密にいうと「is-parent」をつけるとpadding: 0.75rem;(余白ができるイメージ)なのでつけなくてもいいかもです。。。笑 ・横に並べるなら子クラスに「tile」をいれる。 ・縦に並べるなら子クラスに「is-vertical」をいれて、その子クラスに「tile」をいれる。 ・横並びの時は、is-● でこのタイルの幅サイズ(全体を12とした時の●)を決めれる。(1≦●≦12)
↓使用例
.tile.is-ancestor
.tile.is-parent.is-vertical.is-3
.tile.is-child
〜〜〜〜〜
.tile.is-child
〜〜〜〜〜
.tile.is-vertical
.tile.is-child
〜〜〜〜〜
.tile.is-child
〜〜〜〜〜
.tile
〜〜〜〜〜
phase.2 動画の再生場所を作ろう 動画はシンプルに「video」で配置しました。これで標準的な再生機能が使えます。 v-on:play などで再生開始時にしたい処理を追加したりできます。 ※5秒スキップなどの機能はやってないです。。。泣 やるなら、「Video.js」という動画コンテンツの実装やスタイリングを扱えるプラグインを使用するといいかもです。
.content
video.contentRounded(
id="movieBox"
controls
v-if="movieData.movie != null && movieData.movie != ''"
v-on:loadedmetadata="loadingMovie()"
v-on:play="onPlay()"
v-on:ended="onEnded()"
controlsList="nodownload"
oncontextmenu="return false;"
muted
playsinline)
source(
:src="movieData.movie"
type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"')
右を参照
phase.3 動画詳細部分を作ろう 基本的に、上からまとまりごとに考えると、 ・タイトル部分 ・チャンネル情報と好評価たち ・概要欄
に分かれています。 タイトルはまんま入れて、 横並びの左右に分けて(justify-content: space-between;)、チャンネル情報と好評価たちを配置しました。 概要欄はグレーのところに入れるだけですね。 ※「...もっと見る」で開閉する部分忘れてました。するならv-ifで出し訳するのが一番シンプルですね。
よく、動画のタイトルや概要欄など文字サイズを変えてやりたい場合、一般的にはcssで「font-size: 〜〜;」と文字サイズを指定するのですが、一個一個やるのはめんどくさい。そんなあなたにBulmaで標準装備されている「is-size-●」を使うといいかも。 これは、is-size-1(クソでかい。H1タイトルで使うぐらい) から is-size-7(めちゃちっちゃい。よくある契約書の備考ぐらいちっちゃい)まであり、 基本これ使えば大体のやりたい文字サイズになります。 また、文字の省略は「text-overflow: ellipsis;」でしています。枠からはみ出た部分を省略して「...」になります。
.is-child.has-text-left.p-3
p.title.is-5 {{ movieData.title }}
.content-row-space-between
.content-row-space-left
figure.image.is-32x32.m-1
img.is-rounded(:src="movieData.channel.thumbnail" alt="Channel image")
.has-text-left
p.title.is-size-7.cutMaxLength#movieChannelName {{ movieData.channel.name }}
p.subtitle.is-size-7 チャンネル登録者数 {{ $common.millBillUnit(movieData.channel.subscribers) }}人
button.button.is-black.is-rounded.p-2 チャンネル登録
.content-row-space-right
button.button.leftRounded
span.icon
i.fa-lg.fa-regular.fa-thumbs-up
span {{ $common.millBillUnit(movieData.goods) }}
button.button.rightRounded
span.icon
i.fa-lg.fa-regular.fa-thumbs-down
button.button.is-rounded.m-1
span.icon
i.fa-lg.fas.fa-share
span 共有
button.button.is-rounded.m-1
span.icon
i.fas.fa-lg.fa-solid.fa-ellipsis
.is-child.has-text-left.has-background-light.contentRounded.p-3.m-3
.content-row-space-left
p.subtitle.is-size-7.m-0.pr-2 {{ $common.millBillUnit(movieData.views) }}回再生
p.subtitle.is-size-7.m-0.pr-2 {{ $common.dateAgo(movieData.publishedAt) }}
p.subtitle.is-size-7.has-text-grey.m-0.pr-2(v-for="tag in movieData.hashTags") {{ `#${tag}` }}
p.subtitle.is-size-7.cutMaxLength.pt-2 {{ movieData.description }}
phase.4 コメント欄を作ろう コメント欄の構造も、 ・何件のコメントか、並び替えボタン ・コメント入力欄 ・みんなのコメント に分かれています。
特記すべき部分はみんなのコメント部分でしょうか。 横並びに見てみると、 ・チャンネルアイコン ・コメント部分 ・ホバー(カーソル合わせる)するとメニューアイコン出てくる
この三つで、その中の「コメント部分」を縦に見て ・チャンネルIDの部分 ・コメントの内容 ・高評価とか と配置しています。
.is-child.has-text-left.p-3
.content-row-space-left
p {{ sumCommentCount.toLocaleString() }} 件のコメント
button.button.is-white
span.icon
i.fa-solid.fa-arrow-down-wide-short
span 並び替え
.content-row-space-left
figure.image.is-32x32
img.is-rounded(:src="movieData.channel.thumbnail")
.flex-leftover
input.commentInput(v-model="commentText" placeholder="コメントする...")
.content-row-space-between
button.button.is-white
span.icon
i.fa-solid.fa-regular.fa-face-laugh-beam
.content-row-space-right
button.button.is-rounded.is-white キャンセル
button.button.is-rounded(:class="[commentText != '' ? 'is-link' : 'is-light']") コメント
//- コメント
CommentCard(v-for="cm in commentList" :key="cm.commentID" :cm="cm")
<template lang="pug">
//- CommentCard(コメントカード)
.content-row-space-left-start.p-1
figure.image.is-32x32
img.is-rounded(:src="cm.channel.thumbnail")
.flex-leftover
.content-row-space-left.p-2
p.title.is-size-7.cutMaxLength.m-0 {{ `@${cm.channel.channelID}` }}
p.subtitle.is-size-7.cutMaxLength.m-0 {{ $common.dateAgo(cm.publishedAt) }}
p.subtitle.is-size-7.cutMaxLength.m-0(v-if="cm.publishedAt == cm.updatedAt") (編集済み)
p.subtitle.is-6.pl-2.m-0 {{ cm.comment }}
.content-row-space-left
button.button.is-white
span.icon
i.fa-regular.fa-thumbs-up
span {{ $common.millBillUnit(cm.goods) }}
button.button.is-white
span.icon
i.fa-regular.fa-thumbs-down
button.button.is-white 返信
button.button.is-small.is-white#hiddenBtn
span.icon
i.fas.fa-solid.fa-ellipsis-vertical
</template>
<script setup lang="ts">
const props = defineProps<{
cm: any;
}>();
</script>
<style lang="scss" scoped>
#hiddenBtn {
color: rgba(255, 255, 255, 0);
:hover {
color: black;
}
}
</style>
phase.5 関連動画と広告を作ろう といっても、やっていることは ・一番上に広告 ・それ以降は関連動画を並べている です。MovieCard は前回記事で解説していますのでそちらをご覧ください。
.is-child.card
.card-image
img#adCard(:src="adData.thumbnail" alt="Ad thumbnail")
.content-row-space-between-center
.content-row-space-left
figure.image.is-32x32.m-3
img.is-rounded(:src="adData.sponsor.thumbnail" alt="Ad sponsor image")
.has-text-left
p.subtitle.is-6 {{ adData.title }}
p.title.is-size-7 スポンサー・
| {{ adData.sponsor.link }}
button.button.is-link.is-rounded.p-2.m-3 詳細を確認
.is-child.pt-2
ul.columns.is-multiline
li.column.is-full(v-for="mv in TopMovieList")
MovieCard(:movie="mv")
phase.6 まとめ 今回はYouTubeの動画詳細ページ解説しました。 他にも、チャンネルページやShortsなども作ってみる予定ですのでお楽しみに!
全体のソースはこちら! https://github.com/yamu-studio/Nuxt3-YouTube/tree/phase_1
