之前我尝试使用 JS 实现 B 站的首页 Banner,实现了基本1:1的复刻效果,一来是觉得很有意思,二来也是为了练习前端技术。与此同时我也写了一篇技术文章,详细讲解了整个实现过程的技术细节和原理:《如何用原生 JS 复刻 Bilibili 首页头图的视差交互效果

肝完文章时 B 站就更换了新的 Banner 图,文章发布一个月后又更新了中秋版本,这次更新也出现了此前未曾谋面的 video 元素效果,我又跃跃欲试,打算复刻一遍这个 Banner 效果。

新增视频类型

由于这次的 Banner 中增加了水面波光和萤火虫的复杂动态,所以加入了视频图层来展示,是 webm 格式的视频文件,体积非常小。在之前所写代码的基础上只需要增加一个类型字段,然后按类型创建标签即可:

1
2
3
4
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 图层元素:

1
2
// 获取所有 ".layer" 元素
const layerElements = await page.$$(".animated-banner .layer");

接着我们使用 page.evaluate(() => {}, layerElements) 方法循环获取到的元素集合,拿到相对应的信息如 width height src style tagName transform 等,其中 src 是图片/视频资源对应的链接地址,图片为远程链接,视频则为 blob 协议链接,我们同样使用 evaluate 方法,将 src 作为方法的第二个参数传入,在网页中下载资源并保存到本地:

1
2
3
4
5
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 环境中,接下来可对数据进行保存:

1
2
const fileData = Buffer.from(content.buffer);
fs.writeFileSync(filePath, fileData);

使用 nodeBuffer.from 将其转化为 node 环境下的缓冲区,最后使用 fs 的方法操作写入即可,运行结果:

最后

代码我也整理为了 Github 仓库,之后每次 B 站更新都可以使用该仓库来收集,作为 B 站的历史 Banner 存档以供学习和欣赏。

在线网站地址

https://palxiao.github.io/bilibili-banner/

截至目前总共有三种 Banner 可以切换查看。

如何使用

拉取项目:

1
git clone https://github.com/palxiao/bilibili-banner.git

运行 pnpm iyarn / npm i 安装项目本地依赖;

运行 npm run serve 启动演示网页。

获取最新效果

如果当前 B 站 Banner 已更新,那么可以通过简单配置获取最新数据:

  1. 运行 npm run grab,自动在 assets 目录下生成数据(以当天日期命名)
  2. config.js 中添加配置(使用 fetch 引入 json)
1
2
3
4
5
6
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,希望与你共同成长~