因为我很喜欢听音乐(尤其是电子音乐),所以我希望我自己的博客能给浏览者安利很多很多好听的音乐。
总感觉这个全局吸底aplayer如果只能播一个歌单的话太浪费了。我希望它能像家里摆着的唱片机一样,当客人来做客时,任他在唱片架挑选中意的唱片,并放进这个唱片机里开始聆听。
虽然我是技术渣,但还是想试试。
乐辑 这里直接照搬了butterfly主题中图册页的样式和标签插件(确实很好看),就不赘述了。
收藏的唱片一多,确实就需要对唱片架中的唱片进行归类。
唱片架 一定程度上参考了butterfly的图片页。
概述 大概长这样:
当鼠标移至唱片封面时,会出现个人的吐槽评价,以及黑胶唱片被拿出的效果。
点击下方的播放,底部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 'use strict' const urlFor = require ('hexo-util' ).url_for .bind (hexo)function musicItem (args ) { const cover = args[0 ] const title = args[1 ] const author = args[2 ] const id = args[3 ] const server = args[4 ] const type = args[5 ] const description = args[6 ] let extraHTML = '' if (description && typeof (description)!=="undefined" ) { extraHTML = `<div class="description"> <p class="des-inner">${description} </p> </div> ` } return ` <div class="music-item"> <div class="pic-item"> <img class="cover no-lightbox" src="${cover} ?x-oss-process=image/resize,s_300"> <div class="inner"> </div> <img no-lazy src="/img/vinyl.png" class="vinyl no-lightbox"> ${extraHTML} </div> <p class="title">${title} </p> <p class="author">${author} </p> <button class="music-play-btn" data-id="${id} " data-server="${server} " data-type="${type} ">播放</button> </div> ` } hexo.extend .tag .register ('musicItem' , musicItem)
样式 在/source/css/tags
中创建了musicItem.styl
,用于唱片架样式。
别忘了引入到总样式文件里。
每一个唱片是.music-item
,一个唱片架就是.music-list
。
老实说我还是比较习惯scss的写法,stylus让我觉得好别扭orz。
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 #article-container .music-list display : grid; grid-template-columns : 1 fr 1 fr; grid-row -gap: 1.5rem ; justify-items: center; margin : 2rem 0 ; @media screen and (max-width : 500px ) & grid-template-columns: 1 fr; .music-item width : 14rem ; height : 18rem ; border-radius : 0.5rem ; display : flex; flex-direction : column; .pic-item position : relative; width : 12rem ; height : 12rem ; .cover width : 12rem ; height : 12rem ; border-radius : 0.5rem ; position : absolute; z-index : 2 ; transition : 0.1s ; .inner width : 11.5rem ; height : 11.5rem ; background-color : rgba (255 , 255 , 255 , .3 ); border-radius : 1rem ; position : absolute; top : 0.25rem ; left : 1.25rem ; transition : 0.1s ; z-index : 1 ; .vinyl width : 12rem ; height : 12rem ; position : absolute; background-color : transparent; left : 1.5rem ; transition : 0.1s ; .description visibility : hidden; opacity : 0 ; width : 12rem ; height : 12rem ; background-color : rgba (0 ,0 ,0 ,.5 ); color : #fff ; border-radius : 0.5rem ; overflow : hidden; position : absolute; transition : 0.1s ; z-index : 5 ; .des-inner width : 12rem ; height : 12rem ; color : #fff ; padding : 0.5rem ; border-radius : 0.5rem ; font-size : 0.8rem ; letter-spacing : 0.05rem ; line-height : 1.1rem ; text-indent : 1.6rem ; overflow-y : scroll; transition : 0.1s ; z-index : 5 ; &:hover .vinyl left : 3.5rem ; .cover box-shadow : rgba (0 , 0 , 0 , 0.2 ) 0px 5px 15px ; .inner box-shadow : rgba (0 , 0 , 0 , 0.2 ) 0px 5px 15px ; .description opacity : 1 ; visibility : visible; p margin : 0 ; cursor : default; .title font-size : 1rem ; white-space : nowrap; word-break : break-all; overflow : hidden; text-overflow : ellipsis; margin-top : 0.5rem ; .author font-size : 0.8rem ; white-space : nowrap; word-break : break-all; overflow : hidden; text-overflow : ellipsis; button border : none; background-color : #fff ; padding : 0.25rem border-radius : 1rem ; margin-top : 0.5rem ; transition : 0.1s ; box-shadow : rgba (0 , 0 , 0 , 0.1 ) 0px 4px 12px ; &:hover cursor : pointer; box-shadow : rgba (50 , 50 , 93 , 0.25 ) 0px 2px 5px -1px , rgba (0 , 0 , 0 , 0.3 ) 0px 1px 3px -1px ;
行为 在/source/js
中创建了changePlay.js
,用于唱片架样式。
其实也就点击播放后切换播放列表这一个行为。
查阅了其他人的一些博客,各位推介的最佳实践都是使用meting.js对aplayer进行增强,这样只要输入几个参数就能获取整个网络音乐平台的播放列表,不需要自己想办法获得音乐的api。就像这样:
1 <meting-js server ="netease" type ="album" id ="154548341" >
但是这样并不符合我的要求,因为当我查阅meting.js的文档时,我发现一个问题:meting.js仿佛是一个完全的黑箱操作,它好像没有提供任何透露流程细节的API,我只能输入几个参数看着它自己在网页的该位置生成一个aplayer播放器。
这明显不符合我的需求,我希望操作博客已有的全局吸底播放器,而不是生成一个新播放器。
于是我查看了meting.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 const getMusicList = async (options ) => { if (typeof (options) !== "object" ) { return } let api let result init () await parse () function init ( ) { api = options.api || window .meting_api || 'https://api.i-meto.com/meting/api?server=:server&type=:type&id=:id&r=:r' if (options.auto ) _parse_link () function _parse_link ( ) { let rules = [ ['music.163.com.*song.*id=(\\d+)' , 'netease' , 'song' ], ['music.163.com.*album.*id=(\\d+)' , 'netease' , 'album' ], ['music.163.com.*artist.*id=(\\d+)' , 'netease' , 'artist' ], ['music.163.com.*playlist.*id=(\\d+)' , 'netease' , 'playlist' ], ['music.163.com.*discover/toplist.*id=(\\d+)' , 'netease' , 'playlist' ], ['y.qq.com.*song/(\\w+).html' , 'tencent' , 'song' ], ['y.qq.com.*album/(\\w+).html' , 'tencent' , 'album' ], ['y.qq.com.*singer/(\\w+).html' , 'tencent' , 'artist' ], ['y.qq.com.*playsquare/(\\w+).html' , 'tencent' , 'playlist' ], ['y.qq.com.*playlist/(\\w+).html' , 'tencent' , 'playlist' ], ['xiami.com.*song/(\\w+)' , 'xiami' , 'song' ], ['xiami.com.*album/(\\w+)' , 'xiami' , 'album' ], ['xiami.com.*artist/(\\w+)' , 'xiami' , 'artist' ], ['xiami.com.*collect/(\\w+)' , 'xiami' , 'playlist' ], ] for (let rule of rules) { let patt = new RegExp (rule[0 ]) let res = patt.exec (options.auto ) if (res !== null ) { options.server = rule[1 ] options.type = rule[2 ] options.id = res[1 ] return } } } } async function parse ( ) { if (options.url ) { let res = { name : options.name || options.title || 'Audio name' , artist : options.artist || options.author || 'Audio artist' , url : options.url , cover : options.cover || options.pic , lrc : options.lrc || options.lyric || '' , type : options.type || 'auto' , } result = res return } let url = api .replace (':server' , options.server ) .replace (':type' , options.type ) .replace (':id' , options.id ) .replace (':auth' , options.auth ) .replace (':r' , Math .random ()) const r = await fetch (url) const res = await r.json () result = res } return result }
源码是一个继承了HTMLElement的类(因为要以HTML标签形式使用),其中最核心的两个部分即init()
(调整参数)和parse()
(获得播放列表)两个方法。
我把它简单修改成了一个传入配置对象参数就可以获得播放列表的函数,把后续各种构建aplayer实例以及创建DOM元素之类的行为都舍弃了。
你非得问我的话就是我也不懂(
然后就是为按钮绑定事件了,但好像产生了新问题:hexo中,用户自定义js好像只能全局引入。
如果确实有按页面引入的方法的话请务必告诉我
这意味着如果我是为music-list还是为music-item绑定事件都不行,因为只有在特定的页面中才会有这两类元素,在其他页面载入话就会产生空指针报错。
最后我想了个笨方法:直接给全局绑定点击事件,并检测点击目标(
别嘲笑我,我要脸orz。
具体代码:
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 window .addEventListener ("click" , async (event) => { const target = event.target const tagName = target.tagName if (tagName.toUpperCase () == "BUTTON" && target.classList .contains ("music-play-btn" )) { const dataset = target.dataset if (dataset.server == "undefined" || dataset.type == "undefined" || dataset.id == "undefined" ) { console .log ("param err" ) return } const res = await getMusicList ({ server : dataset.server , type : dataset.type , id : dataset.id }) const ap = new APlayer ({ container : document .getElementById ("bottom-aplayer" ) }) if (ap) { ap.list .clear () ap.list .add (res) const metingStr = localStorage .getItem ("metingjs" ) const metingjs = JSON .parse (metingStr) let vol if (typeof (metingjs.volume ) == "number" ) { vol = metingjs.volume } else { vol = ap.audio .volume } ap.volume (vol, true ) ap.setMode ("normal" ) ap.play () } else { return } } }, true )
碎碎念 随后我又想尝试新的功能:添加一个按钮,点击后不改变播放列表,只是在播放列表末尾插入曲目。
我尝试了一阵子,始终未能成功,但发现了问题所在:
加载站点后所处的的第一个页面,可以从console找到该页面中所有aplayer实例(全部都被butterfly框架放在了aplayers
这个数组中,包括吸底aplayer)。
但前往其他页面时,再看aplayers
数组,就找不到刚才的实例了。
确实hexo博客不是单页面应用,这样其实也挺正常的。但全局吸底aplayer托了pjax的福,即便页面连续跳转,播放也不间断。
经观察,上面的代码本质上是给原本全局aplayer的位置新建了一个aplayer实例,如果我只想末尾插入曲目,我可能需要获得原本的aplayer实例。
到底该怎么做呢(我不知道pjax的运行机制)。