이번 시간에는 html에서 기본으로 제공하는 video tag의 재생 bar를 커스터마이징 해보았다. 😁
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Video Player</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="player">
<video class="player__video viewer" src="652333414.mp4"></video>
<div class="player__controls">
<div class="progress">
<div class="progress__filled"></div>
</div>
<button class="player__button toggle" title="Toggle Play">►</button>
<input
type="range"
name="volume"
class="player__slider"
min="0"
max="1"
step="0.05"
value="1"
/>
<input
type="range"
name="playbackRate"
class="player__slider"
min="0.5"
max="2"
step="0.1"
value="1"
/>
<button data-skip="-10" class="player__button">« 10s</button>
<button data-skip="25" class="player__button">25s »</button>
</div>
</div>
<script src="scripts.js"></script>
</body>
</html>
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 0;
padding: 0;
display: flex;
background: #7A419B;
min-height: 100vh;
background: linear-gradient(135deg, #7c1599 0%,#921099 48%,#7e4ae8 100%);
background-size: cover;
align-items: center;
justify-content: center;
}
.player {
max-width: 750px;
border: 5px solid rgba(0,0,0,0.2);
box-shadow: 0 0 20px rgba(0,0,0,0.2);
position: relative;
font-size: 0;
overflow: hidden;
}
/* This css is only applied when fullscreen is active. */
.player:fullscreen {
max-width: none;
width: 100%;
}
.player:-webkit-full-screen {
max-width: none;
width: 100%;
}
.player__video {
width: 100%;
}
.player__button {
background: none;
border: 0;
line-height: 1;
color: white;
text-align: center;
outline: 0;
padding: 0;
cursor: pointer;
max-width: 50px;
}
.player__button:focus {
border-color: #ffc600;
}
.player__slider {
width: 10px;
height: 30px;
}
.player__controls {
display: flex;
position: absolute;
bottom: 0;
width: 100%;
transform: translateY(100%) translateY(-5px);
transition: all .3s;
flex-wrap: wrap;
background: rgba(0,0,0,0.1);
}
.player:hover .player__controls {
transform: translateY(0);
}
.player:hover .progress {
height: 15px;
}
.player__controls > * {
flex: 1;
}
.progress {
flex: 10;
position: relative;
display: flex;
flex-basis: 100%;
height: 5px;
transition: height 0.3s;
background: rgba(0,0,0,0.5);
cursor: ew-resize;
}
.progress__filled {
width: 50%;
background: #ffc600;
flex: 0;
flex-basis: 50%;
}
/* unholy css to style input type="range" */
input[type=range] {
-webkit-appearance: none;
background: transparent;
width: 100%;
margin: 0 5px;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 8.4px;
cursor: pointer;
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0);
background: rgba(255,255,255,0.8);
border-radius: 1.3px;
border: 0.2px solid rgba(1, 1, 1, 0);
}
input[type=range]::-webkit-slider-thumb {
height: 15px;
width: 15px;
border-radius: 50px;
background: #ffc600;
cursor: pointer;
-webkit-appearance: none;
margin-top: -3.5px;
box-shadow:0 0 2px rgba(0,0,0,0.2);
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #bada55;
}
input[type=range]::-moz-range-track {
width: 100%;
height: 8.4px;
cursor: pointer;
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0);
background: #ffffff;
border-radius: 1.3px;
border: 0.2px solid rgba(1, 1, 1, 0);
}
input[type=range]::-moz-range-thumb {
box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0);
height: 15px;
width: 15px;
border-radius: 50px;
background: #ffc600;
cursor: pointer;
}
const video = document.querySelector(".player__video");
const playButton = document.querySelector(".player__button");
const skipButtons = document.querySelectorAll("[data-skip]");
const ranges = document.querySelectorAll(".player__slider");
const progress = document.querySelector(".progress");
const progressBar = document.querySelector(".progress__filled");
const videoPlay = () => {
if (playButton.classList.contains("playing")) {
video.pause();
playButton.innerHTML = "►";
} else {
video.play();
playButton.innerHTML = "❚❚";
}
playButton.classList.toggle("playing");
};
const videoSkip = (e) => {
video.currentTime += parseFloat(e.target.dataset.skip);
};
const handleRangeUpdate = (e) => {
video[e.target.name] = e.target.value;
};
const handleProgressBar = () => {
const percent = (video.currentTime / video.duration) * 100;
progressBar.style.flexBasis = `${percent}%`;
};
const scrub = (e) => {
console.log(progress.offsetWidth);
const time = (e.offsetX / progress.offsetWidth) * video.duration;
video.currentTime = time;
};
playButton.addEventListener("click", videoPlay);
video.addEventListener("timeupdate", handleProgressBar);
skipButtons.forEach((button) => {
button.addEventListener("click", videoSkip);
});
ranges.forEach((slider) => {
slider.addEventListener("change", handleRangeUpdate);
});
ranges.forEach((slider) => {
slider.addEventListener("mousemove", handleRangeUpdate);
});
progress.addEventListener("click", scrub);
event의 pageX,Y와 ClientX,Y에 대해서만 알고 있었는데, 해당 element를 기준으로 하는 event.offsetX,Y도 있다는 것을 알게되었다. 😎😎
또한 element의 layout width를 return해주는 element.offsetWidth 속성에 대해서도 배웠다.
event.offsetX와 기본 재생 bar의 offsetWidth를 이용해서 재생 bar의 특정 부분을 클릭하면 해당하는 시간으로 바로 이동하는 부분을 구현할 수 있었다.
offsetWidth와 offsetHeight에 대해 아래의 그림을 보면 이해가 쉬울 것 같다.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetWidth