Music Player Using HTML, CSS & Javascript

5 min read
Music Player Demo

Project Overview

This project showcases a sleek and functional Music Player built using HTML, CSS, and JavaScript. The player features a minimalist design with essential controls for playback, track navigation, and a progress bar. It includes a dynamic album art display that changes with each song and a collapsible playlist view. The player is designed to be highly interactive, providing a smooth user experience for listening to music directly in the browser.

Key functionalities include playing, pausing, skipping to the next or previous song, and a progress bar that updates in real-time and allows seeking. The playlist can be toggled open or closed, revealing a list of songs with their respective album art, names, and durations. The design emphasizes clean lines and a responsive layout, ensuring the music player looks great and functions seamlessly on various screen sizes. This project is a comprehensive example of building a modern web-based audio player with a focus on user-friendly design and interactive features.

HTML Structure

The HTML structure for the Music Player is organized into a main `.player` container. Inside, it has a `.player__header` which includes the album art slider (`.slider`) and control buttons (playlist, play/pause). The `.slider` contains multiple `img` elements for album covers. Below the header is `.player__controls` with back, next buttons, a song context display (`.slider__context` for song name and title), and a progress bar (`.progres`). The playlist is a separate `ul` with the class `.player__playlist`, containing multiple `li` elements, each representing a song (`.player__song`). Each song `li` includes its own album art, song name, artist title, duration, and an `audio` tag for the music file. This structure allows for a clear separation of concerns and easy manipulation of elements via CSS and JavaScript.


<!DOCTYPE html>
<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">
    <title>Music Player Code</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="player">
        <div class="player__header">
        <div class="player__img player__img--absolute slider">
        <button class="player__button player__button--absolute--nw playlist">
        <img src="http://physical-authority.surge.sh/imgs/icon/playlist.svg" alt="playlist-icon">
        </button>
        <button class="player__button player__button--absolute--center play">
        <img src="http://physical-authority.surge.sh/imgs/icon/play.svg" alt="play-icon">
        <img src="http://physical-authority.surge.sh/imgs/icon/pause.svg" alt="pause-icon">
        </button>
        <div class="slider__content">
        <img class="img slider__img" src="http://physical-authority.surge.sh/imgs/1.jpg" alt="cover">
        <img class="img slider__img" src="http://physical-authority.surge.sh/imgs/2.jpg" alt="cover">
        <img class="img slider__img" src="http://physical-authority.surge.sh/imgs/3.jpg" alt="cover">
        <img class="img slider__img" src="http://physical-authority.surge.sh/imgs/4.jpg" alt="cover">
        <img class="img slider__img" src="http://physical-authority.surge.sh/imgs/5.jpg" alt="cover">
        <img class="img slider__img" src="http://physical-authority.surge.sh/imgs/6.jpg" alt="cover">
        <img class="img slider__img" src="http://physical-authority.surge.sh/imgs/7.jpg" alt="cover">
        </div>
        </div>
        <div class="player__controls">
        <button class="player__button back">
        <img class="img" src="http://physical-authority.surge.sh/imgs/icon/back.svg" alt="back-icon">
        </button>
        <p class="player__context slider__context">
        <strong class="slider__name"></strong>
        <span class="player__title slider__title"></span>
        </p>
        <button class="player__button next">
        <img class="img" src="http://physical-authority.surge.sh/imgs/icon/next.svg" alt="next-icon">
        </button>
        <div class="progres">
        <div class="progres__filled"></div>
        </div>
        </div>
        </div>
        <ul class="player__playlist list">
        <li class="player__song">
        <img class="player__img img" src="http://physical-authority.surge.sh/imgs/1.jpg" alt="cover">
        <p class="player__context">
        <b class="player__song-name">no time</b>
        <span class="flex">
        <span class="player__title">lastlings</span>
        <span class="player__song-time"></span>
        </span>
        </p>
        <audio class="audio" src="http://physical-authority.surge.sh/music/1.mp3"></audio>
        </li>
        <li class="player__song">
        <img class="player__img img" src="http://physical-authority.surge.sh/imgs/2.jpg" alt="cover">
        <p class="player__context">
        <b class="player__song-name">blinding lights</b>
        <span class="flex">
        <span class="player__title">the weeknd</span>
        <span class="player__song-time"></span>
        </span>
        </p>
        <audio class="audio" src="http://physical-authority.surge.sh/music/2.mp3"></audio>
        </li>
        <li class="player__song">
        <img class="player__img img" src="http://physical-authority.surge.sh/imgs/3.jpg" alt="cover">
        <p class="player__context">
        <b class="player__song-name">джованна</b>
        <span class="flex">
        <span class="player__title">enrasta</span>
        <span class="player__song-time"></span>
        </span>
        </p>
        <audio class="audio" src="http://physical-authority.surge.sh/music/3.mp3"></audio>
        </li>
        <li class="player__song">
        <img class="player__img img" src="http://physical-authority.surge.sh/imgs/4.jpg" alt="cover">
        <p class="player__context">
        <b class="player__song-name">a man</b>
        <span class="flex">
        <span class="player__title">travis scott</span>
        <span class="player__song-time"></span>
        </span>
        </p>
        <audio class="audio" src="http://physical-authority.surge.sh/music/4.mp3"></audio>
        </li>
        <li class="player__song">
        <img class="player__img img" src="http://physical-authority.surge.sh/imgs/5.jpg" alt="cover">
        <p class="player__context ">
        <b class="player__song-name">unforgetting</b>
        <span class="flex">
        <span class="player__title">zaxx</span>
        <span class="player__song-time"></span>
        </span>
        </p>
        <audio class="audio" src="http://physical-authority.surge.sh/music/5.mp3"></audio>
        </li>
        <li class="player__song">
        <img class="player__img img" src="http://physical-authority.surge.sh/imgs/6.jpg" alt="cover">
        <p class="player__context">
        <b class="player__song-name">waharan</b>
        <span class="flex">
        <span class="player__title">Randall</span>
        <span class="player__song-time"></span>
        </span>
        </p>
        <audio class="audio" src="http://physical-authority.surge.sh/music/6.mp3"></audio>
        </li>
        <li class="player__song">
        <img class="player__img img" src="http://physical-authority.surge.sh/imgs/7.jpg" alt="cover">
        <p class="player__context ">
        <b class="player__song-name">starlight feat mr gabriel <span class="uppercase">4am</span> remix</b>
        <span class="flex">
        <span class="player__title">jai wolf</span>
        <span class="player__song-time"></span>
        </span>
        </p>
        <audio class="audio" src="http://physical-authority.surge.sh/music/7.mp3"></audio>
        </li>
        </ul>
        </div>
        <!-- Javascript -->
        <script src="app.js"></script>
