@silkytone/danmu
Version:
弹幕的简单实现,实现普通弹幕或高级弹幕。
112 lines (102 loc) • 3.49 kB
text/typescript
// core/barrage.ts
import { BarrageItem, BarrageOptions } from '../interface';
import { Animation, Tracks } from '../lib';
import { mergeObject } from '../utils';
// TODO: 弹幕管理
export class Barrage {
private readonly tracks: Tracks;
private readonly $el: HTMLElement;
private readonly animation: Animation;
private readonly opt: Record<string | number, any>;
private readonly size: { width: number; height: number };
// TODO: 移动区
private moveTracks: { start: number; step: number; value: BarrageItem }[] = [];
private waitTracks: BarrageItem[] = [];
private status: boolean = false;
constructor(element: HTMLElement | string, options?: Partial<BarrageOptions>) {
this.opt = mergeObject({ limit: 60 }, options || {});
this.$el = this.createElement(element);
this.animation = new Animation(this.opt.limit);
this.size = {
width: this.$el.clientWidth,
height: this.$el.clientHeight,
};
this.tracks = new Tracks({ ...this.opt, ...this.size });
}
createElement(element: HTMLElement | string) {
const el = (() => {
if (typeof element === 'string') {
const el = document.querySelector<HTMLElement>(element);
if (!el) throw new Error('element not found');
return el;
} else {
return element;
}
})();
//
el.innerHTML = '';
el.style.width = '100%';
el.style.height = '100%';
el.style.overflow = 'hidden';
el.style.userSelect = 'none';
el.style.position = 'relative';
el.style.pointerEvents = 'none';
//
return el;
}
playAnimation() {
if (this.status) return;
this.status = true;
const minWidth = this.size.width;
this.animation.run(() => {
const now = Date.now();
for (const [index, item] of Object.entries(this.moveTracks)) {
const value = (now - item.start) * item.step;
const maxWidth = minWidth + item.value.width;
if (value > maxWidth) {
item.value.destroy();
this.moveTracks.splice(parseInt(index), 1);
} else {
item.value.$el.style.transform = `translateX(-${value}px)`;
}
}
this.addWaitTracks();
if (!this.moveTracks.length) {
this.animation.stop();
this.status = false;
}
});
}
private addWaitTracks() {
if (!this.waitTracks.length) return false;
this.waitTracks = this.waitTracks.filter(item => {
const time = Date.now();
const { $el: el, width, height, duration } = item;
const track = this.tracks.add(width, height, duration, time);
if (track) {
el.style.top = this.tracks.trackToPx(track.index) + 'px';
const step = Math.ceil(this.size.width / duration * 1000000) / 1000000;
this.moveTracks.push({ start: time, step, value: item });
this.playAnimation();
return null;
}
return item;
});
}
push(item: BarrageItem): void {
// TODO: 将弹幕挂载到等待区
this.$el.appendChild(item.$el);
const time = Date.now();
const { $el: el, width, height, duration } = item;
//
const track = this.tracks.add(width, height, duration, time);
if (track) {
el.style.top = this.tracks.trackToPx(track.index) + 'px';
const step = Math.ceil(this.size.width / duration * 1000000) / 1000000;
this.moveTracks.push({ start: time, step, value: item });
this.playAnimation();
} else {
this.waitTracks.push(item);
}
}
}