vimeoやyoutube動画を表示させようとすると、iframeであったりyoutube APIであったり、動画のIDを読み込ませプレイヤーを表示させるでしょう。vimeoなら以下のような感じでしょうか。
<section>
<div class="vimeo-movie-set">
<div class="relative">
<div class="product-vimeo">
<iframe src="https://player.vimeo.com/video/{{ section.settings.vimeo_id }}?autoplay=1&loop=1&color=000&title=0&byline=0&portrait=0&muted=1&controls=1&autopause=0" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
</div>
</section>
ところがそうではない。
オリジナルのシンプルなプレーヤーにしたい。
vimeoロゴとか、snsのリンクアイコンとかいらないと言うのです。
そうなるともう作るしかない?
動画IDの読み込ませで作ったのに。。
便利なjsライブラリを利用する
どこかの案件で利用したプレイヤー.jsをいただいて利用しよう!
そういうことになり、すぐに実行です。
時間もアレですしお寿司。
今回のライブラリを拝見して新しく分かったことが、<video>タグで動画を表示させるのにvimeoのダウンロードURLを利用しているということでした。
vimeoダウンロード、Starterプラン以上で利用可能
https://vimeo.com/jp/features/video-editor/download-video
vimeoだと常識?私は知りませんでした。
そのダウンロードURLをshopifyのカスタマイズで登録して使います。

