本周一直在实现弹幕的效果 最后看到弹幕看到这位大佬实现的封装感觉很厉害就去学习了一下他的源码 并且仿照他写了一部分。
弹幕类
这是基本的弹幕类 属性为弹幕id time text fontSize lineHeight color render 并且br是render类的实例
这是对弹幕的渲染
this.sections.forEach( section => {
ctx.fillText(section.text, this.left + section.leftOffset, this.top + section.topOffset);
})
import BarrageRenderer from '../index'
import Utils from '../utils';
//参数对象
export type BaseBarrageOptions = {
//弹幕id
id: string;
//时间
time: number;
//文本内容
text: string;
fontSize: number;
lineHeight: number;
color: string;
prior?: boolean;
//自定义render
customRender?: CustomRender;
addition?: {
[key: string]: any
};
barrageType: string
}
//弹幕类
export default abstract class BaseBarrage {
id!: string;
readonly abstract barrageType: BarrageType;
time!: number;
text!: string;
fontSize!: number;
lineHeight!: number;
color!: string;
prior!: boolean;
//自定义render
customRender?: CustomRender;
addition?: Record<string, any>;
//渲染器实例
br: BarrageRenderer;
top!: number;
left!: number;
width!: number;
height!: number
sections: Section[]=[];
protected constructor({
id,
time,
text,
fontSize,
lineHeight,
color,
prior = false,
customRender,
addition,
}: BaseBarrageOptions, barrageRenderer: BarrageRenderer) {
this.id = id;
this.time = time;
this.text = text;
this.fontSize = fontSize;
this.lineHeight = lineHeight;
this.color = color;
this.prior = prior;
this.customRender = customRender;
this.addition = addition;
this.br = barrageRenderer;
this.initBarrage()
}
// //渲染指定上下文
render(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D){
ctx.beginPath()
//如果是自定义渲染弹幕的话 触发自定义渲染函数
if(this.customRender){
this.customRender.renderFn({
ctx,
barrage:this,
br:this.br,
})
return
}
if(this.br.devConfig.isRenderBarrageBorder){
ctx.strokeStyle='#FF0000';
ctx.strokeRect(this.left, this.top, this.width, this.height);
}
//是不是重要的
if(this.prior){
if(this.br.renderConfig.priorBorderCustomRender){
this.br.renderConfig.priorBorderCustomRender({ctx,barrage:this,br:this.br})
}else{
ctx.strokeStyle='#89D5FF'
ctx.strokeRect(this.left,this.top,this.width,this.height)
}
}
this.setCtxFont(ctx)
ctx.fillStyle=this.color
this.sections.forEach( section => {
ctx.fillText(section.text, this.left + section.leftOffset, this.top + section.topOffset);
})
}
initBarrage() {
const sectionObjects = this.analyseText(this.text);
// 整个弹幕的宽
let totalWidth = 0;
// 整个弹幕的高
let maxHeight = 0;
// 计算转换成 sections
const sections: Section[] = [];
sectionObjects.forEach(sectionObject => {
// 设置好文本状态后,进行文本的测量
this.setCtxFont(this.br.ctx);
const textWidth = this.br.ctx?.measureText(sectionObject.value).width || 0;
const textHeight = this.fontSize * this.lineHeight;
totalWidth += textWidth;
maxHeight = maxHeight < textHeight ? textHeight : maxHeight;
// 构建文本片段
sections.push(new TextSection({
text: sectionObject.value,
width: textWidth,
height: textHeight,
leftOffset: Utils.Math.sum(sections.map(section => section.width)),
}));
})
this.sections = sections;
// 设置当前弹幕的宽高,如果自定义中定义了的话,则取自定义中的 width 和 height,因为弹幕实际呈现出来的 width 和 height 是由渲染方式决定的
this.width = this.customRender?.width ?? totalWidth;
this.height = this.customRender?.height ?? maxHeight;
// 遍历计算各个 section 的 topOffset
this.sections.forEach(item => {
if (item.sectionType === 'text') {
item.topOffset = (this.height - this.fontSize) / 2;
} else {
item.topOffset = (this.height - item.height) / 2;
}
});
}
analyseText(barrageText: string): Segment[] {
const segments: Segment[] = [];
// 字符串解析器
while (barrageText) {
// 尝试获取 ]
const rightIndex = barrageText.indexOf(']');
if (rightIndex !== -1) {
// 能找到 ],尝试获取 rightIndex 前面的 [
const leftIndex = barrageText.lastIndexOf('[', rightIndex);
if (leftIndex !== -1) {
// [ 能找到
if (leftIndex !== 0) {
// 如果不等于 0 的话,说明前面是 text
segments.push({
type: 'text',
value: barrageText.slice(0, leftIndex),
})
}
segments.push({
type: rightIndex - leftIndex > 1 ? 'image' : 'text',
value: barrageText.slice(leftIndex, rightIndex + 1),
});
barrageText = barrageText.slice(rightIndex + 1);
} else {
// [ 找不到
segments.push({
type: 'text',
value: barrageText.slice(0, rightIndex + 1),
})
barrageText = barrageText.slice(rightIndex + 1);
}
} else {
// 不能找到 ]
segments.push({
type: 'text',
value: barrageText,
});
barrageText = '';
}
}
// 相邻为 text 类型的需要进行合并
const finalSegments: Segment[] = [];
let currentText = '';
for (let i = 0; i < segments.length; i++) {
if (segments[i].type === 'text') {
currentText += segments[i].value;
} else {
if (currentText !== '') {
finalSegments.push({ type: 'text', value: currentText });
currentText = '';
}
finalSegments.push(segments[i]);
}
}
if (currentText !== '') {
finalSegments.push({ type: 'text', value: currentText });
}
return finalSegments;
}
setCtxFont(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D) {
ctx.font = `${this.br.renderConfig.fontWeight} ${this.fontSize}px`;
}
}
//自定义渲染
export type CustomRender = {
// 弹幕的宽(弹幕实际的宽由具体的渲染操作决定,所以这里由用户自行传入)
width: number;
// 弹幕的高(弹幕实际的高由具体的渲染操作决定,所以这里由用户自行传入)
height: number;
// 弹幕自定义渲染函数
renderFn: RenderFn;
}
//自定义渲染函数
export type CustomRenderOptions = {
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
barrage: BaseBarrage;
br: BarrageRenderer;
//在lib目录下index.ts
}
export type TextSectionOptions = {
// 文本片段的内容
text: string;
// 这段文本的宽高
width: number;
height: number;
// 当前片段相较于整体弹幕 top 和 left 的偏移量
topOffset?: number;
leftOffset: number;
}
export class TextSection {
readonly sectionType = 'text';
text: string;
width: number;
height: number;
topOffset!: number;
leftOffset: number;
constructor({
text,
width,
height,
leftOffset,
}: TextSectionOptions) {
this.text = text;
this.width = width;
this.height = height;
this.leftOffset = leftOffset;
}
}
export type Segment = {
type: 'text' | 'image',
value: string
}
export type Section = TextSection;
export type RenderFn = (options: CustomRenderOptions) => void;
export type BarrageType = 'scroll' | 'top' | 'bottom' | 'senior';
渲染类
在初始化的时候传入 对初始化render进行处理
barrageRenderer.value=new BarrageRenderer({
container:"container",
video:video.value,
renderConfig: {
heightReduce: 60,
speed: currentSpeed.value,
renderRegion:1,
fontWeight: "bold",
opacity: opacity.value / 100,
avoidOverlap: avoidFlag.value,
barrageFilter: (barrage: BaseBarrage) => {
// 弹幕类型的过滤
// 弹幕等级过滤
if (barrage.addition?.grade < shieldGrade.value) return false;
// 其他情况,不过滤
return true;
},
priorBorderCustomRender: ({ ctx, barrage }) => {
ctx.save();
// 设定矩形左上角的偏移量
const leftOffset = 6;
const topOffset = 2;
const { left, top, width, height } = barrage;
// 设置圆角矩形路径
ctx.roundRect(
left - leftOffset,
top - topOffset,
width + 2 * leftOffset,
height + 2 * topOffset,
10
);
// 绘制边框
ctx.strokeStyle = "#89D5FF";
ctx.lineWidth = 2;
ctx.stroke();
// 绘制背景色
ctx.fillStyle = "rgba(137, 213, 255, 0.3)";
ctx.fill();
ctx.restore();
},
},
devConfig: {
isRenderFPS: true,
isRenderBarrageBorder: false,
isLogKeyData: true,
},
})
setDom对Canvas进行初始化 container是父元素 根据子绝父相进行定位
handleHighDprVague进行模糊现象的处理
resize send等方法调用的时候调用布局计算器的实例 也就是 BarrageLayoutCalculate
import type { BarrageOptions } from "./baseBarrage";
import type { RenderFn } from "./baseBarrage/barrageClass"
import type BaseBarrage from "./baseBarrage/barrageClass"
import BarrageLayoutCalculate from "./core"
import Utils from './utils';
//渲染器实例
//弹幕渲染器
export default class BarrageRenderer {
//容器dom
container!: HTMLElement | null
video!: HTMLVideoElement
canvas!: HTMLCanvasElement
ctx!: CanvasRenderingContext2D
//默认渲染配置
defalutRenderConfig: RenderConfig = {
heightReduce: 0,
speed: 200,
opacity: 1,
renderRegion: 1,
avoidOverlap: true,
minSpace: 10,
fontWeight:"normal",
barrageFilter: function (barrage: BaseBarrage): boolean {
throw new Error("Function not implemented.");
}
}
defalurtDeviceConfig: DevConfig = {
isRenderFPS: false,
isRenderBarrageBorder: false,
isLogKeyData: false,
}
devConfig:DevConfig=this.defalurtDeviceConfig
renderConfig: RenderConfig = this.defalutRenderConfig
isOpen = true
animationHandle?: number;
lastContainerHeight = { width: 0, height: 0 }
offscreenCanvans!: HTMLCanvasElement
offerscreenCtx: CanvasRenderingContext2D
dpr = Utils.Canvas.getDevicePixelRatio();
fps = ''
lastFrameTime?: number
lastScalcTime = 0
//布局计算器
barrageLayoutCalculate = new BarrageLayoutCalculate({ barrageRenderer: this, })
offscreenCanvasCtx: any;
constructor({
container,
video,
renderConfig,
devConfig,
barrages,
}: RendererOptions) {
this.video = video;
// 先设置好渲染配置
this.setRenderConfigInternal(renderConfig || {}, true);
// 先设置好开发配置
this.setDevConfig(devConfig || {});
// 创建、处理相关 DOM
this.container =
typeof container === 'string'
? document.getElementById(container)
: container;
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D;
this.offscreenCanvans=document.createElement('canvas')
this.offerscreenCtx = this.offscreenCanvans.getContext('2d') as CanvasRenderingContext2D;
this.setDom(this.container,this.canvas,this.ctx)
//设置弹幕数据
this.setBarrage(barrages)
}
setDevConfig(devConfig: Partial<DevConfig>) {
Object.assign(this.devConfig, devConfig);
}
//弹幕是否被打开
private setRenderConfigInternal(renderConfig: any, init = false) {
//获取弹幕渲染配置属性
const renderCofigKeys = Object.keys(renderConfig)
const isSpeedChange = renderCofigKeys.includes('speed') && renderConfig.speed !== this.renderConfig.speed;
const isHeightReduceChange = renderCofigKeys.includes('heightReduce') && renderConfig.heightReduce !== this.renderConfig.heightReduce;
const isRenderRegionChange = renderCofigKeys.includes('renderRegion') && renderConfig.renderRegion !== this.renderConfig.renderRegion;
const isAvoidOverlapChange = renderCofigKeys.includes('avoidOverlap') && renderConfig.avoidOverlap !== this.renderConfig.avoidOverlap;
const isMinSpaceChange = renderCofigKeys.includes('minSpace') && renderConfig.minSpace !== this.renderConfig.minSpace;
Object.assign(this.renderConfig, renderConfig)
if (!init && (isSpeedChange || isHeightReduceChange || isRenderRegionChange || isAvoidOverlapChange)) {
// 高度缩减需要重新处理 DOM
if (isHeightReduceChange) this.setDom(this.container, this.canvas, this.ctx);
this.barrageLayoutCalculate.renderConfigChange(
isSpeedChange,
isHeightReduceChange,
isRenderRegionChange,
isAvoidOverlapChange,
isMinSpaceChange,
);
}
// 触发一帧的渲染
if (!this.animationHandle && !init) {
this.renderShow();
}
}
setRenderConfig(renderConfig: any) {
this.setRenderConfigInternal(renderConfig)
}
private setDom(container:HTMLElement|null,canvas:HTMLCanvasElement,ctx:CanvasRenderingContext2D){
if(!container){console.log('没有容器');}
if(!ctx){console.log('没有设置画布')}
if(!container||!ctx) return
container.style.position='realtive'
canvas.style.position='absolute'
canvas.style.left='0px'
canvas.style.top = '0px';
canvas.width=container.clientWidth
canvas.height=container.clientHeight-(this.renderConfig.heightReduce|0)
container.appendChild(canvas)
this.handleHighDprVague(canvas, ctx);
// 需要同步处理离屏 canvas
this.offscreenCanvans.width = container.clientWidth;
this.offscreenCanvans.height = container.clientHeight - (this.renderConfig.heightReduce ?? 0);
this.handleHighDprVague(this.offscreenCanvans, this.offerscreenCtx);
}
//设置弹幕数据
setBarrage(barrage?:BarrageOptions[]){
if(!barrage) return
//判断弹幕是不是合规的
barrage=barrage.filter(barrage=>{return this.deleteNoRulues(barrage)==true})
this.barrageLayoutCalculate.setBarrages(barrage);
this.lastContainerHeight = {
width: this.container?.clientWidth || 0,
height: this.container?.clientHeight || 0,
}
}
private handleHighDprVague(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
const logicalWidth = canvas.width;
const logicalHeight = canvas.height;
canvas.width = logicalWidth * this.dpr;
canvas.height = logicalHeight * this.dpr;
canvas.style.width = logicalWidth + 'px';
canvas.style.height = logicalHeight + 'px';
ctx.scale(this.dpr, this.dpr);
ctx.textBaseline = 'hanging';
}
//判断弹幕是否合法 还有高级 已经error返回的具体形式没写
private deleteNoRulues(barrage:BarrageOptions){
if(barrage.barrageType=='top'|| barrage.barrageType=='bottom'){
if(barrage.duration<=0){
return false
}
}
return true
}
//发送新的弹幕
send(barrage:BarrageOptions){
const result=this.deleteNoRulues(barrage)
if(result!==true){
console.log(result);
return
}
this.barrageLayoutCalculate.send(barrage)
}
//重新计算
resize(){
this.setDom(this.container,this.canvas,this.ctx)
const nowSizecontainer={
width:this.container?.clientWidth||0,
height:this.container?.clientHeight||0
}
const {width,height}=this.lastContainerHeight
const widthDiff=width!==nowSizecontainer.width
const heightDiff=height!==nowSizecontainer.height
if(widthDiff||heightDiff){
//记录一下
this.lastContainerHeight=nowSizecontainer
if(widthDiff && !heightDiff){
//宽度发生变化
this.barrageLayoutCalculate.resize('width')
}else if(heightDiff && !widthDiff){
//宽度发生变化
this.barrageLayoutCalculate.resize('height')
}else{
this.barrageLayoutCalculate.resize('both')
}
}
this.renderFrame()
}
renderFrame(){
if(!this.animationHandle){this.renderShow()}
}
private renderShow(){
if(!this.isOpen) return
let renderBarrages=this.barrageLayoutCalculate.getRenderBarrages(this.progress)
if(this.renderConfig.barrageFilter){
renderBarrages=renderBarrages.filter(barrage=>this.renderConfig.barrageFilter!(barrage))
}
this.offerscreenCtx.clearRect(0,0,this.offscreenCanvans.width,this.offscreenCanvans.height)
this.offerscreenCtx.globalAlpha=this.renderConfig.opacity
renderBarrages.forEach((barrage:BaseBarrage) => {
barrage.render(this.offerscreenCtx)
});
if(this.devConfig.isRenderFPS) this.renderFPS()
this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)
this.offscreenCanvans.width && this.ctx.drawImage(
this.offscreenCanvans,0,0,this.offscreenCanvans.width,this.offscreenCanvans.height,
0,0,this.canvas.width/this.dpr,this.canvas.height/this.dpr)
if(this.animationHandle){
requestAnimationFrame(()=>this.renderShow())
}
}
get progress(){
return this.viedoStatus.currentTime;
}
get viedoStatus(){
return{
// currenTime
currentTime:this.video.currentTime*1000,
playing:!this.video.paused
}
}
private renderFPS(){
const now=Date.now();
if(this.lastFrameTime && now -this.lastScalcTime>200){
this.fps = `${Math.floor(1000 / (now - this.lastFrameTime))}FPS`;
this.lastScalcTime = now;
}
this.lastFrameTime = now;
if (this.fps) {
this.offerscreenCtx.font = 'bold 32px Microsoft YaHei';
this.offerscreenCtx.fillStyle = 'blue';
this.offerscreenCtx.fillText(this.fps, 20, 30);
}
}
private createAnimation(){
if(!this.animationHandle && this.isOpen){
this.animationHandle=requestAnimationFrame(()=>
{this.renderShow()})
}
}
play(){
this.createAnimation()
}
pause() {
this.animationHandle && cancelAnimationFrame(this.animationHandle);
this.animationHandle = undefined;
}
get canvaSize(){
return {
width:this.canvas.width/this.dpr,
height:this.canvas.height/this.dpr
}
}
getRenderBarrages(time: number): BaseBarrage[] {
// 获取需要渲染的滚动弹幕
const renderScrollBarrages = this.getRenderBarrages(time);
// 获取需要渲染的固定弹幕
const renderFixedBarrages = this.getRenderBarrages(time);
// 获取需要渲染的高级弹幕
const renderSeniorBarrages = this.getRenderBarrages(time);
// 整合排序
return [
...renderScrollBarrages,
...renderFixedBarrages,
...renderSeniorBarrages,
].sort((a, b) => {
if (a.prior !== b.prior) {
// 如果 a 的 prior 为 true,则返回 1,否则返回 -1
// 这意味着 true 的值会在最后面
return a.prior ? 1 : -1;
} else {
// 如果 prior 属性相同,则按照 time 属性排序
return a.time - b.time;
}
});
}
}
export type RenderConfig = {
barrageFilter: (barrage: BaseBarrage) => boolean
priorBorderCustomRender?: RenderFn;
heightReduce: number;
speed: number;
renderRegion: number;
minSpace: number;
avoidOverlap: boolean;
opacity: number;
fontWeight:string
}
export type RendererOptions = {
// 容器 DOM
container: string | HTMLElement;
// video 元素(获取 video 元素,只是为了获取播放状态,不会对 video 执行其他操作)
video: HTMLVideoElement;
// 弹幕数据
barrages?: BarrageOptions[];
// // 渲染相关配置
renderConfig?: Partial<RenderConfig>;
// // 开发相关配置
devConfig?: Partial<DevConfig>,
}
//开发相关配置
export type DevConfig = {
// 是否渲染 fps
isRenderFPS: boolean;
// 是否渲染弹幕边框
isRenderBarrageBorder: boolean;
// 是否打印关键数据
isLogKeyData: boolean;
}
export {
BarrageRenderer,
};
布局计算器类
setBarrages对于类型不同 返回不同的实体类
import type BarrageRenderer from "..";
import type { BarrageOptions } from "../baseBarrage";
import type BaseBarrage from "../baseBarrage/barrageClass";
import type { BaseBarrageOptions } from "../baseBarrage/barrageClass";
import FixedBarrage from "../baseBarrage/fixedBarrage";
import SrollBarrage from "../baseBarrage/srollBarrage";
import FixedBarrageLayout from "./fixed-barrage-layout";
import Utils from '../utils';
import VirtualBarrage from "./virtualScroll";
export default class BarrageLayoutCalculate {
br!: BarrageRenderer
allBarrage: BaseBarrage[] = []
fixedBarrage: FixedBarrage[] = []
scrollBarrage: SrollBarrage[] = []
senioBarrage!: []
fixedLayout!: FixedBarrageLayout
virtualTrackAlgorithm: VirtualBarrage;
constructor({ barrageRenderer }: BarrageLayoutCalculateOptions) {
this.br = barrageRenderer
this.fixedLayout = new FixedBarrageLayout(this.br)
this.virtualTrackAlgorithm = new VirtualBarrage(this.br);
}
setBarrages(barrageOptions: BarrageOptions[]) {
let barrageInstance = barrageOptions.map((barrageOption) => {
switch (barrageOption.barrageType) {
case 'top':
case 'bottom':
return new FixedBarrage(barrageOption, this.br);
case 'scroll':
return new SrollBarrage(barrageOption, this.br);
// case 'senior':
//高级弹幕 还没写
}
})
barrageInstance = barrageInstance.sort((a, b) => a!.time - b!.time)
this.allBarrage = barrageInstance
this.fixedBarrage = barrageInstance.filter(instance =>
['top', 'bottom'].includes(instance.barrageType)) as FixedBarrage[];
this.scrollBarrage = barrageInstance.filter(instance => instance.barrageType === 'scroll') as SrollBarrage[];
this.virtualTrackAlgorithm.layoutScrollBarrages(this.scrollBarrage);
}
resize(type: 'width' | 'height' | 'both') {
// 高级弹幕的 start 和 end 定位全部重新计算
if (type === 'width') {
this.handleWidthChange();
} else if (type === 'height') {
this.handleHeightChange();
} else {
this.handleWidthChange();
this.handleHeightChange();
}
}
private handleWidthChange() {
// 固定弹幕 -- 重新计算 left
this.fixedBarrage.forEach(barrage => barrage.calFixedBarrageLeft());
// 滚动弹幕 -- 重新计算 originalLeft
this.scrollBarrage.forEach(barrage => barrage.cancalSrcoll());
}
private handleHeightChange () {
// 固定弹幕 -- 清空现有的即可
this.fixedLayout.clearStoredBarrage();
// 滚动弹幕 -- 布局完全重新计算
this.virtualTrackAlgorithm.heightChangeReLayoutCalc(this.scrollBarrage);
}
send(barrage: BarrageOptions) {
// 根据弹幕类型进行不同的处理
if (barrage.barrageType === 'scroll') {
// 滚动弹幕
const scrollBarrage = new SrollBarrage(barrage, this.br);
Utils.Algorithm.insertBarrageByTime(this.scrollBarrage, scrollBarrage);
this.virtualTrackAlgorithm.send(scrollBarrage);
} else if (barrage.barrageType === 'top' || barrage.barrageType === 'bottom') {
// 固定弹幕
const fixedBarrage = new FixedBarrage(barrage, this.br);
Utils.Algorithm.insertBarrageByTime(this.fixedBarrage, fixedBarrage);
this.fixedLayout.send(fixedBarrage);
// } else if (barrage.barrageType === 'senior') {
// // 高级弹幕
// const seniorBarrage = new SeniorBarrage(barrage, this.br);
// Utils.Algorithm.insertBarrageByTime(this.seniorBarrageInstances, seniorBarrage);
// }
}
}
getRenderBarrages(time: number): BaseBarrage[] {
// 获取需要渲染的滚动弹幕
const renderScrollBarrages = this.getRenderScrollBarrages(time);
// 获取需要渲染的固定弹幕
const renderFixedBarrages = this.getRenderFixedBarrages(time);
// 获取需要渲染的高级弹幕
console.log(renderFixedBarrages);
// 整合排序
return [
...renderScrollBarrages,
...renderFixedBarrages,
].sort((a, b) => {
if (a.prior !== b.prior) {
// 如果 a 的 prior 为 true,则返回 1,否则返回 -1
// 这意味着 true 的值会在最后面
return a.prior ? 1 : -1;
} else {
// 如果 prior 属性相同,则按照 time 属性排序
return a.time - b.time;
}
});
}
renderConfigChange(
isSpeedChange: boolean,
isHeightReduceChange: boolean,
isRenderRegionChange: boolean,
isAvoidOverlapChange: boolean,
isMinSpaceChange: boolean,
) {
// 重新计算 originalLeft 事项
if (isSpeedChange) {
this.scrollBarrage.forEach(barrage => barrage.cancalSrcoll());
}
// 清空固定弹幕的 store 事项
if (isHeightReduceChange) {
this.fixedLayout.clearStoredBarrage();
}
// 重置轨道数据 事项
if (isHeightReduceChange || isRenderRegionChange) {
this.virtualTrackAlgorithm.resetTracks();
}
// 根据 avoidOverlap 进行重新布局 事项
if (
(isSpeedChange && this.br.renderConfig.avoidOverlap) ||
isHeightReduceChange ||
isRenderRegionChange ||
isAvoidOverlapChange ||
(isMinSpaceChange && this.br.renderConfig.avoidOverlap)
) {
this.virtualTrackAlgorithm.layoutScrollBarrages(this.scrollBarrage);
}
}
getRenderScrollBarrages(time: number): SrollBarrage[] {
// 弹幕整体向左移动的总距离,时间 * 速度
const translateX = (time / 1000) * this.br.renderConfig.speed;
console.log(this.scrollBarrage,'4888');
const renderScrollBarrages = this.scrollBarrage.filter(barrage => barrage.show && barrage.top !== undefined).filter(barrage =>
barrage.originalRright - translateX >= 0 &&
barrage.originalLeft - translateX < this.br.canvaSize.width
);
renderScrollBarrages.forEach(barrage => {
barrage.left = barrage.originalLeft - translateX;
})
return renderScrollBarrages;
}
getRenderFixedBarrages(time: number): FixedBarrage[] {
return this.fixedLayout.getRenderFixedBarrages(this.fixedBarrage, time);
}
}
export type BarrageLayoutCalculateOptions = {
barrageRenderer: BarrageRenderer
}
总结
这个实现效果还是有点问题 但是看别人的代码学习确实很有效果 学习到了怎么在ts定义变量并在vue使用 学习到了反射思想等等 还是很有收获的