vue-danmaku
Version:
基于 Vue 的弹幕交互组件 | A danmaku component for Vue
214 lines (179 loc) • 5.71 kB
text/typescript
/**
* requestAnimationFrame动画管理器
* 用于弹幕的性能模式动画处理
*/
// 存储所有动画帧ID的映射
const animationFrames = new Map<HTMLElement, number>()
// 存储所有弹幕元素当前位置的映射
const danmuPositions = new Map<HTMLElement, number>()
// 主循环的动画帧ID
let mainLoopFrameId: number | null = null
/**
* 启动弹幕动画
* @param el 弹幕DOM元素
* @param width 弹幕宽度
* @param containerWidth 容器宽度
* @param speed 弹幕速度(像素/秒)
* @param isPaused 是否处于暂停状态
* @param onAnimationEnd 动画结束回调
*/
export function startAnimation(
el: HTMLDivElement,
width: number,
containerWidth: number,
speed: number,
isPaused: () => boolean,
onAnimationEnd: (el: HTMLDivElement) => void,
moveDirection?: number
): void {
// 设置初始位置
const moveDirectionN = (moveDirection || -1) * -1;
el.style.transform = `translateX(0px)`
el.style[moveDirectionN < 0 ? 'right' : 'left'] = `${containerWidth}px`
// 初始化动画参数
const startTime = performance.now()
const duration = (containerWidth / speed) * 1000 // 转换为毫秒
const startPosition = containerWidth * moveDirectionN
const endPosition = -width * moveDirectionN
// 存储初始位置
danmuPositions.set(el, startPosition)
// 动画帧函数
function animate(timestamp: number) {
if (isPaused()) {
// 如果暂停了,不继续请求动画帧
return
}
const elapsed = timestamp - startTime
if (elapsed >= duration) {
// 动画结束
onAnimationEnd(el)
return
}
// 计算当前位置 (使用线性插值)
const progress = elapsed / duration
const currentPosition = startPosition + (endPosition - startPosition) * progress
// 更新元素位置
el.style.transform = `translateX(${currentPosition - startPosition}px)`
// 存储当前位置
danmuPositions.set(el, currentPosition)
// 请求下一帧
const frameId = requestAnimationFrame(animate)
animationFrames.set(el, frameId)
}
// 启动动画
const frameId = requestAnimationFrame(animate)
animationFrames.set(el, frameId)
}
/**
* 恢复弹幕动画
* @param el 弹幕DOM元素
* @param width 弹幕宽度
* @param containerWidth 容器宽度
* @param speed 弹幕速度
* @param isPaused 是否处于暂停状态
* @param onAnimationEnd 动画结束回调
*/
export function resumeAnimation(
el: HTMLDivElement,
width: number,
containerWidth: number,
speed: number,
isPaused: () => boolean,
onAnimationEnd: (el: HTMLDivElement) => void,
moveDirection?: number
): void {
// 获取存储的位置
const currentPosition = danmuPositions.get(el)
if (currentPosition === undefined) {
// 如果没有位置信息,直接启动新动画
startAnimation(el, width, containerWidth, speed, isPaused, onAnimationEnd)
return
}
const moveDirectionN = (moveDirection || -1) * -1;
// 计算当前已经完成的进度比例
const startPosition = containerWidth * moveDirectionN
const endPosition = -width * moveDirectionN
const totalDistance = startPosition - endPosition
const distanceTraveled = startPosition - currentPosition
const completedProgress = distanceTraveled / totalDistance
// 获取原始动画总时长,与startAnimation保持一致
const totalDuration = (containerWidth / speed) * 1000
// 计算已经过的时间和剩余时间
const elapsedTime = totalDuration * completedProgress
const remainingTime = totalDuration - elapsedTime
// 初始化新的动画参数
const startTime = performance.now() - elapsedTime // 调整开始时间,保持速度连贯
// 动画帧函数
function animate(timestamp: number) {
if (isPaused()) {
// 如果已暂停,则不继续请求动画帧
return
}
const elapsed = timestamp - startTime
if (elapsed >= totalDuration) {
// 动画结束
onAnimationEnd(el)
return
}
// 使用与startAnimation完全相同的计算逻辑
const progress = elapsed / totalDuration
const position = startPosition + (endPosition - startPosition) * progress
// 更新元素位置,使用与startAnimation相同的计算方式
el.style.transform = `translateX(${position - startPosition}px)`
// 存储当前位置
danmuPositions.set(el, position)
// 请求下一帧
const frameId = requestAnimationFrame(animate)
animationFrames.set(el, frameId)
}
// 启动动画
const frameId = requestAnimationFrame(animate)
animationFrames.set(el, frameId)
}
/**
* 暂停所有动画
*/
export function pauseAllAnimations(): void {
// 取消所有动画帧,但保留位置信息
animationFrames.forEach((frameId) => {
cancelAnimationFrame(frameId)
})
// 清空动画帧映射,但保留位置信息
animationFrames.clear()
// 取消主循环
cancelMainLoop()
}
/**
* 取消元素的动画
* @param el 弹幕DOM元素
*/
export function cancelAnimation(el: HTMLElement): void {
const frameId = animationFrames.get(el)
if (frameId) {
cancelAnimationFrame(frameId)
animationFrames.delete(el)
}
}
/**
* 取消所有动画,并清除位置信息
*/
export function cancelAllAnimations(): void {
// 取消所有动画帧
animationFrames.forEach((frameId) => {
cancelAnimationFrame(frameId)
})
// 清空所有映射
animationFrames.clear()
danmuPositions.clear()
// 取消主循环
cancelMainLoop()
}
/**
* 取消主循环
*/
export function cancelMainLoop(): void {
if (mainLoopFrameId !== null) {
cancelAnimationFrame(mainLoopFrameId)
mainLoopFrameId = null
}
}