ライブラリを利用してのオリジナルプレイヤーはとても早く実装できました。
完璧だと思っていたのですが、しかし。
しかし!?
複数の動画を登録したら、再生停止や、音の切り替えのボタンのコントロール操作できないと言うのです。ウソぉ。
1ページ内に複数の動画を入れると途端に、2つ目からでもプレイヤーで動画をコントロールできません。1つに減らすと正常に動作します。
複数の動画登録はテストしましたか?
してなかったかも。ダメなんですね。
AIに意見を聞いたところ、複数に対応するには「プレイヤーと動画を一致させる必要がある」と言うことでした。
なるほどぉ。よくわかりません。
AIと一緒に改修をしていく
無料なので低姿勢にAIさんへのらりくらりと質問すること1日。
AIさんは親切で前向きで、馬鹿にしたりしません。本当にありがたいです。
できたものを以下に共有します。
要件は以下です。
- 自動再生でループさせておく。
- PCとスマホで動画を切り替えたい。
- スマホの動画がない時はPCの動画を表示させる。
- 音は入り切り、シークバーとフルスクリーンは入れたい。
・テーマhtml(footer.liquid )
<!-- vimeo set -->
<link rel="stylesheet" href="{{ 'player.css' | asset_url }}">
<script src="{{ 'player.js' | asset_url }}"></script>
スクリプトは1回呼ばれれば十分なので、テーマテンプレートのどこかに一箇所仕込みます。(同じスクリプトが複数呼ばれると不具合の原因に)
・html(section-video-keymovie.liquid)
<!-- /sections/section-video-keymovie.liquid -->
<style>
.video-custom {
}
.fadeIn1s {
animation-name: fadeIn1s;
animation-delay: .5s;
animation-duration: 1.0s;
animation-fill-mode: forwards;
opacity: 0;
}
@keyframes fadeIn1s {
0% {
}
100% {
opacity: 1;
}
}
.kv .custom_video_wrapper {
position: relative;
height: 0;
width: 100%;
padding: 59.26% 0 0 0;
background-color: #000;
}
.kv .custom_video_wrapper video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
@media only screen and (max-width: 1024px) {
.kv .custom_video_wrapper {
padding: 177.77% 0 0 0;
height: auto;
background-color: #000;
}
}
</style>
<section class="video-custom">
<div class="kv"
data-section-id="{{ section.id }}"
data-section-type="featured-video"
data-overlay-header>
{% if section.settings.vimeo_controls %}
{% if section.settings.vimeo_url != blank %}
<div class="custom_video_wrapper">
<div class="custom_video_inner">
<div class="video-container" data-video-id="{{ section.id }}" data-pc-video-url="{{ section.settings.vimeo_url }}" data-mobile-video-url="{{ section.settings.vimeo_url_mobile }}">
<video id="video-{{ section.id }}" class="custom-video" autoplay playsinline muted loop allowfullscreen>
<source type="video/mp4" src="">
</video>
<div class="video-controls fadeIn1s">
<button class="video-play">
<img src="{{ 'pause.svg' | asset_url }}" alt="play video" width="12" height="12" title="play video">
<img src="{{ 'play.svg' | asset_url }}" alt="pause video" width="12" height="12" aria-hidden="true">
</button>
<div class="video-timeline">
<progress class="video-progressBar" value="0" min="0" max="100"></progress>
<input class="video-seek" value="0" min="0" type="range" step="1" max="100">
</div>
<button class="video-forward">
<img src="{{ 'forward.svg' | asset_url }}" alt="forward video" title="forward video" width="12" height="12">
</button>
<button class="video-fullscreen">
<img src="{{ 'fullscreen.svg' | asset_url }}" alt="full screen" title="full screen" width="18" height="10">
</button>
<button class="video-muted">
<img src="{{ 'sound_off.svg' | asset_url }}" alt="soundoff video" title="muted video" width="13" height="10">
<img src="{{ 'sound_on.svg' | asset_url }}" alt="soundon video" width="13" height="10" aria-hidden="true">
</button>
</div>
</div>
</div>
</div>
{% endif %}
{% else %}
{% if section.settings.vimeo_url != blank %}
<div class="custom_video_wrapper">
<video class="custom-video" autoplay playsinline muted loop id="video-{{ section.id }}">
<source type="video/mp4" src="{{ section.settings.vimeo_url }}">
</video>
</div>
{% endif %}
{% endif %}
</div>
</section>
<script>
document.addEventListener('DOMContentLoaded', function() {
var videoContainers = document.querySelectorAll('.video-container');
videoContainers.forEach(function(container) {
var video = container.querySelector('video');
if (!video) return; // 動画要素が取得できなかった場合は処理を終了
// PC用とスマートフォン用の動画URLをdata属性から取得
var pcVideoUrl = container.getAttribute('data-pc-video-url');
var mobileVideoUrl = container.getAttribute('data-mobile-video-url');
// デバウンス用のタイマー
var resizeTimeout;
// デバイスタイプに応じてsrc属性を設定
function setVideoSource() {
var currentTime = video.currentTime;
var isPlaying = !video.paused;
if (mobileVideoUrl && window.matchMedia && window.matchMedia('(max-width: 1024px)').matches) {
// スマートフォンの場合でmobileVideoUrlが設定されている場合
if (video.getAttribute('src') !== mobileVideoUrl) {
video.setAttribute('src', mobileVideoUrl);
}
} else {
// それ以外の場合、またはmobileVideoUrlが設定されていない場合
if (video.getAttribute('src') !== pcVideoUrl) {
video.setAttribute('src', pcVideoUrl);
}
}
// 動画が新しいURLで再生されるようにする
video.addEventListener('loadedmetadata', function() {
video.currentTime = currentTime;
if (isPlaying) {
video.play();
}
}, { once: true });
}
// 初回の動画URL設定
setVideoSource();
// リサイズイベントをデバウンスする
window.addEventListener('resize', function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function() {
setVideoSource();
}, 250); // 250ms後にsetVideoSourceを実行
});
});
});
</script>
{% schema %}
{
"name": "Custom Video Kv",
"class": "section-overlay-header",
"settings": [
{
"type": "text",
"id": "vimeo_url",
"label": "vimeo URL"
},
{
"type": "text",
"id": "vimeo_url_mobile",
"label": "vimeo URL mobile"
},
{
"type": "checkbox",
"id": "vimeo_controls",
"label": "view controls"
}
],
"presets": [
{
"name": "Custom Video Kv",
"category": "Image"
}
]
}
{% endschema %}
fadeIn1sというcssは、プレイヤーのデザインが当たっていないオリジナルのcss表示が、ページの読み込み時にチラッと見えるのを回避するためのものです。
他はvideoのサイズ指定です。
schemaの前にあるjsは、画面サイズでPCとスマホの動画を切り替えるためのスクリプトです。
デバウンズというのは、スマホでスクロールするとリサイズ処理が走るため、自動再生が何回も発火するのを回避するおまじないです。
アイコンのsvgは、いいフリー素材あります。
ICOOON MONO
https://icooon-mono.com/?s=%E3%82%B9%E3%83%94%E3%83%BC%E3%82%AB%E3%83%BC
・js(player.js)
document.addEventListener('DOMContentLoaded', function() {
var videoContainers = document.querySelectorAll('.video-container');
videoContainers.forEach(function(container) {
var videoId = container.getAttribute('data-video-id');
var video = document.getElementById('video-' + videoId);
var playButton = container.querySelector('.video-play');
var progressBar = container.querySelector('.video-progressBar');
var seekBar = container.querySelector('.video-seek');
var forwardButton = container.querySelector('.video-forward');
var fullscreenButton = container.querySelector('.video-fullscreen');
var muteButton = container.querySelector('.video-muted');
// 初期状態の変数
var isPlaying = !video.paused;
var isMuted = video.muted;
// 初期状態のアイコン設定
playButton.querySelector('img[alt="play video"]').style.display = 'block';
playButton.querySelector('img[alt="pause video"]').style.display = 'none';
// 自動再生後にアイコンを一時停止に変更
video.addEventListener('play', function() {
playButton.querySelector('img[alt="play video"]').style.display = 'none';
playButton.querySelector('img[alt="pause video"]').style.display = 'block';
});
video.addEventListener('pause', function() {
playButton.querySelector('img[alt="play video"]').style.display = 'block';
playButton.querySelector('img[alt="pause video"]').style.display = 'none';
});
// Play/Pause functionality
playButton.addEventListener('click', function() {
if (video.paused) {
video.play();
} else {
video.pause();
}
});
// Update progress bar and seek bar
video.addEventListener('timeupdate', function() {
if (isFinite(video.duration) && isFinite(video.currentTime)) {
var value = (video.currentTime / video.duration) * 100;
progressBar.value = value;
seekBar.value = video.currentTime;
}
});
// Set seek bar max value once metadata is loaded
video.addEventListener('loadedmetadata', function() {
if (isFinite(video.duration)) {
seekBar.max = video.duration;
progressBar.max = 100;
}
});
// Seek functionality
seekBar.addEventListener('input', function() {
if (isFinite(video.duration)) {
video.currentTime = seekBar.value;
}
});
// Forward functionality
forwardButton.addEventListener('click', function() {
video.currentTime += 10;
});
// Fullscreen functionality
fullscreenButton.addEventListener('click', function() {
if (video.requestFullscreen) {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
video.requestFullscreen();
}
} else if (video.webkitEnterFullScreen) { // iOS Safari
video.webkitEnterFullScreen();
} else if (video.mozRequestFullScreen) {
if (document.mozFullScreenElement) {
document.mozCancelFullScreen();
} else {
video.mozRequestFullScreen();
}
} else if (video.msRequestFullscreen) {
if (document.msFullscreenElement) {
document.msExitFullscreen();
} else {
video.msRequestFullscreen();
}
}
});
// Mute/Unmute functionality
muteButton.addEventListener('click', function() {
video.muted = !video.muted;
isMuted = video.muted;
if (isMuted) {
muteButton.querySelector('img[alt="soundoff video"]').style.display = 'block';
muteButton.querySelector('img[alt="soundon video"]').style.display = 'none';
} else {
muteButton.querySelector('img[alt="soundoff video"]').style.display = 'none';
muteButton.querySelector('img[alt="soundon video"]').style.display = 'block';
}
});
});
});
プレイヤーを動作させるスクリプトですが、今回。
5行〜6行目の記述で、動画とプレイヤーを一致させるためのidを仕込みました。
shopifyではカスタマイズでブロックを増やすと、sectionに自動でidが割り振られるようで、{{ section.id }}というそのidを利用しました。
自動でのidがない時は、面倒ですが専用にスキーマを作ってカスタマイズでidを自由に登録しても良いのではないでしょうか。
・css(player.css)
.video-container {
width: 100%;
height: 100%;
position: relative;
}
.custom_video_inner {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.video-controls {
position: absolute;
bottom: 2rem;
width: 85%;
display: -ms-flexbox;
display: flex;
-ms-flex-pack: distribute;
justify-content: space-around;
left: 50%;
transform: translateX(-50%);
}
@media (min-width: 64em) {
.video-controls {
width: 68%;
}
}
.video-controls button {
background: transparent;
color: #fff;
border: none;
cursor: pointer;
padding: 0;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
outline: none;
}
.video-controls img {
width: 20px;
}
.video-play {
width: 1.8rem;
margin-right: 0.8rem;
}
.video-play:not(.-pause) img:nth-child(1) {
display: none;
}
.video-play.-pause img:nth-child(2) {
display: none;
}
.video-muted:not(.-soundon) img:nth-child(2) {
display: none;
}
.video-muted.-soundon img:nth-child(1) {
display: none;
}
.video-fullscreen,
.video-forward {
width: 1.8rem;
margin-left: 1.0rem;
}
.video-forward {
display: none !important;
}
.video-muted {
margin-left: 0.4rem;
}
.video-muted img {
width: 20px;
}
.video-timeline {
-ms-flex: 1;
flex: 1;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
border: none;
position: relative;
}
.video-timeline input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 100%;
height: 2px; /* スライダーの高さをバーと一致させる */
background: transparent;
cursor: pointer;
position: absolute;
}
.video-timeline input::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 0; /* つまみの幅を0に設定 */
height: 0; /* つまみの高さを0に設定 */
background: transparent; /* 背景を透明にする */
}
.video-timeline input::-moz-range-thumb {
-moz-appearance: none;
appearance: none;
width: 0; /* つまみの幅を0に設定 */
height: 0; /* つまみの高さを0に設定 */
background: transparent; /* 背景を透明にする */
}
.video-timeline input::-ms-thumb {
-ms-appearance: none;
appearance: none;
width: 0; /* つまみの幅を0に設定 */
height: 0; /* つまみの高さを0に設定 */
background: transparent; /* 背景を透明にする */
}
.video-progressBar {
background: #a4a4a4;
background-color: #a4a4a4;
height: 2px;
-ms-flex: 1;
flex: 1;
appearance: none;
position: absolute;
width: 100%;
border: none;
}
.video-progressBar::-webkit-progress-bar {
background-color: #a4a4a4; /* 背景色 */
}
.video-progressBar::-webkit-progress-value {
background: #fff; /* 進行部分の色 */
background-color: #fff;
}
.video-progressBar::-moz-progress-bar {
background: #a4a4a4; /* 背景色 */
}
.video-timeline input::-moz-range-progress {
background: #fff; /* 進行部分の色 */
}
.video-progressBar::-ms-fill {
background: #fff; /* 進行部分の色 */
}
.video-container:hover .video-controls {
opacity: 1;
}
:fullscreen {
/* フルスクリーン時のスタイル */
width: 100%;
height: 100%;
}
:-webkit-full-screen {
/* Safari用のフルスクリーンスタイル */
width: 100%;
height: 100%;
}
input.video-seek {
margin: 0;
padding: 0;
background: none;
border: none;
border-radius: 0;
outline: none;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
プレイヤーの部分のデザイン表示のためのcssたちです。
以上、メモになります。
いろいろな方法があると思います。
今回私はライブラリのjs改修はうまくいかず、諦めてプレーンな状態からスタートさせました。
参考資料置き場
電脳情報局
https://www.omakase.net/blog/2021/12/videojs.html
vimeoダウンロード
https://vimeo.com/jp/features/video-editor/download-video
ICOOON MONO
https://icooon-mono.com/?s=%E3%82%B9%E3%83%94%E3%83%BC%E3%82%AB%E3%83%BC