From 7df4530bcff4ce53165184f163ea032334d30e39 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Thu, 4 Dec 2025 09:28:31 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=B1=8F=E8=94=BD=E5=BC=B9?= =?UTF-8?q?=E5=B9=95=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/play/page.tsx | 11 +- src/components/DanmakuFilterSettings.tsx | 301 ++++++++++++++++++----- 2 files changed, 247 insertions(+), 65 deletions(-) diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index 2f8d1b1..133ef53 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -2373,7 +2373,16 @@ function PlayPageClient() { icon: '', tooltip: '配置弹幕过滤规则', onClick() { - setShowDanmakuFilterSettings(true); + // 如果播放器处于全屏状态,先退出全屏 + if (artPlayerRef.current && artPlayerRef.current.fullscreen) { + artPlayerRef.current.fullscreen = false; + // 延迟一下再显示弹窗,确保全屏退出动画完成 + setTimeout(() => { + setShowDanmakuFilterSettings(true); + }, 300); + } else { + setShowDanmakuFilterSettings(true); + } return '打开设置'; }, }, diff --git a/src/components/DanmakuFilterSettings.tsx b/src/components/DanmakuFilterSettings.tsx index 7fe73d5..c18d158 100644 --- a/src/components/DanmakuFilterSettings.tsx +++ b/src/components/DanmakuFilterSettings.tsx @@ -24,6 +24,89 @@ export default function DanmakuFilterSettings({ const [newType, setNewType] = useState<'normal' | 'regex'>('normal'); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); + const [isVisible, setIsVisible] = useState(false); + const [isAnimating, setIsAnimating] = useState(false); + + // 控制动画状态 + useEffect(() => { + let animationId: number; + let timer: NodeJS.Timeout; + + if (isOpen) { + setIsVisible(true); + // 使用双重 requestAnimationFrame 确保DOM完全渲染 + animationId = requestAnimationFrame(() => { + animationId = requestAnimationFrame(() => { + setIsAnimating(true); + }); + }); + } else { + setIsAnimating(false); + // 等待动画完成后隐藏组件 + timer = setTimeout(() => { + setIsVisible(false); + }, 300); + } + + return () => { + if (animationId) { + cancelAnimationFrame(animationId); + } + if (timer) { + clearTimeout(timer); + } + }; + }, [isOpen]); + + // 阻止背景滚动 + useEffect(() => { + if (isVisible) { + // 保存当前滚动位置 + const scrollY = window.scrollY; + const scrollX = window.scrollX; + const body = document.body; + const html = document.documentElement; + + // 获取滚动条宽度 + const scrollBarWidth = window.innerWidth - html.clientWidth; + + // 保存原始样式 + const originalBodyStyle = { + position: body.style.position, + top: body.style.top, + left: body.style.left, + right: body.style.right, + width: body.style.width, + paddingRight: body.style.paddingRight, + overflow: body.style.overflow, + }; + + // 设置body样式来阻止滚动,但保持原位置 + body.style.position = 'fixed'; + body.style.top = `-${scrollY}px`; + body.style.left = `-${scrollX}px`; + body.style.right = '0'; + body.style.width = '100%'; + body.style.overflow = 'hidden'; + body.style.paddingRight = `${scrollBarWidth}px`; + + return () => { + // 恢复所有原始样式 + body.style.position = originalBodyStyle.position; + body.style.top = originalBodyStyle.top; + body.style.left = originalBodyStyle.left; + body.style.right = originalBodyStyle.right; + body.style.width = originalBodyStyle.width; + body.style.paddingRight = originalBodyStyle.paddingRight; + body.style.overflow = originalBodyStyle.overflow; + + // 使用 requestAnimationFrame 确保样式恢复后再滚动 + requestAnimationFrame(() => { + window.scrollTo(scrollX, scrollY); + }); + }; + } + }, [isVisible]); // 加载配置 useEffect(() => { @@ -114,111 +197,192 @@ export default function DanmakuFilterSettings({ })); }; - if (!isOpen) return null; + if (!isVisible) return null; return ( -
-
+
{ + // 阻止最外层容器的触摸移动,防止背景滚动 + e.preventDefault(); + e.stopPropagation(); + }} + style={{ + touchAction: 'none', // 禁用所有触摸操作 + }} + > + {/* 背景遮罩 */} +
{ + // 只阻止滚动,允许其他触摸事件(包括点击) + e.preventDefault(); + }} + onWheel={(e) => { + // 阻止滚轮滚动 + e.preventDefault(); + }} + style={{ + backdropFilter: 'blur(4px)', + willChange: 'opacity', + touchAction: 'none', // 禁用所有触摸操作 + }} + /> + + {/* 弹窗主体 */} +
{ + // 允许弹窗内部滚动,阻止事件冒泡到外层 + e.stopPropagation(); + }} + style={{ + marginBottom: 'calc(0rem + env(safe-area-inset-bottom))', + willChange: 'transform, opacity', + backfaceVisibility: 'hidden', // 避免闪烁 + transform: isAnimating + ? 'translateY(0) translateZ(0)' + : 'translateY(100%) translateZ(0)', // 组合变换保持滑入效果和硬件加速 + opacity: isAnimating ? 1 : 0, + touchAction: 'auto', // 允许弹窗内的正常触摸操作 + }} + > + {/* 顶部拖拽指示器 */} +
+
+
+
+
+ {/* 头部 */} -
-

弹幕关键字屏蔽设置

+
+

+ 弹幕关键字屏蔽设置 +

{/* 内容区域 */} -
+
{/* 添加规则 */} -
-

添加屏蔽规则

-
+
+

+ 添加屏蔽规则 +

+
setNewKeyword(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && handleAddRule()} placeholder="输入要屏蔽的关键字" - className="flex-1 px-3 py-2 bg-gray-700 text-white rounded border border-gray-600 focus:border-teal-500 focus:outline-none" + className="w-full px-4 py-3 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg border border-gray-200 dark:border-gray-600 focus:border-teal-500 focus:outline-none focus:ring-2 focus:ring-teal-500/20 transition-all duration-200" /> - - +
+ + +
-

- * 普通模式:包含关键字即屏蔽 | 正则模式:支持正则表达式匹配 +

+ 💡 普通模式:包含关键字即屏蔽
+ 🔧 正则模式:支持正则表达式匹配

{/* 规则列表 */} -
-

- 当前规则 ({config.rules.length}) -

+
+
+

+ 当前规则 +

+ + {config.rules.length} + +
+ {loading ? ( -
加载中...
+
+
+
+ 加载中... +
+
) : config.rules.length === 0 ? ( -
- 暂无屏蔽规则,点击上方添加 +
+
+
+ +
+
+

暂无屏蔽规则

+

点击上方添加关键字

+
+
) : (
{config.rules.map((rule) => (
{/* 启用/禁用按钮 */} {/* 关键字 */}
-
+
{rule.keyword} - {rule.type === 'regex' ? '正则' : '普通'} + {rule.type === 'regex' ? '🔧 正则' : '💬 普通'}
@@ -226,7 +390,7 @@ export default function DanmakuFilterSettings({ {/* 删除按钮 */} @@ -238,20 +402,29 @@ export default function DanmakuFilterSettings({
{/* 底部按钮 */} -
- - +
+
+ + +