refactor: 移除弹幕项的消失动画逻辑,简化弹幕处理逻辑

This commit is contained in:
2025-04-18 03:40:09 +08:00
parent 5891f20f86
commit 521cd1eddf
3 changed files with 35 additions and 170 deletions

View File

@@ -8,12 +8,12 @@ const props = defineProps<BaseDanmakuItemProps>();
// 使用工具函数获取基础计算属性 // 使用工具函数获取基础计算属性
const emojiData = useDanmakuWindow().emojiData; const emojiData = useDanmakuWindow().emojiData;
const { isDisappearing, typeClass } = useDanmakuUtils(props, emojiData); const { typeClass } = useDanmakuUtils(props, emojiData);
</script> </script>
<template> <template>
<div <div
:class="['danmaku-item-content', typeClass, { 'disappearing': isDisappearing }]" :class="['danmaku-item-content', typeClass]"
:data-disappear="item.disappearAt" :data-disappear="item.disappearAt"
> >
<!-- 根据设置选择显示风格 --> <!-- 根据设置选择显示风格 -->
@@ -47,22 +47,4 @@ const { isDisappearing, typeClass } = useDanmakuUtils(props, emojiData);
font-size: var(--dw-font-size); font-size: var(--dw-font-size);
color: var(--dw-text-color); color: var(--dw-text-color);
} }
.danmaku-item-content.disappearing {
animation: danmaku-out var(--dw-animation-duration, 300ms) ease forwards;
pointer-events: none;
}
/* 动画相关 */
@keyframes danmaku-out {
from {
opacity: var(--dw-opacity);
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-30px);
}
}
</style> </style>

View File

