这可能是见过的最好用的弹幕库 🔥🔥

imtaotao · 2024-9-2 20:20:16 · 46 次点击
最近把五年前写的一个弹幕库给重构了一下,本来两年前就想着做这件事,但是其中有一段工作时间压力很大,所以就搁置了,导致没有时间来做这件事情,最近周末 + 晚上花一些时间重构了下,并把文档好好写了一下。言归正传,这篇文章会介绍部分这个弹幕库有的能力,如果正好符合你的需求或者感兴趣,[**可以帮忙点点 star 来支持一下。**]( https://github.com/imtaotao/danmu)

## 我们有哪些能力

我们提供了**灵活调整轨道**,**自定义弹幕和容器样式**,**弹幕运动算法**等能力,还在提供非常丰富的钩子来让用户处理自定义的行为,只要你想要的,都能做到,本文档会简单介绍一些能力和一些功能的实现。
> - 在线 demo: https://imtaotao.github.io/danmu/
> - github: https://github.com/imtaotao/danmu?tab=readme-ov-file
> - 官方文档: https://imtaotao.github.io/danmu/document/zh/


## 快速开始

对于一个开箱即用的 demo ,可以非常简单的接入,如下所示:
```js
import { create } from 'danmu';

const manager = create();

manager.mount('#root');
manager.startPlaying();

// 发送弹幕
manager.push('弹幕内容')
```


## 对轨道进行调整

我们对支持类似 CSS calc 表达式的能力,一些位置/宽高等信息都可以用表达式来计算。所以对于轨道来说可以很方便的进行调整。

1.  **`number`**:默认单位为 `px`。
1.  **`string`**:表达式计算。支持(`+`, `-`, `*`, `/`)数学计算,只支持 `%` 和 `px` 两种单位。

```ts
// 例如,这里的 100% 是指容器宽度(如果是高度相关的配置 100%  就是容器的高度)
manager.setGap('(100% - 10px) / 5');
```

### 限制为顶部 3 条弹幕

```ts
// 如果我们希望轨道高度为 50px
manager.setTrackHeight('100% / 3');

// 如果不设置渲染区域,轨道的高度会根据默认的 container.height / 3 得到,
// 这可能导致轨道高度不是你想要的
manager.setArea({
  y: {
    start: 0,
    // 3 条轨道的总高度为 150px
    end: 150,
  },
});
```

### 限制为中间 3 条弹幕

```ts
manager.setTrackHeight('100% / 3');

manager.setArea({
  y: {
    start: `50%`,
    end: `50% + 150`,
  },
});
```

### 限制为几条不连续的轨道
限制为几条不连续的轨道,除了要做和连续轨道的操作之外,还需要借助 [**`willRender`**]( https://imtaotao.github.io/danmu/document/zh/reference/manager-hooks/#hooks-willrender) 这个钩子来实现。

```ts
// 如果我们希望轨道高度为 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` 来设置**

```ts
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` 来设置**

```ts
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]);
  }
});
```

### 自定义容器样式

```ts
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`** 这两个位置举例。由于我们需要自定义位置,所以我们需要使用高级弹幕的能力。

### 将弹幕固定在顶部

```ts
// 这条弹幕将会居中距离顶部 10px 的位置悬停 5s
manager.pushFlexibleDanmaku('弹幕内容', {
  duration: 5000,
  direction: 'none',
  position(danmaku, container) {
    return {
      x: `50% - ${danmaku.getWidth() / 2}`,
      y: 10, // 具体容器顶部的距离为 10px
    };
  },
});
```

### 固定在顶部第 2 条轨道上

```ts
// 这条弹幕将会在第二条轨道居中的位置悬停 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,
    };
  },
});
```

### 将弹幕固定在左边

```ts
// 这条弹幕将会在容器中间距离左边 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** 来实现演示。

### 开发弹幕组件

```ts
export function Danmaku({ danmaku }) {
  return (
    <div>
      <img src="https://abc.jpg" />
      {danmaku.data}
    </div>
  );
}
```

### 渲染弹幕

```ts
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`,以便于调试定位问题(注意不要和其他插件冲突了)。
-   插件可以选择性的声明一个 `fersion`,这在你的插件作为独立包发到 `npm` 上时很有用。

```ts
export function filter({ userIds, keywords }) {
  return (manager) => {
    return {
      name: 'filter-keywords-or-user',
      fersion: '1.0.0', // fersion 字段不是必须的
      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()` 来注册插件。

```ts
import { create } from 'danmu';

const manager = create<{
  userId: number;
  content: string;
}>();

manager.use(
  filter({
    userIds: [1],
    keywords: ['菜'],
  }),
);
```

### 发送弹幕

-   ❌ **会**被插件阻止渲染

```ts
manager.push({
  userId: 1,
  content: '',
});
```

-   ❌ **会**被插件阻止渲染

```ts
manager.push({
  userId: 2,
  content: '你真菜',
});
```

-   ✔️ **不会**被插件阻止渲染

```ts
manager.push({
  userId: 2,
  content: '',
});
```

-   ✔️ **不会**被插件阻止渲染

```ts
manager.push({
  userId: 2,
  content: '你真棒',
});
```

## 总结

本文档只是简单介绍了下现在的部分能力,更详细的文档在官网可以查看,如果对你的业务或者学习有帮助的,[**给个 star 支持一下作者**]( https://github.com/imtaotao/danmu),也欢迎大家评论探讨(不止弹幕,哈哈)。
举报· 46 次点击
登录 注册 站外分享
2 条回复  
kk2syc 初学 2024-9-2 20:27:02
+1s
Tubbs 小成 2024-9-3 10:58:44

这可能是见过的最好用的弹幕库 🔥🔥

看着很不错,支持一下
返回顶部