小天管理 发表于 2024年9月2日 发表于 2024年9月2日 最近把五年前写的一个弹幕库给重构了一下,本来两年前就想着做这件事,但是其中有一段工作时间压力很大,所以就搁置了,导致没有时间来做这件事情,最近周末 + 晚上花一些时间重构了下,并把文档好好写了一下。言归正传,这篇文章会介绍部分这个弹幕库有的能力,如果正好符合你的需求或者感兴趣,可以帮忙点点 star 来支持一下。 我们有哪些能力 我们提供了灵活调整轨道,自定义弹幕和容器样式,弹幕运动算法等能力,还在提供非常丰富的钩子来让用户处理自定义的行为,只要你想要的,都能做到,本文档会简单介绍一些能力和一些功能的实现。 在线 demo: https://imtaotao.github.io/danmu/ github: https://github.com/imtaotao/danmu?tab=readme-ov-file 官方文档: https://imtaotao.github.io/danmu/document/zh/ 快速开始 对于一个开箱即用的 demo ,可以非常简单的接入,如下所示: import { create } from 'danmu'; const manager = create(); manager.mount('#root'); manager.startPlaying(); // 发送弹幕 manager.push('弹幕内容') 对轨道进行调整 我们对支持类似 CSS calc 表达式的能力,一些位置/宽高等信息都可以用表达式来计算。所以对于轨道来说可以很方便的进行调整。 **number**:默认单位为 px。 **string**:表达式计算。支持(+, -, *, /)数学计算,只支持 % 和 px 两种单位。 // 例如,这里的 100% 是指容器宽度(如果是高度相关的配置 100% 就是容器的高度) manager.setGap('(100% - 10px) / 5'); 限制为顶部 3 条弹幕 // 如果我们希望轨道高度为 50px manager.setTrackHeight('100% / 3'); // 如果不设置渲染区域,轨道的高度会根据默认的 container.height / 3 得到, // 这可能导致轨道高度不是你想要的 manager.setArea({ y: { start: 0, // 3 条轨道的总高度为 150px end: 150, }, }); 限制为中间 3 条弹幕 manager.setTrackHeight('100% / 3'); manager.setArea({ y: { start: `50%`, end: `50% + 150`, }, }); 限制为几条不连续的轨道 限制为几条不连续的轨道,除了要做和连续轨道的操作之外,还需要借助 willRender 这个钩子来实现。 // 如果我们希望轨道高度为 50px ,并渲染 0 ,2 ,4 这几条轨道 manager.setTrackHeight('100% / 6'); // 设置容器的渲染区域 manager.setArea({ y: { start: 0, // 6 条轨道的总高度为 300px end: 300, }, }); manager.use({ willRender(ref) { // 高级弹幕和轨道不强相关,没有 trackIndex 这个属性 if (ref.trackIndex === null) return ref; // 如果为 1 ,3 ,5 这几条轨道就阻止渲染,并重新添加等待下次渲染 if (ref.trackIndex % 2 === 1) { ref.prevent = true; manager.unshift(ref.danmaku); } return ref; }, }); 自定义渲染 弹幕和容器都允许自定义的渲染样式,你可以很方便的做到。 自定义弹幕的样式 1. 通过 manager.setStyle 来设置 import { create } from 'danmu'; // 需要添加的样式 const styles = { color: 'red', fontSize: '15px', // . }; const manager = create(); // 后续渲染的弹幕和当前已经渲染的弹幕会设置上这些样式。 for (const key in styles) { manager.setStyle(key, styles[key]); } 2. 通过 danamaku.setStyle 来设置 import { create } from 'danmu'; // 需要添加的样式 const styles = { color: 'red', fontSize: '15px', // . }; // 初始化的时候添加钩子处理,这样当有新的弹幕渲染时会自动添加上这些样式 const manager = create({ plugin: { $moveStart(danmaku) { for (const key in styles) { danmaku.setStyle(key, styles[key]); } // 你也可以在这里给弹幕 DOM 添加 className danmaku.node.classList.add('className'); }, }, }); // 对当前正在渲染的弹幕添加样式 manager.asyncEach((danmaku) => { for (const key in styles) { danmaku.setStyle(key, styles[key]); } }); 自定义容器样式 import { create } from 'danmu'; // 需要添加的样式 const styles = { background: 'red', // . }; const manager = create({ plugin: { // 你可以在初始化的时候添加钩子处理 init(manager) { for (const key in styles) { manager.container.setStyle(key, styles[key]); } // 你也可以在这里给容器 DOM 添加 className manager.container.node.classList.add('className'); }, }, }); // 或者直接调用 api for (const key in styles) { manager.container.setStyle(key, styles[key]); } 高级弹幕的示例 本章节将介绍如何将弹幕固定在某一位置,以 top 和 left 这两个位置举例。由于我们需要自定义位置,所以我们需要使用高级弹幕的能力。 将弹幕固定在顶部 // 这条弹幕将会居中距离顶部 10px 的位置悬停 5s manager.pushFlexibleDanmaku('弹幕内容', { duration: 5000, direction: 'none', position(danmaku, container) { return { x: `50% - ${danmaku.getWidth() / 2}`, y: 10, // 具体容器顶部的距离为 10px }; }, }); 固定在顶部第 2 条轨道上 // 这条弹幕将会在第二条轨道居中的位置悬停 5s manager.pushFlexibleDanmaku('弹幕内容', { duration: 5000, direction: 'none', position(danmaku, container) { // 渲染在第 3 条轨道中 const { middle } = manager.getTrackLocation(2); return { x: `50% - ${danmaku.getWidth() / 2}`, y: middle - danmaku.getHeight() / 2, }; }, }); 将弹幕固定在左边 // 这条弹幕将会在容器中间距离左边 10px 的地方停留 5s manager.pushFlexibleDanmaku('弹幕内容', { duration: 5000, direction: 'none', position(danmaku, container) { // 渲染在第 3 条轨道中 const { middle } = manager.getTrackLocation(2); return { x: 10, y: `50% - ${danmaku.getHeight() / 2}`, }; }, }); 发送带图片的弹幕 要让弹幕里面能够携带图片,要在弹幕的节点内部添加自定义的内容,实际上不止图片,你可以往弹幕的节点里面添加任何的内容。 本章节的组件以 React 来实现演示。 开发弹幕组件 export function Danmaku({ danmaku }) { return ( <div> <img src="https://abc.jpg" /> {danmaku.data} </div> ); } 渲染弹幕 import ReactDOM from 'react-dom/client'; import { create } from 'danmu'; import { Danmaku } from './Danmaku'; const manager = create<string>({ plugin: { // 将组件渲染到弹幕的内置节点上 $createNode(danmaku) { ReactDOM.createRoot(danmaku.node).render(<Danmaku danmaku={danmaku} />); }, }, }); 编写一个插件 编写一个插件是很简单的,但是借助内核暴露出来的钩子和 API,你可以很轻松的实现强大且定制化的需求。由于内核没有暴露出来根据条件来实现过滤弹幕的功能,原因在于内核不知道弹幕内容的数据结构,这和业务的诉求强相关,所以我们在此通过插件来实现精简弹幕的功能用来演示。 编写一个插件 你编写的插件应当取一个 name,以便于调试定位问题(注意不要和其他插件冲突了)。 插件可以选择性的声明一个 version,这在你的插件作为独立包发到 npm 上时很有用。 export function filter({ userIds, keywords }) { return (manager) => { return { name: 'filter-keywords-or-user', version: '1.0.0', // version 字段不是必须的 willRender(ref) { const { userId, content } = ref.danmaku.data.value; console.log(ref.type); // 可以根据此字段来区分是普通弹幕还是高级弹幕 if (userIds && userIds.includes(userId)) { ref.prevent = true; } else if (keywords) { for (const word of keywords) { if (content.includes(word)) { ref.prevent = true; break; } } } return ref; }, }; }; } 注册插件 你需要通过 mananger.use() 来注册插件。 import { create } from 'danmu'; const manager = create<{ userId: number; content: string; }>(); manager.use( filter({ userIds: [1], keywords: ['菜'], }), ); 发送弹幕 ❌ 会被插件阻止渲染 manager.push({ userId: 1, content: '', }); ❌ 会被插件阻止渲染 manager.push({ userId: 2, content: '你真菜', }); ✔️ 不会被插件阻止渲染 manager.push({ userId: 2, content: '', }); ✔️ 不会被插件阻止渲染 manager.push({ userId: 2, content: '你真棒', }); 总结 本文档只是简单介绍了下现在的部分能力,更详细的文档在官网可以查看,如果对你的业务或者学习有帮助的,给个 star 支持一下作者,也欢迎大家评论探讨(不止弹幕,哈哈)。
已推荐帖子