之前我尝试使用 JS 实现 B 站的首页 Banner,实现了基本1:1的复刻效果,一来是觉得很有意思,二来也是为了练习前端技术。与此同时我也写了一篇技术文章,详细讲解了整个实现过程的技术细节和原理:《如何用原生 JS 复刻 Bilibili 首页头图的视差交互效果》
肝完文章时 B 站就更换了新的 Banner 图,文章发布一个月后又更新了中秋版本,这次更新也出现了此前未曾谋面的 video
元素效果,我又跃跃欲试,打算复刻一遍这个 Banner 效果。
新增视频类型
由于这次的 Banner 中增加了水面波光和萤火虫的复杂动态,所以加入了视频图层来展示,是 webm 格式的视频文件,体积非常小。在之前所写代码的基础上只需要增加一个类型字段,然后按类型创建标签即可:
js
const child = document.createElement(item.tagName || 'img');
if (item.tagName === 'video') {
child.loop=true; child.autoplay=true; child.muted=true;
}
其中我们为 video
标签加入了 loop
属性表示一直循环播放视频,autoplay
设置自动播放,但是在浏览器上这通常不会生效,所以得将 muted
也设置为 true
表示静音,这样视频就会默认自动播放了。
抓取资源
每次手动获取图层资源还是挺麻烦的,所以我想使用 node
编写一段脚本来自动下载我所需的资源,并根据每个 div
上的样式生成我所需的基础 json
数据。
由于 B 站的 Banner 是动态加载的,普通的网页抓取手段并不能获取资源,所以我们可以使用 puppeteer
加载网页,然后等待其资源加载完毕后获取网页中所有 Banner 图层元素:
js
// 获取所有 ".layer" 元素
const layerElements = await page.$$(".animated-banner .layer");
接着我们使用 page.evaluate(() => {}, layerElements)
方法循环获取到的元素集合,拿到相对应的信息如 width
height
src
style
tagName
transform
等,其中 src
是图片/视频资源对应的链接地址,图片为远程链接,视频则为 blob
协议链接,我们同样使用 evaluate
方法,将 src
作为方法的第二个参数传入,在网页中下载资源并保存到本地:
js
const content = await page.evaluate(async (url) => {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
return { buffer: Array.from(new Uint8Array(buffer)) };
}, src);
这里我们使用浏览器原生方法 fetch
可以很方便地发起一个请求,得到响应数据后将其转为二进制的 arrayBuffer
数据,注意此时的操作都是在"浏览器"的环境当中,当我们获取完数据后,接收到的 content
变量则是在 node
环境中,接下来可对数据进行保存:
js
const fileData = Buffer.from(content.buffer);
fs.writeFileSync(filePath, fileData);
使用 node
的 Buffer.from
将其转化为 node
环境下的缓冲区,最后使用 fs
的方法操作写入即可,运行结果:
最后
代码我也整理为了 Github 仓库,之后每次 B 站更新都可以使用该仓库来收集,作为 B 站的历史 Banner 存档以供学习和欣赏。
在线网站地址
palxiao.github.io/bilibili-ba...
截至目前总共有三种 Banner 可以切换查看。
如何使用
拉取项目:
shell
git clone https://github.com/palxiao/bilibili-banner.git
运行 pnpm i
或 yarn / npm i
安装项目本地依赖;
运行 npm run serve
启动演示网页。
获取最新效果
如果当前 B 站 Banner 已更新,那么可以通过简单配置获取最新数据:
- 运行
npm run grab
,自动在assets
目录下生成数据(以当天日期命名) - 在
config.js
中添加配置(使用 fetch 引入 json)
js
const banner_20231001 = await fetch('./assets/2023-10-01/data.json?r='+Math.random())
export default [{
name: '海上明月 - 兔子',
data: await banner_20231001.json()
}, ...... ]
接下来需要对每个图层进行参数调试,具体步骤为:打开 assets
目录下对应的 data.json
文件,修改其中每个对象的参数,刷新网页查看效果。
目前支持参数如下:
属性 | 类型 | 说明 |
---|---|---|
a | number | 表示加速度,数值越高移动变化越大(接受正负值) |
deg | number | 表示旋转幅度,数值越高旋转越快(接受正负值) |
g | number | 表示重力,数值越高上下移动变化越大(接受正负值) |
f | number | 表示大小变化,对应 CSS transform: scale |
opacity | array | 透明度变化,接收一个区间 |
注:正负值会影响变化的方向
思考
在这次中秋 Banner 的解析中我发现,DOM Tree 当中多出来一些完全无用的图层,它们被设置了 opacity: 0;
,在 Banner 中并无论如何操作都不会展示,而 translate
则依旧会跟随变化。通过这一不合理现象可以确定,B 站的动态 Banner 并不是每次都由前端进行开发的,而是由设计师或产品PM直接进行设计和发布,应该有一个后台配置的界面。
而且这次更新的 Banner 存在视频图层,我猜测可能先在 PS 中制作了静态版的设计文件,然后其中两个动态图层再从视频工具中制作后导出。后咨询了设计师朋友,他则认为整个设计文件应该在 AE 这类软件中完整制作导出比较合理。接下来设计师将如何导出数据上传及调试,如何高效率协作,这背后他们一定做了不少努力和尝试。
以上就是文章的全部内容了,感谢看到这里!如果觉得写得还不错,对你有所帮助或启发,别忘了点赞收藏关注"一键三连"哦~ 我是茶无味de一天(公众号: 品味前端),一名平凡的前端 Developer,希望与你共同成长~