子比主题 – 左下角胶囊音乐样式代码
腾飞博客给大家写了一款仿张洪博客的胶囊音乐样式,我觉得这个还是不错的,一开始腾飞想写插件,发现插件有点卡,为了不影响速度直接用代码实现,这个算半成品,也算已经写好了,都可以用,话不多说喜欢的自行部署吧!当鼠标点了胶囊就有一个效果横向弹出来,然后播放音乐,当点了之后就收缩就自动停止音乐,模仿了张洪博客的样式,一开始写的是根据封面颜色,发现跨域问题,但是这个写插件可以解决,插件虽然写好了但是删掉了,因为太卡了,所以就代码分享吧,有技术的可以二开一下!
代码部署
将下面的代码放到:子比主题–>>自定义底部HTML代码,全部代码直接丢里面就可以!
<style>
:root{--player-height:44px;--capsule-radius:22px;--album-size:32px;--album-gap:9px;--lyrics-max-width:150px;--lyric-padding-right:12px;--main-bg:rgba(60,66,64,0.97);--main-bg2:rgba(70,74,80,0.98)}body{min-height:100vh;background:#f8f8f9}.tengfei-player-container{position:fixed;left:32px;bottom:40px;z-index:1000;min-width:166px;max-width:420px;width:340px;height:var(--player-height);border-radius:var(--capsule-radius);box-shadow:0 2px 9px rgba(0,0,0,0.18);background:linear-gradient(120deg,var(--main-bg) 88%,var(--main-bg2) 100%);display:flex;align-items:center;padding:0 10px;cursor:pointer;transition:width 0.6s cubic-bezier(.51,.33,.38,1.14),max-width 0.6s cubic-bezier(.51,.33,.38,1.14),min-width 0.6s cubic-bezier(.51,.33,.38,1.14),box-shadow 0.18s,background 0.8s;overflow:visible}@media (max-width:700px){.tengfei-player-container{display:none !important}}.tengfei-player-container.collapsed{width:auto !important;min-width:120px;max-width:280px;transition:width 0.5s cubic-bezier(.42,0,.11,1.05),max-width 0.29s cubic-bezier(.42,0,.11,1.05),min-width 0.22s cubic-bezier(.42,0,.11,1.05)}.song-title-area{display:flex;align-items:center;min-width:0;transition:opacity 0.39s cubic-bezier(.42,0,.11,1.05)}.tengfei-album-cover{width:var(--album-size);height:var(--album-size);object-fit:cover;border-radius:50%;margin-right:var(--album-gap);border:2px solid rgba(255,255,255,0.81);box-shadow:0 1px 4px rgba(0,0,0,0.09);flex-shrink:0;transition:box-shadow 0.25s,opacity 0.39s cubic-bezier(.42,0,.11,1.05)}.tengfei-song-title{color:white;font-size:13px;font-weight:500;min-width:34px;max-width:118px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:max-width .19s cubic-bezier(.42,0,.11,1.05),font-size .14s cubic-bezier(.42,0,.11,1.05),opacity 0.34s cubic-bezier(.42,0,.11,1.05);cursor:pointer;padding-right:2px;flex-shrink:2;display:inline-block}.center-lyric-area{flex-shrink:1;flex-grow:1;min-width:0;max-width:78vw;height:100%;display:flex;align-items:center;justify-content:center;position:relative;margin-left:16px;transition:margin-left 0.32s cubic-bezier(.42,0,.11,1.05),opacity 0.38s cubic-bezier(.42,0,.11,1.05);opacity:1}.lyrics-container{max-width:var(--lyrics-max-width);width:auto;white-space:nowrap;overflow:hidden;color:#fff;text-align:left;font-size:11.3px;font-weight:bold;letter-spacing:0.07em;line-height:1;pointer-events:none;position:relative;padding-right:var(--lyric-padding-right);text-overflow:ellipsis;flex:1 1 0;z-index:2;transition:max-width 0.38s cubic-bezier(.42,0,.11,1.05),font-size .14s cubic-bezier(.42,0,.11,1.05),opacity 0.5s cubic-bezier(.42,0,.11,1.05);background:none;opacity:1}.lyrics-line{display:block;opacity:1;pointer-events:none;transition:opacity 0.34s cubic-bezier(.5,.7,.7,1),color .17s,font-size .11s}.lyrics-current{color:#fff}.lyrics-next{color:rgba(255,255,255,0.29);font-size:9.5px}.lyrics-capsule-mask{position:absolute;top:0;bottom:0;right:0;pointer-events:none;border-radius:0 var(--capsule-radius) var(--capsule-radius) 0;background:linear-gradient(to left,rgba(183,182,182,0.65) 78%,transparent 100%);backdrop-filter:none;-webkit-backdrop-filter:none;opacity:0;z-index:13;transition:opacity 0.25s cubic-bezier(.42,0,.11,1.05),left 0.2s cubic-bezier(.42,0,.11,1.05)}.tengfei-player-container:not(.collapsed):hover .lyrics-capsule-mask{opacity:1}.controls{position:absolute;top:0;right:0px;height:var(--player-height);display:flex;align-items:center;gap:5px;z-index:16;padding-right:10px;pointer-events:none;opacity:1;transition:opacity 0.39s cubic-bezier(.42,0,.11,1.05)}.control-btn{background:rgba(255,255,255,0.09);border:none;color:white;cursor:pointer;width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:background-color .18s cubic-bezier(.42,0,.11,1.05),box-shadow .22s cubic-bezier(.42,0,.11,1.05),opacity .13s cubic-bezier(.42,0,.11,1.05),transform 0.32s cubic-bezier(.42,0,.11,1.05);opacity:.95;pointer-events:auto;box-shadow:0 1px 5px rgba(0,0,0,0.07);outline:none;border:0.1px solid rgba(225,225,225,0.09);font-size:0}.control-btn svg{display:block;width:21px;height:21px;pointer-events:none;transition:filter 0.13s,transform 0.13s;filter:drop-shadow(0 0 2px rgba(51,120,235,0.10));stroke:#fff}.control-btn:hover{background:rgba(255,255,255,0.19)}.control-btn:hover svg{filter:drop-shadow(0 0 5px #99c2ff60);transform:scale(1.14);stroke:#3caae7}.tengfei-player-container:not(:hover):not(.collapsed) .control-btn{opacity:0 !important;pointer-events:none;transform:scale(.89)}.tengfei-player-container:not(.collapsed):hover .control-btn{opacity:1 !important;pointer-events:auto;transform:scale(1)}.tengfei-player-container.collapsed .center-lyric-area,.tengfei-player-container.collapsed .controls,.tengfei-player-container.collapsed .lyrics-capsule-mask{opacity:0 !important;pointer-events:none !important;width:0 !important;margin:0 !important;display:none !important;transition:opacity 0.38s cubic-bezier(.42,0,.11,1.05)}.tengfei-player-container:not(.collapsed) .center-lyric-area,.tengfei-player-container:not(.collapsed) .controls{opacity:1;pointer-events:auto;display:flex !important;transition:opacity 0.38s cubic-bezier(.42,0,.11,1.05)}
</style>
</head>
<body>
<div class="tengfei-player-container collapsed" id="tengfeiMusicPlayer">
<div class="song-title-area">
<img class="tengfei-album-cover" src="" alt="专辑封面" id="tengfeiCoverClick">
<div class="tengfei-song-title" id="tengfeiTitleClick">正在加载...</div>
</div>
<div class="center-lyric-area">
<div class="lyrics-container" id="tengfeiLyricsScroll"></div>
<div class="lyrics-capsule-mask"></div>
<div class="controls">
<button class="control-btn prev" id="tengfeiPrevBtn" title="上一首">
<svg viewBox="0 0 32 32" fill="none">
<g>
<rect x="7.5" y="9.5" rx="1.2" width="2.2" height="13" fill="none" stroke="currentColor" stroke-width="1.45"/>
<polygon points="22.3,8.5 13,16 22.3,23.5" fill="none" stroke="currentColor" stroke-width="2.65" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
</button>
<button class="control-btn play-pause" id="tengfeiPlayPauseBtn" title="播放/暂停">
<svg id="tengfeiPlayIconSvg" viewBox="0 0 32 32" fill="none">
<g id="tengfeiPlaySvgIcon">
<polygon points="13.6,9.6 13.6,22.4 23,16" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
</button>
<button class="control-btn next" id="tengfeiNextBtn" title="下一首">
<svg viewBox="0 0 32 32" fill="none">
<g>
<rect x="22.3" y="9.5" rx="1.2" width="2.2" height="13" fill="none" stroke="currentColor" stroke-width="1.45"/>
<polygon points="9.7,8.5 19,16 9.7,23.5" fill="none" stroke="currentColor" stroke-width="2.65" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
</button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/tinycolor2@1.6.0/dist/tinycolor-min.js"></script>
<script>
(function() {
function setPauseIcon(isPause){
const svg = document.getElementById('tengfeiPlayIconSvg');
if(isPause){
svg.innerHTML = '<g id="tengfeiPlaySvgIcon">' +
'<rect x="12.2" y="10.2" rx="1.05" width="3.2" height="11.6" fill="none" stroke="currentColor" stroke-width="2.1"/>' +
'<rect x="17.0" y="10.2" rx="1.05" width="3.2" height="11.6" fill="none" stroke="currentColor" stroke-width="2.1"/>' +
'</g>';
}else{
svg.innerHTML = '<g id="tengfeiPlaySvgIcon">' +
'<polygon points="13.6,9.6 13.6,22.4 23,16" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>' +
'</g>';
}
}
const playerState = {
playlist: [],
currentIndex: 0,
isPlaying: false,
lyrics: [],
currentLyricIndex: 0,
lyricOffset: 0,
collapsed: true
};
const elements = {
playerContainer: document.getElementById('tengfeiMusicPlayer'),
albumCover: document.querySelector('.tengfei-album-cover'),
songTitle: document.querySelector('.tengfei-song-title'),
playPauseBtn: document.getElementById('tengfeiPlayPauseBtn'),
prevBtn: document.getElementById('tengfeiPrevBtn'),
nextBtn: document.getElementById('tengfeiNextBtn'),
lyricsScroll: document.getElementById('tengfeiLyricsScroll'),
controls: document.querySelector('.controls'),
songTitleArea: document.querySelector('.song-title-area'),
lyricArea: document.querySelector('.center-lyric-area'),
lyricsMask: document.querySelector('.lyrics-capsule-mask'),
playIconSvg: document.getElementById('tengfeiPlayIconSvg')
};
const audio = new Audio();
function calcCapsuleMask() {
if(playerState.collapsed) return;
const lyricRect = elements.lyricsScroll.getBoundingClientRect();
const parentRect = elements.lyricArea.getBoundingClientRect();
let left = lyricRect.right - parentRect.left; // 歌词区右边到父级左边
elements.lyricsMask.style.left = left + 'px';
elements.lyricsMask.style.right = '0';
elements.lyricsMask.style.top = '0';
elements.lyricsMask.style.bottom = '0';
}
function triggerMaskUpdate() {
setTimeout(calcCapsuleMask, 1);
}
window.addEventListener('resize', triggerMaskUpdate);
function setGradientBackground(coverUrl) {
elements.albumCover.src = coverUrl || "https://via.placeholder.com/40/47484c/fff?text=音乐";
elements.albumCover.onerror = () => {
elements.albumCover.src="https://via.placeholder.com/40/47484c/fff?text=音乐";
elements.playerContainer.style.background="rgba(60,66,64,0.93)";
}
const img = new Image();
img.crossOrigin="Anonymous";
img.src=coverUrl;
img.onload = () => {
try {
const canvas = document.createElement('canvas');
canvas.width = 10; canvas.height = 10;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, 10, 10);
const data = ctx.getImageData(0,0,10,10).data;
let r=0,g=0,b=0,count=0;
for (let i=0;i<data.length;i+=4) { r+=data; g+=data; b+=data; count++; }
r=Math.floor(r/count); g=Math.floor(g/count); b=Math.floor(b/count);
let mainColor = tinycolor({ r,g,b });
if(mainColor.getBrightness()<60) mainColor=mainColor.lighten(20);
if(mainColor.getBrightness()>230) mainColor=mainColor.darken(12);
const dark = tinycolor(mainColor).darken(13).setAlpha(0.93).toRgbString();
const light = tinycolor(mainColor).lighten(3).setAlpha(0.90).toRgbString();
elements.playerContainer.style.transition="background 0.8s";
elements.playerContainer.style.background=`linear-gradient(135deg,${light},${dark} 95%)`;
} catch(e){
elements.playerContainer.style.background="rgba(60,66,64,0.93)";
}
}
}
async function fetchPlaylist() {
try {
const res = await fetch('https://music.3e0.cn/?server=tencent&type=playlist&id=3771408184');
const playlist = await res.json();
if(Array.isArray(playlist)&&playlist.length>0) {
playerState.playlist = playlist;
loadSong(0);
}
else elements.songTitle.textContent = '获取播放列表失败';
} catch(e){
elements.songTitle.textContent = '获取播放列表出错';
}
}
function loadSong(index) {
if(index < 0 || index >= playerState.playlist.length) return;
const song = playerState.playlist;
playerState.currentIndex = index;
elements.songTitle.textContent = song.name || '未知歌曲';
elements.songTitle.title = song.name || '未知歌曲';
setGradientBackground(song.pic);
audio.src = song.url || '';
audio.load();
fetchLyrics(song.lrc || song.lyricsUrl || song.lrcUrl);
setTimeout(() => {
if (playerState.collapsed) {
let titleW = getTextWidth(elements.songTitle.textContent, window.getComputedStyle(elements.songTitle));
let calcW = 32 + 9 + Math.max(titleW,40) + 2*10 + 18;
calcW = Math.max(126, Math.min(calcW, 260));
elements.playerContainer.style.width = calcW + 'px';
}
triggerMaskUpdate();
}, 50);
}
function getTextWidth(text, style) {
const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
const context = canvas.getContext("2d");
context.font = [
style.fontWeight,
style.fontSize,
style.fontFamily
].join(' ');
return context.measureText(text).width;
}
let lyricMaxPixel = 0;
let lyricFontSize = 11.3;
let lyricGapAuto = 14;
let titleFontSize = 13;
let titleMaxWidth = 118;
let lastLyricWidth = 0;
function updateLyricLayout() {
if(playerState.collapsed) return;
if(!playerState.lyrics || playerState.lyrics.length==0) return;
let maxLyric = "";
for(const item of playerState.lyrics)
if(item.text && item.text.length > maxLyric.length) {
maxLyric = item.text;
}
let isEnglish = /{2}/.test(maxLyric);
let lyricLen = maxLyric.length;
let fontSize = 12.4, lyricsWidth = 150;
let gap = 14;
let titleSize = 13;
let titleWidthMin = 60, titleWidthMax = 148, titleWidthBase = 120;
if(isEnglish || lyricLen > 22) {
fontSize = 10.6; lyricsWidth = 204; gap = 9; titleSize = 12.1; titleWidthBase = 92;
} else if(lyricLen > 16) {
fontSize = 11.4; lyricsWidth = 162; gap = 12; titleSize = 12.35; titleWidthBase = 105;
} else if(lyricLen > 11) {
fontSize = 12.2; lyricsWidth = 146; gap = 15; titleSize = 12.8; titleWidthBase = 115;
} else {
fontSize = 13.2; lyricsWidth = 92+lyricLen*4.5; gap = 18; titleSize = 13.1; titleWidthBase = 128;
}
lyricsWidth = Math.min(lyricsWidth, 226);
let maxW = Math.max(titleWidthMin, Math.min(titleWidthBase, 0.72*lyricsWidth));
elements.lyricsScroll.style.fontSize = fontSize + "px";
elements.lyricsScroll.style.maxWidth = lyricsWidth + "px";
lyricMaxPixel = lyricsWidth;
lyricFontSize = fontSize;
lastLyricWidth = lyricsWidth;
elements.lyricArea.style.marginLeft = gap + "px";
lyricGapAuto = gap;
titleMaxWidth = Math.max(titleWidthMin, Math.min(titleWidthMax, titleWidthBase, elements.playerContainer.clientWidth-lyricsWidth-80-32-10-10));
elements.songTitle.style.maxWidth = titleMaxWidth + "px";
elements.songTitle.style.fontSize = titleSize + "px";
titleFontSize = titleSize;
triggerMaskUpdate();
}
async function fetchLyrics(url) {
try {
if(!url){ showLyrics('暂无歌词'); triggerMaskUpdate(); return; }
const response = await fetch(url);
const text = await response.text();
parseLyrics(text);
} catch (error) {
showLyrics('歌词加载失败');
triggerMaskUpdate();
}
}
function parseLyrics(text) {
if(!text){ showLyrics('暂无歌词'); triggerMaskUpdate(); return;}
const lines = text.split('\n');
const lyrics = [];
const regexs = [
/\[(\d+):(\d+)\.(\d+)\](.*)/,
/\[(\d+):(\d+)\](.*)/
];
for(let line of lines){
let match = line.match(regexs);
if(match){
let m=parseInt(match), s=parseInt(match), ms=parseInt(match);
let time = m*60 + s + ms/100;
let lyricText = match.trim();
if(lyricText) lyrics.push({time, timeEnd: time+2, text: lyricText});
continue;
}
match = line.match(regexs);
if(match){
let m = parseInt(match), s = parseInt(match);
let time = m*60 + s;
let lyricText = match.trim();
if(lyricText) lyrics.push({time, timeEnd: time+2, text: lyricText});
}
}
for (let i=0;i<lyrics.length-1;i++) {
lyrics.timeEnd = lyrics.time-0.1;
}
playerState.lyrics = lyrics;
playerState.currentLyricIndex = 0;
updateLyricLayout();
showLyricsAnimated(0, false);
triggerMaskUpdate();
}
function showLyricsAnimated(idx, animate=true) {
const lyrics = playerState.lyrics || [];
const cur = lyrics ? lyrics.text : '';
const nxt = lyrics ? lyrics.text : '';
const container = elements.lyricsScroll;
container.innerHTML = '';
const lineCur = document.createElement('div');
lineCur.className = 'lyrics-line lyrics-current';
lineCur.textContent = cur || '';
lineCur.style.opacity = "1";
lineCur.style.transition = "opacity 0.34s cubic-bezier(.5,.7,.7,1)";
lineCur.style.willChange = "opacity";
lineCur.style.fontSize = lyricFontSize + "px";
const lineNxt = document.createElement('div');
lineNxt.className = 'lyrics-line lyrics-next';
lineNxt.textContent = nxt || '';
lineNxt.style.opacity = "0.45";
lineNxt.style.transition = "opacity 0.33s cubic-bezier(.54,.54,.7,1)";
lineNxt.style.willChange = "opacity";
lineNxt.style.fontSize = Math.max(9, lyricFontSize*0.77) + "px";
container.appendChild(lineCur);
container.appendChild(lineNxt);
if(animate){
lineCur.style.opacity = "0";
setTimeout(()=>{ lineCur.style.opacity="1"; }, 12);
lineNxt.style.opacity = "0.15";
setTimeout(()=>{ lineNxt.style.opacity="0.45"; }, 33);
}
triggerMaskUpdate();
}
function updateLyrics() {
if (!playerState.lyrics || playerState.lyrics.length === 0) return;
const currentTime = audio.currentTime + playerState.lyricOffset;
const lyrics = playerState.lyrics;
let newIndex = playerState.currentLyricIndex;
let found = false;
for(let i=0; i<lyrics.length; i++){
if(currentTime >= lyrics.time && currentTime < lyrics.timeEnd) {
newIndex = i; found = true; break;
}
}
if(!found && currentTime < lyrics.time) { newIndex = 0; }
else if(!found) { newIndex = lyrics.length-1; }
if(newIndex !== playerState.currentLyricIndex) {
showLyricsAnimated(newIndex, true);
playerState.currentLyricIndex = newIndex;
} else if(playerState.lyrics && elements.lyricsScroll.children.length===2){
let nxt = lyrics ? lyrics.text : '';
elements.lyricsScroll.children.textContent = nxt;
}
triggerMaskUpdate();
}
function togglePlay(force) {
if(force==="play"){
audio.play().catch(()=>{});
playerState.isPlaying=true;
setPauseIcon(true);
elements.playerContainer.classList.add('playing');
}else if(force==="pause"){
audio.pause();
playerState.isPlaying=false;
setPauseIcon(false);
elements.playerContainer.classList.remove('playing');
collapsePlayer();
}else{
if(audio.paused)togglePlay("play");
else togglePlay("pause");
}
}
function nextSong() {
let idx = (playerState.currentIndex+1)%playerState.playlist.length;
loadSong(idx); if(playerState.isPlaying) audio.play();
}
function prevSong() {
let idx = (playerState.currentIndex-1+playerState.playlist.length)%playerState.playlist.length;
loadSong(idx); if(playerState.isPlaying) audio.play();
}
elements.playPauseBtn.addEventListener('click', e=>{
e.stopPropagation(); togglePlay();
});
elements.nextBtn.addEventListener('click', e=>{
e.stopPropagation(); nextSong();
});
elements.prevBtn.addEventListener('click', e=>{
e.stopPropagation(); prevSong();
});
function collapsePlayer() {
playerState.collapsed = true;
elements.playerContainer.classList.add('collapsed');
elements.playerContainer.style.transition="width 0.59s cubic-bezier(.49,.37,.36,.97), max-width 0.36s, min-width 0.32s";
elements.playerContainer.style.width = '';
elements.songTitle.style.maxWidth = '118px';
elements.songTitle.style.fontSize = '13px';
audio.pause();
elements.lyricsScroll.style.fontSize = "11.3px";
elements.lyricsScroll.style.maxWidth = "150px";
elements.lyricArea.style.marginLeft = "14px";
setTimeout(() => {
elements.songTitleArea.style.opacity = 1;
elements.lyricArea.style.opacity = 0;
elements.lyricsScroll.style.opacity = 0;
elements.controls.style.opacity = 0;
}, 20);
elements.lyricsMask.style.width = "0px";
elements.lyricsMask.style.left = "";
elements.lyricsMask.style.opacity = "0";
}
function expandPlayer() {
playerState.collapsed = false;
elements.playerContainer.classList.remove('collapsed');
elements.playerContainer.style.transition="width 0.68s cubic-bezier(.51,.33,.38,1.18), max-width 0.45s";
elements.playerContainer.style.width = '340px';
elements.songTitleArea.style.opacity = 1;
elements.lyricArea.style.opacity = 1;
elements.lyricsScroll.style.opacity = 1;
elements.controls.style.opacity = 1;
setTimeout(()=>{togglePlay('play');},130);
setTimeout(()=>{updateLyricLayout();
elements.lyricArea.style.opacity=1;
elements.lyricsScroll.style.opacity=1;
elements.controls.style.opacity=1;
triggerMaskUpdate();
},120);
}
elements.playerContainer.addEventListener('click', function(e){
if (.includes(e.target)) return;
if (playerState.collapsed) { expandPlayer(); }
else { collapsePlayer(); }
});
audio.addEventListener('timeupdate', updateLyrics);
audio.addEventListener('ended', ()=>{ nextSong(); });
fetchPlaylist();
collapsePlayer();
if(window.matchMedia('(any-pointer: coarse)').matches){
elements.prevBtn.style.display = elements.nextBtn.style.display = elements.playPauseBtn.style.display = 'none';
}
setTimeout(triggerMaskUpdate, 200);
})();
</script>
页:
[1]