fix(editing-planner): 修复添加片段面板被裁剪 — 默认向上弹出+useLayoutEffect精确校正
- 面板默认从+按钮上方弹出,上方不够时翻转到下方 - 用useLayoutEffect+实际offsetHeight替代硬编码PICKER_H - fixed定位脱离父容器overflow裁剪 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,13 @@
|
||||
* 时间标尺(20px) + 水平片段卡片轨道(100x100) + HTML5拖拽排序
|
||||
* "+" 卡片 → 类型+时长选择器
|
||||
*/
|
||||
import React, { useState, useRef, useCallback, useEffect } from "react";
|
||||
import React, {
|
||||
useState,
|
||||
useRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
} from "react";
|
||||
import type { ClipData, ClipType } from "../types";
|
||||
|
||||
interface TimelinePanelProps {
|
||||
@@ -60,32 +66,24 @@ const TimelinePanel: React.FC<TimelinePanelProps> = ({
|
||||
? ["pip"]
|
||||
: ["voice", "pip"]; // voice_pip 或默认
|
||||
|
||||
/* ── 面板尺寸估算(用于边界检测) ── */
|
||||
const PICKER_H = 200; // 面板预估高度(padding + 标题 + 类型行 + 时长行 + 按钮 + 间距)
|
||||
/* ── 面板尺寸(宽度固定,高度由 useLayoutEffect 实测) ── */
|
||||
const PICKER_W = 240; // 面板宽度(与 CSS 一致)
|
||||
const GAP = 6; // 面板与"+"卡片的间距
|
||||
|
||||
/* ── 计算 picker 弹出位置(fixed 定位 + 视口边界检测) ── */
|
||||
/* ── 计算 picker 初始位置(默认从"+"按钮上方弹出) ── */
|
||||
const updatePickerPosition = useCallback(() => {
|
||||
if (!addCardRef.current) return;
|
||||
const rect = addCardRef.current.getBoundingClientRect();
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight;
|
||||
|
||||
/* 垂直方向:优先向下弹出;空间不足则向上翻转 */
|
||||
let top: number;
|
||||
if (rect.bottom + GAP + PICKER_H > vh) {
|
||||
// 下方空间不够 → 向上弹出
|
||||
top = rect.top - GAP - PICKER_H;
|
||||
if (top < 8) top = 8; // 极端情况: clamp 到视口顶部
|
||||
} else {
|
||||
top = rect.bottom + GAP;
|
||||
}
|
||||
/* 垂直方向:默认向上弹出(上方空间永远比下方大) */
|
||||
const roughHeight = 180; // 粗略估算,useLayoutEffect 会用实际高度校正
|
||||
let top = rect.top - GAP - roughHeight;
|
||||
if (top < 8) top = 8;
|
||||
|
||||
/* 水平方向:右对齐"+"卡片;太靠右超出视口则左移 */
|
||||
let right = vw - rect.right;
|
||||
if (rect.right - PICKER_W < 8) {
|
||||
// 左边界保护
|
||||
right = vw - PICKER_W - 8;
|
||||
}
|
||||
|
||||
@@ -99,36 +97,36 @@ const TimelinePanel: React.FC<TimelinePanelProps> = ({
|
||||
setShowAddPicker((v) => !v);
|
||||
};
|
||||
|
||||
/* ── 渲染后用实际面板高度做精确边界校正 ── */
|
||||
useEffect(() => {
|
||||
if (!showAddPicker || !pickerRef.current) return;
|
||||
const pickerRect = pickerRef.current.getBoundingClientRect();
|
||||
/* ── 渲染后用实际 offsetHeight 做精确边界校正(useLayoutEffect 确保 paint 前完成) ── */
|
||||
useLayoutEffect(() => {
|
||||
if (!showAddPicker || !pickerRef.current || !addCardRef.current) return;
|
||||
const pickerEl = pickerRef.current;
|
||||
const addRect = addCardRef.current.getBoundingClientRect();
|
||||
const pickerH = pickerEl.offsetHeight; // 实际高度,不用硬编码
|
||||
const vh = window.innerHeight;
|
||||
const vw = window.innerWidth;
|
||||
|
||||
let adjustTop: number | null = null;
|
||||
let adjustRight: number | null = null;
|
||||
/* 默认:面板在"+"按钮上方 */
|
||||
let top = addRect.top - GAP - pickerH;
|
||||
|
||||
// 底部溢出 → 上移
|
||||
if (pickerRect.bottom > vh - 8) {
|
||||
adjustTop = vh - 8 - pickerRect.height;
|
||||
if (adjustTop < 8) adjustTop = 8;
|
||||
/* 上方空间也不够(极端情况)→ 翻转到下方 */
|
||||
if (top < 8) {
|
||||
top = addRect.bottom + GAP;
|
||||
/* 下方也溢出 → clamp */
|
||||
if (top + pickerH > vh - 8) {
|
||||
top = vh - 8 - pickerH;
|
||||
if (top < 8) top = 8;
|
||||
}
|
||||
}
|
||||
// 顶部溢出 → 下移
|
||||
if (pickerRect.top < 8) {
|
||||
adjustTop = 8;
|
||||
}
|
||||
// 左侧溢出 → 左移(right 增大)
|
||||
|
||||
/* 水平方向:右对齐"+"卡片;左侧溢出保护 */
|
||||
let right = vw - addRect.right;
|
||||
const pickerRect = pickerEl.getBoundingClientRect();
|
||||
if (pickerRect.left < 8) {
|
||||
adjustRight = vw - PICKER_W - 8;
|
||||
right = vw - PICKER_W - 8;
|
||||
}
|
||||
|
||||
if (adjustTop !== null || adjustRight !== null) {
|
||||
setPickerPos((prev) => ({
|
||||
top: adjustTop ?? prev.top,
|
||||
right: adjustRight ?? prev.right,
|
||||
}));
|
||||
}
|
||||
setPickerPos({ top, right });
|
||||
}, [showAddPicker]);
|
||||
|
||||
/* ── 点击外部关闭添加面板 ── */
|
||||
|
||||
Reference in New Issue
Block a user