这不是打算用于本站的东西(如果有需要,我会直接修改aplayer的样式),只是一个小练习
很简单的小练习,请各位大佬海涵
效果 当音乐播放到某一特定位置时,歌词出现滚动高亮效果
样式 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 * { margin : 0 ; padding : 0 ; } .container { width : 100vw ; height : 100vh ; background-color : #222 ; display : flex; flex-direction : column; align-items : center; padding : 20px ; box-sizing : border-box; overflow : hidden; } .player { display : flex; justify-content : center; padding : 20px ; } .player audio { width : 50vw ; } .lyric { width : 100% ; height : 100% ; color : #888 ; overflow : hidden; } .lyric ul { padding : 20px ; transform : translateY (0px ); transition : 0.2s ; } .lyric li { list-style : none; text-align : center; white-space : nowrap; overflow : hidden; height : 30px ; line-height : 30px ; transition : 0.2s ; } .lyric li .active { color : white; transform : scale (1.25 ); }
结构与行为 测试数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export default `[00:13.445]Digital affection [00:17.693]Mechanical perfection [00:20.688]I was made for you [00:26.691]Outer world dimension [00:30.185]Hardwired connection [00:38.124]We can have it all tonight [00:38.623] [00:39.122]Nothing matters when I'm with you baby [00:41.876]Plug me in like your electric lady [00:45.825]Nothing matters when I’m with you baby [00:47.833]Plug me in like your electric lady [01:01.324]Let me in like you electric baby [01:19.074] [01:19.331]Electric [01:23.825]Nothing matters when I'm with you baby [01:26.574]Let me in like you electric baby [01:29.581] [01:40.333]Let me in like you electric baby [01:55.833] [01:56.331]Your electric [02:02.577]Nothing matters when I'm with you baby [02:05.073]Let me in like you electric baby`
这是我从网易云音乐弄来的,这个平台的歌词就是这种格式,不知道这是不是通用的
编码:
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 166 167 168 169 <!DOCTYPE html > <html lang ="en" > <head > <title > Document</title > <link rel ="stylesheet" href ="./css/index.css" > </head > <body > <div class ="container" > <div class ="player" > <audio controls src ="./assets/Electric Lady (feat. XO ELIZA).flac" > </audio > </div > <div class ="lyric" > <ul > </ul > </div > </div > <script type ="module" > import lrcStr from './js/data.js' const DOMs = { lyricContainer : document .querySelector ('.lyric' ), lyricUl : document .querySelector ('ul' ), audio : document .querySelector ('audio' ) } const parseLrc = (lrcStr ) => { return lrcStr.split ('\n' ).map (l => { let [time, words] = l.split (']' ) time = parseTime (time.substring (1 )) return { time, words } }) } const parseTime = (timeStr ) => { const [minute, second] = timeStr.split (':' ) return minute * 60 + +second } const findIndex = (lyric ) => { const currentTime = DOMs .audio .currentTime for (let i = 0 ; i < lyric.length ; i++) { if (currentTime < lyric[i].time ) { return i - 1 } } return lyric.length - 1 } const lyric = parseLrc (lrcStr) const init = ( ) => { const frag = document .createDocumentFragment () lyric.forEach (l => { const li = document .createElement ('li' ) li.textContent = l.words li.dataset .time = l.time frag.appendChild (li) }) DOMs .lyricUl .appendChild (frag) } init () let containerHeight let lrcUlHeight let lrcUlPaddingT let maxUlOffset let lrcLiHeight const computeData = ( ) => { containerHeight = DOMs .lyricContainer .clientHeight lrcUlHeight = DOMs .lyricUl .clientHeight lrcUlPaddingT = Number (getComputedStyle (DOMs .lyricUl )['padding-top' ].split ('p' )[0 ]) maxUlOffset = lrcUlHeight - containerHeight < 0 ? 0 : lrcUlHeight - containerHeight lrcLiHeight = DOMs .lyricUl .children [0 ].clientHeight } const observer = new ResizeObserver (computeData) observer.observe (DOMs .lyricContainer , { box : 'border-box' }) computeData () const setOffset = ( ) => { const index = findIndex (lyric) const lrcHeight = lrcLiHeight * index + lrcLiHeight / 2 let ulOffset = lrcUlPaddingT + lrcHeight - containerHeight / 2 if (ulOffset < 0 ) ulOffset = 0 if (ulOffset > maxUlOffset) ulOffset = maxUlOffset DOMs .lyricUl .style .transform = `translateY(-${ulOffset} px)` const oldLi = DOMs .lyricUl .querySelector ('.active' ) if (oldLi) oldLi.classList .remove ('active' ) const newLi = DOMs .lyricUl .children [index] if (newLi) newLi.classList .add ('active' ) } DOMs .audio .addEventListener ('timeupdate' , setOffset) </script > </body > </html >
因为要确保高亮歌词显示在中心(除非它位于开头或结尾),所以需要监听歌词容器的高度,每当它发生变化时,需要重新计算这些与偏移量相关的数值
ResizeObserver 类似于MutationObserver
,但它专门监听元素的几何尺寸,主流电脑端浏览器都已兼容
发散 如果需要允许用户滚动歌词怎么办?
那就把偏移量实现换成滚动条实现,应该是这样
但用户在浏览所有歌词时,由于播放时间改变事件持续触发,很有可能出现内容动不动就滑走的情况
所以我认为可能需要一个计时器控制flag,这个flag应该代表用户是否有在浏览歌词,正在浏览时则不允许播放时间改变事件滑动歌词
用户滑动歌词就相当于正在浏览,若用户若干秒内没有再进行滑动,则相当于用户并未在浏览