Music Player Project
Now, we are going to create a really nice looking music player. This is the same player that we used in my 20 vanilla projects course. I figured, why create a new project that will show you the same exact thing? This player also has some CSS transitions and animations.
There is a folder called music with 3 songs in it. There is also a folder called images with 3 cover images. We will be using these songs and images for the player. In this project, the music file and image file should be the same.
The HTML
Let's go ahead and take a look at the HTML. It is really simple and minimal.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.min.css"
/>
<link rel="stylesheet" href="style.css" />
<script src="script.js" defer></script>
<title>Music Player</title>
</head>
<body>
<h1>Music Player</h1>
<div class="music-container" id="music-container">
<div class="music-info">
<h4 id="title"></h4>
<div class="progress-container" id="progress-container">
<div class="progress" id="progress"></div>
</div>
</div>
<audio src="music/ukulele.mp3" id="audio"></audio>
<div class="img-container">
<img src="images/ukulele.jpg" alt="music-cover" id="cover" />
</div>
<div class="navigation">
<button id="prev" class="action-btn">
<i class="fas fa-backward"></i>
</button>
<button id="play" class="action-btn action-btn-big">
<i class="fas fa-play"></i>
</button>
<button id="next" class="action-btn">
<i class="fas fa-forward"></i>
</button>
</div>
</div>
</body>
</html>
Notice, we did include the Font Awesome 5 CDN. We will be using some icons for the play, prev, next button, etc. We also have an area for the progress bar, an h4 for the title, and an image for the cover. We have the default audio file and image as the ukulele song, but this player will have 3 songs and cover images.
The CSS
Here is the CSS. I am not going to go over it all. It is pretty simple and self explanatory. The one thing I want to point out is the .play class. This class is applied to the music container via javascript. This is what makes the image rotate as well as the title slide up.
@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');
* {
box-sizing: border-box;
}
body {
background-image: linear-gradient(
0deg,
rgba(247, 247, 247, 1) 23.8%,
rgba(252, 221, 221, 1) 92%
);
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: 'Lato', sans-serif;
margin: 0;
}
.music-container {
background-color: #fff;
border-radius: 15px;
box-shadow: 0 20px 20px 0 rgba(252, 169, 169, 0.6);
display: flex;
padding: 20px 30px;
position: relative;
margin: 100px 0;
z-index: 10;
}
.img-container {
position: relative;
width: 110px;
}
.img-container::after {
content: '';
background-color: #fff;
border-radius: 50%;
position: absolute;
bottom: 100%;
left: 50%;
width: 20px;
height: 20px;
transform: translate(-50%, 50%);
}
.img-container img {
border-radius: 50%;
object-fit: cover;
height: 110px;
width: inherit;
position: absolute;
bottom: 0;
left: 0;
animation: rotate 3s linear infinite;
animation-play-state: paused;
}
.music-container.play .img-container img {
animation-play-state: running;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.navigation {
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
.action-btn {
background-color: #fff;
border: 0;
color: #dfdbdf;
font-size: 20px;
cursor: pointer;
padding: 10px;
margin: 0 20px;
}
.action-btn.action-btn-big {
color: #cdc2d0;
font-size: 30px;
}
.action-btn:focus {
outline: 0;
}
.music-info {
background-color: rgba(255, 255, 255, 0.5);
border-radius: 15px 15px 0 0;
position: absolute;
top: 0;
left: 20px;
width: calc(100% - 40px);
padding: 10px 10px 10px 150px;
opacity: 0;
transform: translateY(0%);
transition: transform 0.3s ease-in, opacity 0.3s ease-in;
z-index: 0;
}
.music-container.play .music-info {
opacity: 1;
transform: translateY(-100%);
}
.music-info h4 {
margin: 0;
}
.progress-container {
background: #fff;
border-radius: 5px;
cursor: pointer;
margin: 10px 0;
height: 4px;
width: 100%;
}
.progress {
background-color: #fe8daa;
border-radius: 5px;
height: 100%;
width: 0%;
transition: width 0.1s linear;
}
The JavaScript
Now for the fun part. First we will bring in all of the elements that we need.
const musicContainer = document.getElementById('music-container');
const playBtn = document.getElementById('play');
const prevBtn = document.getElementById('prev');
const nextBtn = document.getElementById('next');
const audio = document.getElementById('audio');
const progress = document.getElementById('progress');
const progressContainer = document.getElementById('progress-container');
const title = document.getElementById('title');
const cover = document.getElementById('cover');
Setup & Load Song
Next, we will create an array of songs and their cover images. Remember, they are named the same, so we just have an array with 3 titles/image names
const songs = ['hey', 'summer', 'ukulele'];
We need a way to keep track of the songs. So we will create a song index. We will start with the song with the index of 2.
let songIndex = 2;
Now we will load the song into the DOM. We will set the title, audio source, and cover image source.
loadSong(songs[songIndex]);
function loadSong(song) {
title.innerText = song;
audio.src = `music/${song}.mp3`;
cover.src = `images/${song}.jpg`;
}
Play & Pause Song
The play and pause button are the same, so we will have an event listener and then choose to run either the playSong or pauseSong function based on if the song is playing or not.
This is also where we will either add or remove the play class from the music container. This will make the image spin and display the song info.
playBtn.addEventListener('click', () => {
const isPlaying = musicContainer.classList.contains('play');
if (isPlaying) {
pauseSong();
} else {
playSong();
}
});
Let's create both methods
function playSong() {
musicContainer.classList.add('play'); // Makes image spin and display info
playBtn.querySelector('i.fas').classList.remove('fa-play');
playBtn.querySelector('i.fas').classList.add('fa-pause');
audio.play();
}
function pauseSong() {
musicContainer.classList.remove('play');
playBtn.querySelector('i.fas').classList.add('fa-play');
playBtn.querySelector('i.fas').classList.remove('fa-pause');
audio.pause();
}
Prev & Next Buttons
Now, let's add the prev and next functionality. We need to change the song index and then load the song.
prevBtn.addEventListener('click', prevSong);
nextBtn.addEventListener('click', nextSong);
function prevSong() {
songIndex--;
if (songIndex < 0) {
songIndex = songs.length - 1;
}
loadSong(songs[songIndex]);
playSong();
}
function nextSong() {
songIndex++;
if (songIndex > songs.length - 1) {
songIndex = 0;
}
loadSong(songs[songIndex]);
playSong();
}
Show Progress Bar
We need to show the progress bar. It should be relative to where in the song is being played. We listen for the timeupdate event on the audio element. This event is fired when the time indicated by the currentTime attribute has been updated. We will then calculate the percentage of the song that has been played and set the width of the progress bar.
audio.addEventListener('timeupdate', updateProgress);
function setProgress(e) {
const width = this.clientWidth;
const clickX = e.offsetX;
const duration = audio.duration;
audio.currentTime = (clickX / width) * duration;
}
Set Progress Bar
We also want to be able to click on the progress bar and have the song go to that point
progressContainer.addEventListener('click', setProgress);
function setProgress(e) {
const width = this.clientWidth;
const clickX = e.offsetX;
const duration = audio.duration;
audio.currentTime = (clickX / width) * duration;
}
That's it! We now have a cool little music player.