</body>
</html>
                        

CSS Styling

The CSS for the Music Player defines a modern and compact design, with a focus on smooth transitions and user experience. It imports the 'Quicksand' font and sets up root variables for animation durations and cubic-bezier timings, ensuring consistent and fluid animations. The `body` is centered and styled with a subtle background transition. Core styles for images (`.img`) and lists (`.list`) are provided. The main `.player` container is styled as a rounded, white card with a shadow, and its height dynamically adjusts. The `.player__header` is designed to expand and collapse, revealing or hiding the playlist. The album art slider (`.slider`) uses `transform` for smooth transitions between images, and the text within the slider context (`.slider__context`) has a `text-wrap` animation for long titles. Buttons are styled for interactive elements, and the progress bar (`.progres`) provides visual feedback with a filled section and a draggable handle. The `.player__playlist` is styled for a scrollable list of songs, with each song item having its own cover, name, artist, and time. Overall, the CSS creates a visually appealing and highly functional music player interface.


@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@700&display=swap');
html {
box-sizing: border-box ;
--parent-height : 20em ;
--duration: 1s ;
--duration-text-wrap: 12s 1.5s cubic-bezier(0.82, 0.82, 1, 1.01) ;
--cubic-header: var(--duration) cubic-bezier(0.71, 0.21, 0.3, 0.95) ;
--cubic-slider : var(--duration) cubic-bezier(0.4, 0, 0.2, 1) ;
--cubic-play-list : .35s var(--duration) cubic-bezier(0, 0.85, 0.11, 1.64) ;
--cubic-slider-context : cubic-bezier(1, -0.01, 1, 1.01) ;
}
html *,
html *::before,
html *::after {
box-sizing: inherit ;
scrollbar-width: none ;
}
body{
margin: 0 ;
height: 100vh ;
display: flex ;
user-select: none ;
align-items: center ;
justify-content: center ;
background-color: #e5e7e9 ;
font-family: 'Quicksand', sans-serif ;
-webkit-tap-highlight-color: transparent ;
transition: background-color var(--cubic-slider) ;
}
.img {
width: 100% ;
flex-shrink: 0;
display: block ;
object-fit: cover ;
}
.list {
margin: 0 ;
padding: 0 ;
list-style-type: none ;
}
.flex {
display: flex ;
align-items: center ;
justify-content: space-between ;
}
.uppercase{
text-transform: uppercase ;
}
.player {
width: 17.15em ;
display: flex ;
overflow: hidden ;
font-size: 1.22em ;
border-radius: 1.35em ;
flex-direction: column ;
background-color: white ;
height: var(--parent-height) ;
}
.player__header {
z-index: 1 ;
gap: 0 .4em ;
width: 100% ;
display: flex;
height: 5.85em ;
flex-shrink: 0 ;
position: relative;
align-items: flex-start ;
border-radius: inherit ;
justify-content: flex-end ;
background-color: white ;
padding: .95em 0.6em 0 1.2em ;
box-shadow: 0 2px 6px 1px #0000001f ;
transition: height var(--cubic-header), box-shadow var(--duration), padding var(--duration) ease-in-out ;
}
.player__header.open-header {
height: 100% ;
padding-left: 0 ;
padding-right: 0 ;
box-shadow: unset ;
}
.player__img {
width: 3.22em ;
height: 3.22em ;
border-radius: 1.32em ;
}
.player__img--absolute {
top: 1.4em ;
left: 1.2em ;
position: absolute ;
}
.slider {
flex-shrink: 0 ;
overflow: hidden ;
transition: width var(--cubic-header), height var(--cubic-header), top var(--cubic-header), left var(--cubic-header);
}
.slider.open-slider{
top: 0 ;
left: 0 ;
width: 100% ;
height: 14.6em ;
}
.slider__content {
display: flex ;
height: 100% ;
will-change : transform ;
transition: transform var(--cubic-slider);
}
.slider__img {
filter: brightness(75%) ;
}
.slider__name,
.slider__title {
overflow: hidden ;
white-space: nowrap ;
}
.text-wrap {
display: block ;
white-space: pre ;
width: fit-content ;
animation: text-wrap var(--duration-text-wrap) infinite ;
}
@keyframes text-wrap {
75%{
transform: translate3d(-51.5%, 0, 0) ;
}
100%{
transform: translate3d(-51.5%, 0, 0) ;
}
}
.player__button {
all: unset ;
z-index: 100 ;
width: 2.5em ;
height: 2.5em ;
cursor: pointer ;
}
.playlist {
transform: scale(0) ;
transition: transform calc(var(--duration) / 2) ;
}
.slider.open-slider .playlist {
transform: scale(1) ;
transition: transform var(--cubic-play-list) ;
}
.player__button--absolute--nw {
top: 5.5% ;
left: 5.5% ;
position: absolute ;
}
.player__button--absolute--center {
top: 0 ;
left: 0 ;
right: 0 ;
bottom: 0 ;
margin: auto ;
position: absolute ;
}
img[alt ="pause-icon"] {
display: none ;
}
.player__controls {
width: 77% ;
gap: .5em 0 ;
display: flex ;
flex-wrap: wrap ;
align-items: center ;
will-change: contents ;
align-content: center ;
justify-content: center ;
transition: transform var(--cubic-header) , width var(--cubic-header) ;
}
.player__controls.move {
width: 88% ;
transform: translate3d(-1.1em , calc(var(--parent-height) - 153%) , 0) ;
}
.player__context {
margin: 0 ;
width: 100% ;
display: flex ;
line-height: 1.8 ;
flex-direction: column ;
justify-content: center ;
text-transform: capitalize ;
}
.slider__context {
width: 56.28% ;
cursor: pointer ;
text-align: center ;
padding-bottom: .2em ;
will-change: contents ;
transition: width var(--cubic-header) ;
animation: calc(var(--duration) / 2) var(--cubic-slider-context) ;
}
@keyframes opacity {
0% {
opacity: 0 ;
}
90%{
opacity: 1 ;
}
}
.player__controls.move .slider__context{
width: 49.48% ;
}
.player__title {
font-size: .7em ;
font-weight: bold ;
color: #00000086 ;
}
.progres {
width: 90% ;
height: .25em ;
cursor: pointer ;
border-radius: 1em ;
touch-action : none ;
background-color: #e5e7ea ;
transition: width var(--cubic-header) ;
}
.player__controls.move .progres{
width: 98% ;
}
.progres__filled {
width: 0% ;
height: 100% ;
display: flex ;
position: relative ;
align-items: center ;
border-radius: inherit ;
background-color: #78adfe ;
}
.progres__filled::before {
right: 0 ;
width: .35em ;
content: " " ;
height: .35em ;
border-radius: 50% ;
position: absolute ;
background-color: #5781bd ;
}
.player__playlist {
height: 100% ;
overflow: auto ;
padding: 1.05em .9em 0 1.2em ;
}
.player__playlist::-webkit-scrollbar {
width: 0 ;
}
.player__song {
/* gap: 0 .65em ; */
display: flex ;
cursor: pointer ;
margin-bottom: .5em ;
padding-bottom: .7em ;
border-bottom: .1em solid #d8d8d859 ;
}
.player__song .player__context {
line-height: 1.5 ;
margin-left: .65em ;
}
.player__song-name {
font-size: .88em ;
}
.player__song-time {
font-size: .65em ;
font-weight: bold ;
color: #00000079 ;
height: fit-content ;
align-self: flex-end ;
}
.audio {
display: none !important ;}

                        

