<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://kit.fontawesome.com/412379eca8.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="style.css">
<title>음악 플레이어</title>
</head>
<body>
<div class="container">
<h1>음악 플레이어</h1>
<div class="music-container" id="musicContainer">
<div class="music-info">
<h4 id="title">노래 제목</h4>
<div class="progress-container" id="progress-container">
<div class="progress" id="progress"></div>
</div>
</div>
<audio id="audio" src="./music/hey.mp3" onloadstart="this.volume=0.005"></audio>
<div class="img-container">
<img src="./images/summer.jpg" alt="cover" id="cover">
</div>
<div class="navigation">
<button id="prev" class="action-btn"><i class="fa-sharp fa-solid fa-backward"></i></button>
<button id="play" class="actiong-btn big"><i class="fa-solid fa-play"></i></button>
<button id="next" class="actiong-btn"><i class="fa-sharp fa-solid fa-forward"></i></button>
</div>
</div>
</div>
<script src="main.js"> </script>
</body>
</html>
* {
box-sizing: border-box;
}
body {
margin: 0;
background: linear-gradient(to bottom, rgb(222, 185, 185), #fff);
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
.music-container {
position: relative;
margin: 100px 0;
width: 400px;
height: 100px;
border-radius: 10px;
background-color: #fff;
box-shadow: 1px 20px 20px rgb(222, 185, 185);
}
.music-info {
position: absolute;
padding: 10px 10px 10px 150px;
height: 50px;
width: 350px;
top: 0;
left: 25;
background-color: rgb(219, 193, 193);
border-radius: 10px;
opacity: 0;
transform: translateY(0%);
transition: transform 0.3s ease-in;
z-index: 0;
animation-play-state: paused;
}
.music-container.play .music-info {
animation: showMusicInfo 0.5s forwards;
transform: translateY(-100%);
animation-play-state: running;
}
@keyframes showMusicInfo {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.music-info h4 {
margin: 0;
}
.progress-container {
margin: 5px 0;
background-color: #fff;
width: 100%;
border-radius: 5px;
cursor: pointer;
}
.progress {
background-color: #fe8daa;
height: 4px;
width: 0%;
border-radius: 5px;
transition: width 0.1s linear;
}
.img-container {
position: absolute;
top: -30;
left: 40;
width: 110px;
height: 110px;
}
.img-container::after {
content: '';
position: absolute;
top: 40;
left: 40;
width: 20px;
height: 20px;
background-color: #fff;
border-radius: 50%;
z-index: 10;
}
.img-container img {
width: 100px;
height: 100px;
border-radius: 50%;
}
.navigation {
position: absolute;
right: 120;
bottom: 10;
width: 100px;
display: flex;
}
.navigation button {
color: rgb(223, 214, 214);
background-color: #fff;
border: none;
padding: 10px;
margin: 10px;
cursor: pointer;
font-size: 25px;
}
.actiong-btn.big {
font-size: 40px;
}
.music-container.play .img-container img {
animation: rotate 3s infinite linear;
animation-play-state: running;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
const playBtn = document.getElementById("play");
const musicContainer = document.getElementById("musicContainer");
const audio = document.getElementById("audio");
const prevBtn = document.getElementById("prev");
const nextBtn = document.getElementById("next");
const progress = document.getElementById("progress");
const progressContainer = document.getElementById('progress-container');
const imgCover = document.getElementById("cover");
const title = document.getElementById("title");
const songs = ["hey", "summer", "ukulele"];
let songIndex = 2;
loadSong(songs[songIndex]);
function loadSong(song) {
title.innerText = song;
audio.src = `http://127.0.0.1:5500/music/${song}.mp3`;
imgCover.src = `http://127.0.0.1:5500/images/${song}.jpg`;
}
function playMusic() {
musicContainer.classList.add("play");
playBtn.innerHTML = `<i class="fa-solid fa-pause"></i>`;
audio.play();
}
function pauseMusic(){
musicContainer.classList.remove('play');
playBtn.innerHTML = `<i class="fa-solid fa-play"></i>`;
audio.pause();
}
function playPrevSong() {
songIndex--;
if (songIndex < 0) {
songIndex = songs.length - 1;
}
loadSong(songs[songIndex]);
playMusic();
}
function playNextSong (){
songIndex++;
if(songIndex > 2){
songIndex = 0;
}
loadSong(songs[songIndex]);
playMusic();
}
function updateProgress(e){
const {duration, currentTime} = e.srcElement;
const progressPer = (currentTime / duration) * 100;
progress.style.width = `${progressPer}%`;
}
function changeProgress(e){
const width = e.target.clientWidth; // 전체 넓이
const offsetX = e.offsetX; // 현재 x 좌표;
const duration = audio.duration; // 재생길이
audio.currentTime = (offsetX / width) * duration;
}
playBtn.addEventListener("click", () => {
const isPlaying = musicContainer.classList.contains('play');
if(isPlaying){
pauseMusic();
} else{
playMusic();
}
});
prevBtn.addEventListener("click", playPrevSong);
nextBtn.addEventListener('click', playNextSong);
audio.addEventListener('ended', playNextSong);
audio.addEventListener('timeupdate', updateProgress);
progressContainer.addEventListener('click', changeProgress);
서로 겹치는 섹션이 많다보니 CSS position 배치가 굉장히 어려웠다.
음악이 재생될때 music-info 의 opacity가 0에서 1로 서서히 바뀌어야했는데, 1로 바뀌자마자 music-info 가 사라지는 오류가 발생했다. animation-fill-mode 에서 forwards 속성을 이용해서 애니메이션의 마지막 적용값을 유지시켜주었다. 반대로 backwards 속성을 적용시키면 애니메이션 적용 전의 속성으로 유지된다.
progress 바 이동 이벤트를 만들때 전체 넓이를 구할때는 e.target을 쓰고 현재의 x좌표를 구할때는 e.offsetX 를 사용했는데 차이점을 구분해야했다.