製作一個音樂撥放器 使用 html CSS 以及 audio API
成品:
成品網址
成品功能:
1.點擊中間的撥放後左邊的唱盤會開始旋轉
2.並且上方會跳出視窗顯示歌名
3.點擊上一首下一首會直接跳轉
4.在暫停狀態下點擊上一首或是下一首會直接撥放並且跳出視窗顯示歌名
HTML:
上 CSS 之前的 HTML:
html 程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <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>
<script src="script.js"></script> </body>
|
小補充:
audio-MDN
CSS:
相關 CSS 設定:
- 背景使用漸層色處理
- 讓封面(cover)旋轉使用 animation
- 使用偽元素做出封面圓點
- 包含專輯名稱、進度條的跳出視窗做 transition 處理
- 進度條的 CSS 設置
背景使用漸層色處理
linear-gradient(產生位置角度,顏色 1 顏色 2) 後面%數代表梯度(顏色漸變的層度)
linear-gradient -w3cschool
1 2 3 4 5
| background-image: linear-gradient( 0deg, rgba(247, 247, 247, 1) 23.8%, rgba(252, 221, 221, 1) 92% );
|
CSS animation
CSS animaiton 縮寫順序:
animation: name | duration | timing-function | delay | iteration-count | direction | fill-mode | play-state;
keyframes
animation 的動作是來自於 CSS style 逐步的改變有兩種做法
- 0~100%的時間甚麼樣的 style 做甚麼事情
- from to 擺入不同的 css style 去做動畫(本專案使用這個)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| .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); } }
|
偽元素製作圓心
唱盤指針 使用 CSS 偽元素做出子元素圓點當作指針並且做定位
偽元素 -MDN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| .img-container { position: relative; width: 110px; }
.img-container::after { content: ""; background-color: #fff; border-radius: 50%; position: absolute; bottom: 100%; left: 50%; width: 10px; height: 10px; transform: translate(-50%, 50%); }
|
CSS transition
CSS Transition 縮寫順序: property | duration | timing-function | delay
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| .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%); }
|
專輯進度條 CSS 處理
使用 transition: width 0.1s linear
改變 width 讓寬度改變就會像一般撥放軟體再跑進度條一樣顯示了
1 2 3 4 5 6 7 8
| .progress { background-color: #fe8daa; border-radius: 5px; height: 100%; width: 0%; transition: width 0.1s linear; }
|
CSS 設置好的樣式
CSS 完整程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
| @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 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: 10px; height: 10px; 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-color: #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; }
|
小補充:
inherit
會繼承到他 parent 的 width 也就是 img-container width:110
width: inherit;
calc
是指 100%的 width 減去 40px 寬度就是取得的 width
width: calc(100% - 40px);
JS:
變數設置
musicContainer 區域
playBtn,prevBtn,nextBtn 區域
audio 區域
這部分是音檔嵌入在 html 裡面
progressContainer 區域
progress 區域
裡面的進度條
title
目前藏在 music-container 下方
cover
專輯封面
songs
['hey', 'summer', 'ukulele']
這邊設置歌曲名稱給下面做抓取
songIndex
一開始的畫面初始值的 index
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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');
const songs = ['hey', 'summer', 'ukulele']; let songIndex = 2;
|
functions:
雖然做了更新但是這邊專輯資訊還沒有跳出來
在撥放後才會跳出專輯視窗
- 上一首下一首相關:
prevSong
nextSong
這邊也會跳出視窗因為也會撥放歌曲
- 進度條相關
updatePorgress
setProgress
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
|
const songs = ['hey', 'summer', 'ukulele'];
let songIndex = 2;
loadSong(songs[songIndex]);
function loadSong(song) { title.innerText = song; audio.src = `music/${song}.mp3`; cover.src = `images/${song}.jpg`; }
function playSong() {
musicContainer.classList.add('play');
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(); }
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(); }
function updateProgress(e) {
const { duration, currentTime } = e.srcElement;
const progressPercent = (currentTime / duration) * 100; progress.style.width = `${progressPercent}%`; }
function setProgress(e) {
const width = this.clientWidth;
const clickX = e.offsetX;
const duration = audio.duration;
audio.currentTime = (clickX / width) * duration; }
|
事件監聽
- 撥放按鍵
- 上一首下一首
- 讓進度條跟著音檔進度走(監聽時間更新進度事件)
- 滑鼠點擊哪邊進度條走哪邊(監聽點擊事件)
- 歌曲結束(監聽結束事件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
playBtn.addEventListener('click', () => { const isPlaying = musicContainer.classList.contains('play');
if (isPlaying) { pauseSong(); } else { playSong(); } });
prevBtn.addEventListener('click', prevSong); nextBtn.addEventListener('click', nextSong);
audio.addEventListener('timeupdate', updateProgress);
progressContainer.addEventListener('click', setProgress)
audio.addEventListener('ended', nextSong);
|
小補充:
Audio currentTime Property MDN
Audio duration Property MDN
Ended Event MDN
clientWidth 的範圍:
JS 完整程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
| 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');
const songs = ['hey', 'summer', 'ukulele'];
let songIndex = 2;
loadSong(songs[songIndex]);
function loadSong(song) { title.innerText = song; audio.src = `music/${song}.mp3`; cover.src = `images/${song}.jpg`; }
console.dir(audio);
function playSong() {
musicContainer.classList.add('play');
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(); }
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(); }
function updateProgress(e) {
const { duration, currentTime } = e.srcElement;
const progressPercent = (currentTime / duration) * 100; progress.style.width = `${progressPercent}%`; }
function setProgress(e) {
const width = this.clientWidth;
const clickX = e.offsetX;
const duration = audio.duration;
audio.currentTime = (clickX / width) * duration;
}
playBtn.addEventListener('click', () => { const isPlaying = musicContainer.classList.contains('play');
if (isPlaying) { pauseSong(); } else { playSong(); } });
prevBtn.addEventListener('click', prevSong); nextBtn.addEventListener('click', nextSong);
audio.addEventListener('timeupdate', updateProgress);
progressContainer.addEventListener('click', setProgress)
audio.addEventListener('ended', nextSong);
|