JavaScript

The JavaScript for the Music Player handles all the interactive functionality, from song playback to UI updates. It starts by selecting various DOM elements and initializing variables to manage the current song, playback state, and slider position. Key functions include `openPlayer()` and `closePlayer()` to toggle the playlist view, `next()` and `back()` to navigate through songs, and `changeSliderContext()` to update the displayed song name and title with a text-wrapping animation. `changeBgBody()` updates the background color of the `body` based on the current song. `selectSong()` ensures only one song plays at a time. The `playSong()` function toggles playback and updates the play/pause icons. `progresUpdate()` continuously updates the progress bar and automatically advances to the next song when the current one ends. `scurb()` allows users to click and drag on the progress bar to seek through the song. Event listeners are attached to the slider context to open the player, to the playlist button to close it, to the next/back buttons for navigation, and to the play button for playback control. Each song in the playlist also has a click listener to directly select and play that song. This comprehensive script ensures a fully interactive and responsive music player experience.


// Designed by: Mauricio Bucardo
// Original image: https://dribbble.com/shots/6957353-Music-Player-Widget
"use strict";
// add elemnts
const bgBody = ["#e5e7e9", "#ff4545", "#f8ded3", "#ffc382", "#f5eda6", "#ffcbdc", "#dcf3f3"];
const body = document.body;
const player = document.querySelector(".player");
const playerHeader = player.querySelector(".player__header");
const playerControls = player.querySelector(".player__controls");
const playerPlayList = player.querySelectorAll(".player__song");
const playerSongs = player.querySelectorAll(".audio");
const playButton = player.querySelector(".play");
const nextButton = player.querySelector(".next");
const backButton = player.querySelector(".back");
const playlistButton = player.querySelector(".playlist");
const slider = player.querySelector(".slider");
const sliderContext = slider.querySelector(".slider__context");
const sliderName = sliderContext.querySelector(".slider__name");
const sliderTitle = sliderContext.querySelector(".slider__title");
const sliderContent = slider.querySelector(".slider__content");
const sliderContentLength = playerPlayList.length - 1;
const sliderWidth = 100;
let left = 0;
let count = 0;
let song = playerSongs[count];
let isPlay = false;
const pauseIcon = playButton.querySelector("img[alt = 'pause-icon']");
const playIcon = playButton.querySelector("img[alt = 'play-icon']");
const progres = player.querySelector(".progres");
const progresFilled = progres.querySelector(".progres__filled");
let isMove = false;
// creat functions
function openPlayer() {
playerHeader.classList.add("open-header");
playerControls.classList.add("move");
slider.classList.add("open-slider");
}
function closePlayer() {
playerHeader.classList.remove("open-header");
playerControls.classList.remove("move");
slider.classList.remove("open-slider");
}
function next(index) {
count = index || count;
if (count == sliderContentLength) {
count = count;
return;
}
left = (count + 1) * sliderWidth;
left = Math.min(left, (sliderContentLength) * sliderWidth);
sliderContent.style.transform = `translate3d(-${left}%, 0, 0)`;
count++;
run();
}
function back(index) {
count = index || count;
if (count == 0) {
count = count;
return;
}
left = (count - 1) * sliderWidth;
left = Math.max(0, left);
sliderContent.style.transform = `translate3d(-${left}%, 0, 0)`;
count--;
run();
}
function changeSliderContext() {
sliderContext.style.animationName = "opacity";
sliderName.textContent = playerPlayList[count].querySelector(".player__title").textContent;
sliderTitle.textContent = playerPlayList[count].querySelector(".player__song-name").textContent;
if (sliderName.textContent.length > 16) {
const textWrap = document.createElement("span");
textWrap.className = "text-wrap";
textWrap.innerHTML = sliderName.textContent + " " + sliderName.textContent;
sliderName.innerHTML = "";
sliderName.append(textWrap);
}
if (sliderTitle.textContent.length >= 18) {
const textWrap = document.createElement("span");
textWrap.className = "text-wrap";
textWrap.innerHTML = sliderTitle.textContent + " " + sliderTitle.textContent;
sliderTitle.innerHTML = "";
sliderTitle.append(textWrap);
}
}
function changeBgBody() {
body.style.backgroundColor = bgBody[count];
}
function selectSong() {
song = playerSongs[count];
for (const item of playerSongs) {
if (item != song) {
item.pause();
item.currentTime = 0;
}
}
if (isPlay) song.play();
}
function run() {
changeSliderContext();
changeBgBody();
selectSong();
}
function playSong() {
if (song.paused) {
song.play();
playIcon.style.display = "none";
pauseIcon.style.display = "block";
}else{
song.pause();
isPlay = false;
playIcon.style.display = "";
pauseIcon.style.display = "";
}
}
function progresUpdate() {
const progresFilledWidth = (this.currentTime / this.duration) * 100 + "%";
progresFilled.style.width = progresFilledWidth;
if (isPlay && this.duration == this.currentTime) {
next();
}
if (count == sliderContentLength && song.currentTime == song.duration) {
playIcon.style.display = "block";
pauseIcon.style.display = "";
isPlay = false;
}
}
function scurb(e) {
// If we use e.offsetX, we have trouble setting the song time, when the mousemove is running
const currentTime = ( (e.clientX - progres.getBoundingClientRect().left) / progres.offsetWidth ) * song.duration;
song.currentTime = currentTime;
}
function durationSongs() {
let min = parseInt(this.duration / 60);
if (min < 10) min = "0" + min;
let sec = parseInt(this.duration % 60);
if (sec < 10) sec = "0" + sec;
const playerSongTime = `${min}:${sec}`;
this.closest(".player__song").querySelector(".player__song-time").append(playerSongTime);
}
changeSliderContext();
// add events
sliderContext.addEventListener("click", openPlayer);
sliderContext.addEventListener("animationend", () => sliderContext.style.animationName ='');
playlistButton.addEventListener("click", closePlayer);
nextButton.addEventListener("click", () => {
next(0)
});
backButton.addEventListener("click", () => {
back(0)
});
playButton.addEventListener("click", () => {
isPlay = true;
playSong();
});
playerSongs.forEach(song => {
song.addEventListener("loadeddata" , durationSongs);
song.addEventListener("timeupdate" , progresUpdate);
});
progres.addEventListener("pointerdown", (e) => {
scurb(e);
isMove = true;
});
document.addEventListener("pointermove", (e) => {
if (isMove) {
scurb(e);
song.muted = true;
}
});
document.addEventListener("pointerup", () => {
isMove = false;
song.muted = false;
});
playerPlayList.forEach((item, index) => {
item.addEventListener("click", function() {
if (index > count) {
next(index - 1);
return;
}
if (index < count) {
back(index + 1);
return;
}
});
});
                        

Live Demo: Music Player

Watch a live demonstration of the Music Player below.

Final Thoughts

This Music Player project provides a comprehensive example of building a responsive and interactive audio player for the web. Its clean design, dynamic album art, and intuitive controls make for an excellent user experience. By combining HTML for structure, CSS for aesthetics, and JavaScript for functionality, this project demonstrates how to create a feature-rich media player from scratch.

You can easily customize this component by:

  • Replacing the placeholder audio files and album art with your own music library.
  • Adjusting the CSS variables to change the player's color scheme and overall look.
  • Adding more advanced features like volume control, shuffle, repeat, or a search function.
  • Integrating with a backend to fetch music data dynamically.

Pro Tip

For a more robust music player, consider implementing a custom audio API wrapper to handle playback, buffering, and error states more gracefully. This can provide finer control over audio playback and improve compatibility across different browsers.

Ready to Use This Project?

Click the button below to download the full source code. Download will be ready in 10 seconds.

Stay Updated

Receive coding tips and resources updates. No spam.

We respect your privacy. Unsubscribe at any time.