Javascript30 - Custom Video Player

seeh_h·2021년 1월 23일
1

javascript30

목록 보기
11/16
post-thumbnail

이번 시간에는 html에서 기본으로 제공하는 video tag의 재생 bar를 커스터마이징 해보았다. 😁

Learning Point

  • html video tag 및 관련 속성에 대해 공부한다.
  • event의 offsetX,Y 및 element의 offsetWidth 속성에 대해 공부한다.

HTML Part

<!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>

CSS Part

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;
}

Javascript Part

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

profile
주니어 개발자 성장기😎

0개의 댓글