@@ -9,7 +9,6 @@
type TempDanmakuType = EventModel & { type TempDanmakuType = EventModel & {
randomId: string; randomId: string;
isNew?: boolean; // 添加:标记是否为新弹幕
disappearAt?: number; // 消失时间戳 disappearAt?: number; // 消失时间戳
timestamp?: number; // 添加:记录插入时间戳 timestamp?: number; // 添加:记录插入时间戳
}; };
@@ -71,24 +70,11 @@
disappearAt = Date.now() + setting.value.autoDisappearTime * 1000; disappearAt = Date.now() + setting.value.autoDisappearTime * 1000;
} }
// 判断短时间内是否有大量弹幕插入
const isRapidInsertion = danmakuList.value.filter(item =>
item.isNew && Date.now() - (item.timestamp || 0) < 500).length > 5;
if (isRapidInsertion && !isInBatchUpdate.value) {
isInBatchUpdate.value = true;
// 在大量插入时简化动画300ms后恢复
setTimeout(() => {
isInBatchUpdate.value = false;
}, 300);
}
// 为传入的弹幕对象添加一个随机ID和isNew标记 // 为传入的弹幕对象添加一个随机ID和isNew标记
const dataWithId = { const dataWithId = {
...data, ...data,
randomId: nanoid(), // 生成一个随机ID randomId: nanoid(), // 生成一个随机ID
disappearAt, // 添加消失时间 disappearAt, // 添加消失时间
isNew: true, // 标记为新弹幕,用于动画
timestamp: Date.now(), // 添加时间戳记录插入时间 timestamp: Date.now(), // 添加时间戳记录插入时间
}; };
@@ -96,35 +82,8 @@
// 优化超出长度的弹幕处理 - 改为标记并动画方式移除 // 优化超出长度的弹幕处理 - 改为标记并动画方式移除
if (danmakuList.value.length > maxItems.value) { if (danmakuList.value.length > maxItems.value) {
// 找到要移除的项目 danmakuList.value.splice(maxItems.value, danmakuList.value.length - maxItems.value);
const itemsToRemove = danmakuList.value.slice(maxItems.value);
itemsToRemove.forEach(item => {
if (!pendingRemovalItems.value.includes(item.randomId)) {
pendingRemovalItems.value.push(item.randomId);
} }
});
// 延迟移除,给动画足够时间
setTimeout(() => {
danmakuList.value = danmakuList.value.filter(item =>
!pendingRemovalItems.value.includes(item.randomId) ||
item.timestamp && Date.now() - item.timestamp < setting.value!.animationDuration
);
// 更新待移除列表
pendingRemovalItems.value = pendingRemovalItems.value.filter(id =>
danmakuList.value.some(item => item.randomId === id)
);
}, setting.value.animationDuration || 300);
}
// 设置一个定时器在动画完成后移除isNew标记
setTimeout(() => {
const index = danmakuList.value.findIndex(item => item.randomId === dataWithId.randomId);
if (index !== -1) {
danmakuList.value[index].isNew = false;
}
}, setting.value.animationDuration || 300);
//console.log('[DanmakuWindow] 添加弹幕:', dataWithId); //console.log('[DanmakuWindow] 添加弹幕:', dataWithId);
} }
@@ -139,14 +98,7 @@
// 先标记将要消失的弹幕 // 先标记将要消失的弹幕
danmakuList.value.forEach(item => { danmakuList.value.forEach(item => {
if (item.disappearAt && item.disappearAt <= now && !pendingRemovalItems.value.includes(item.randomId)) { if (item.disappearAt && item.disappearAt <= now && !pendingRemovalItems.value.includes(item.randomId)) {
// 标记为待移除,但还不实际移除 danmakuList.value.splice(danmakuList.value.indexOf(item), 1);
pendingRemovalItems.value.push(item.randomId);
// 延迟删除,让动画有时间完成
setTimeout(() => {
danmakuList.value = danmakuList.value.filter(d => d.randomId !== item.randomId);
pendingRemovalItems.value = pendingRemovalItems.value.filter(id => id !== item.randomId);
}, animationDuration);
} }
}); });
} }
@@ -232,27 +184,21 @@
tag="div" tag="div"
class="danmaku-list-container" class="danmaku-list-container"
> >
<div <ClientDanmakuItem
v-for="item in danmakuList" v-for="item in danmakuList"
:key="item.randomId" :key="item.randomId"
:data-type="item.type"
class="danmaku-item"
:class="{
'danmaku-item-leaving': pendingRemovalItems.includes(item.randomId),
'batch-item': isInBatchUpdate
}"
>
<ClientDanmakuItem
:item="item" :item="item"
:setting="setting" :setting="setting"
:data-type="item.type"
class="danmaku-item"
/> />
</div>
</TransitionGroup> </TransitionGroup>
</div> </div>
</div> </div>
</template> </template>
<style> <style>
html, html,
body { body {
background: transparent; background: transparent;
@@ -279,7 +225,8 @@
.danmaku-window { .danmaku-window {
-webkit-app-region: drag; -webkit-app-region: drag;
overflow: hidden; overflow: hidden;
background-color: transparent; /* 完全透明背景 */ background-color: transparent;
/* 完全透明背景 */
width: 100%; width: 100%;
height: 100%; height: 100%;
color: var(--dw-text-color); color: var(--dw-text-color);
@@ -300,7 +247,8 @@
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: var(--dw-direction); flex-direction: var(--dw-direction);
box-sizing: border-box; /* 确保padding不会增加元素的实际尺寸 */ box-sizing: border-box;
/* 确保padding不会增加元素的实际尺寸 */
} }
.danmaku-list-container { .danmaku-list-container {
@@ -308,9 +256,8 @@
display: flex; display: flex;
flex-direction: inherit; flex-direction: inherit;
gap: var(--dw-item-spacing); gap: var(--dw-item-spacing);
padding-bottom: 8px; /* 添加底部内边距以防止项目溢出 */ position: relative;
box-sizing: border-box; /* 确保padding不会增加元素的实际尺寸 */ padding-bottom: 8px;
position: relative; /* 为TransitionGroup添加相对定位 */
} }
.danmaku-list.reverse { .danmaku-list.reverse {
@@ -330,24 +277,6 @@
border-radius: 4px; border-radius: 4px;
} }
/* 弹幕项样式 */
.danmaku-item {
transform-origin: center left;
transition: all var(--dw-animation-duration) ease;
/* 添加硬件加速,防止文字模糊 */
transform: translateZ(0);
will-change: transform, opacity;
backface-visibility: hidden;
-webkit-font-smoothing: subpixel-antialiased;
}
/* 正在离开的弹幕项样式 */
.danmaku-item-leaving {
animation: danmaku-leave var(--dw-animation-duration) cubic-bezier(0.4, 0, 0.2, 1) forwards;
opacity: 0.8; /* 轻微降低不透明度,提高视觉层次感 */
z-index: -1; /* 确保离开的项在其他项下方 */
}
/* 批量更新模式下的优化 */ /* 批量更新模式下的优化 */
.batch-update .danmaku-list-move { .batch-update .danmaku-list-move {
transition-duration: 100ms !important; transition-duration: 100ms !important;
@@ -358,73 +287,42 @@
transition-duration: 100ms !important; transition-duration: 100ms !important;
} }
/* TransitionGroup动画效果 */ /* 1. declare transition */
.danmaku-list-move,
.danmaku-list-enter-active, .danmaku-list-enter-active,
.danmaku-list-leave-active,
.danmaku-list-move {
transition: all var(--dw-animation-duration) cubic-bezier(0.55, 0, 0.1, 1);
/* 确保动画过程中文字不模糊 */
transform: translateZ(0);
will-change: transform, opacity;
backface-visibility: hidden;
}
.danmaku-list-leave-active { .danmaku-list-leave-active {
position: absolute; transition: all var(--dw-animation-duration) cubic-bezier(0.55, 0, 0.1, 1);
pointer-events: none;
z-index: -1;
width: 100%;
}
.danmaku-list-enter-from {
opacity: 0;
transform: scaleY(0.5) translateX(-30px) translateZ(0);
}
.danmaku-list-enter-to {
opacity: 1;
transform: scaleY(1) translateX(0) translateZ(0);
} }
.danmaku-list-enter-from,
.danmaku-list-leave-to { .danmaku-list-leave-to {
opacity: 0; opacity: 0;
transform: scaleY(0.5) translateX(30px) translateZ(0); transform: scaleY(0.01) translate(3000px, 0);
} }
/* 处理已有弹幕的移动动画 */ /* 3. ensure leaving items are taken out of layout flow so that moving
.danmaku-list-move { animations can be calculated correctly. */
transition: transform var(--dw-animation-duration) cubic-bezier(0.55, 0, 0.1, 1); .danmaku-list-leave-active {
/* 确保移动动画时文字不模糊 */ position: absolute;
backface-visibility: hidden;
transform: translateZ(0);
} }
/* 根据弹幕类型提供不同的动画特性 */ /* 根据弹幕类型提供不同的动画特性 */
[data-type="3"] { /* 普通弹幕 */ [data-type="3"] {
/* 普通弹幕 */
--transition-delay: 0.02s; --transition-delay: 0.02s;
} }
[data-type="2"] { /* 礼物 */ [data-type="2"] {
/* 礼物 */
--transition-delay: 0.04s; --transition-delay: 0.04s;
animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); /* 小弹跳效果 */ animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
/* 小弹跳效果 */
} }
[data-type="1"] { /* SC */ [data-type="1"] {
/* SC */
--transition-delay: 0.05s; --transition-delay: 0.05s;
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); /* 特殊强调效果 */ animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
} /* 特殊强调效果 */
/* 添加弹幕消失动画 */
@keyframes danmaku-leave {
0% {
opacity: 1;
transform: translateX(0) translateZ(0);
filter: blur(0px);
}
100% {
opacity: 0;
transform: translateX(30px) translateZ(0);
filter: blur(1px);
}
} }
</style> </style>

View File

@@ -191,18 +191,3 @@ defineExpose({
<template> <template>
<slot /> <slot />
</template> </template>
<style scoped>
/* 公共基础动画样式 */
@keyframes danmaku-out {
from {
opacity: var(--dw-opacity);
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-30px);
}
}
</style>