JapaneseText3 组件笔记定位优化
问题描述
原有的 JapaneseText3 组件在显示笔记时存在位置不稳定的问题,主要表现为:
- 笔记面板有时会出现在屏幕边界外
- 位置计算不够精确,导致频繁重新定位
- 移动端适配不够完善
- 缺乏有效的防抖机制
- 点击笔记时页面会意外滚动到顶部或其他位置
- 组件出现无限重渲染,导致性能问题和日志刷屏 ⭐ 新发现
优化方案
1. 修复无限重渲染问题 🚨 紧急修复
问题原因:
useAccessibilityManager
的options
对象每次渲染都重新创建useEffect
依赖数组包含不稳定的对象引用- 性能监控的
useEffect
没有正确的依赖设置 - 回调函数没有使用
useCallback
进行稳定化
错误表现:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
解决方案:
// 1. 稳定化 accessibility options
const accessibilityOptions = useMemo(() => ({
announceChanges: true,
keyboardNavigation: preferences.interaction?.keyboardShortcuts !== false,
screenReaderOptimized: preferences.accessibility?.screenReaderOptimized || false
}), [
preferences.interaction?.keyboardShortcuts,
preferences.accessibility?.screenReaderOptimized
]);
// 2. 在 useAccessibilityManager 中使用 useMemo
const memoizedOptions = useMemo(() => ({
announceChanges: options.announceChanges ?? true,
keyboardNavigation: options.keyboardNavigation ?? true,
screenReaderOptimized: options.screenReaderOptimized ?? false
}), [
options.announceChanges,
options.keyboardNavigation,
options.screenReaderOptimized
]);
// 3. 稳定化回调函数
const handleShortcutChange = useCallback((section, field, value) => {
updatePreference(section, field, value);
if (onPreferenceChange) {
onPreferenceChange({ [section]: { [field]: value } });
}
}, [updatePreference, onPreferenceChange]);
// 4. 优化性能监控
useLayoutEffect(() => {
// 性能监控逻辑,不需要依赖数组
}, []); // 空依赖数组或无依赖数组
1. 修复位置校正循环问题 🚨 关键修复
问题表现:
performance.js:186 JapaneseText2: Very slow render detected (72.10ms)
useNoteHandling.js:548 Note panel out of bounds detected: {bounds: {…}, panelRect: DOMRect, currentPosition: {…}}
useNoteHandling.js:256 Position validation: {panelRect: {…}, viewport: {…}, bounds: {…}, currentPosition: {…}}
useNoteHandling.js:319 Applying position correction: {from: {…}, to: {…}, reason: 'boundary violation'}
循环原因:
NotePanel
检测到边界超出 → 触发notePanelOutOfBounds
事件useNoteHandling
收到事件 → 调用validateAndCorrectPosition
- 位置校正 → 触发重渲染 →
NotePanel
重新验证位置 - 无限循环...
解决方案:
// 1. 添加循环检测和防护
const positionCorrectionCount = useRef(0);
const lastCorrectionTime = useRef(0);
const isValidatingPosition = useRef(false);
const validateAndCorrectPosition = useCallback((initialPosition) => {
// 防止并发验证
if (isValidatingPosition.current) return;
// 检测校正循环
const now = Date.now();
if (now - lastCorrectionTime.current < 1000) {
positionCorrectionCount.current++;
if (positionCorrectionCount.current > 3) {
console.warn('Position correction loop detected, stopping validation');
return;
}
} else {
positionCorrectionCount.current = 0;
}
// 只在显著超出边界时才校正 (>10px)
if (isOutOfBoundsRight && panelRect.right > viewportWidth - 10) {
// 应用校正...
}
// 只在变化显著时才更新位置 (>5px)
if (deltaX > 5 || deltaY > 5) {
setNotePosition(newPosition);
}
}, []);
// 2. 减少验证频率
// 从 [16, 100, 250]ms 减少到单次 100ms 验证
setTimeout(() => validateAndCorrectPosition(position), 100);
// 3. 优化边界检测敏感度
const threshold = 10; // 只在真正超出边界时触发
const isOutOfBounds = {
right: rect.right > viewportWidth - threshold,
// ...
};
2. 修 复滚动后定位失效问题
问题原因: 滚动后,缓存的位置信息过时,导致 note 定位错误。
解决方案:
const handleNoteClick = useCallback((noteKey, noteContent, event, sentenceIndex) => {
// 总是基于当前元素位置重新计算
const position = calculateNotePosition(event.target);
setNotePosition(position);
// 简化验证流程
setTimeout(() => validateAndCorrectPosition(position), 100);
}, [calculateNotePosition, validateAndCorrectPosition]);
2. 修复意外滚动问题 ⭐ 已修复
问题原因:
navigateNote
函数中的scrollIntoView
调用在所有场景下都会执行- 未区分键盘导航和鼠标点击的行为差异
- 位置计算可能触发 DOM 变化导致滚动跳跃
解决方案:
// 区分导航来源,避免点击时不必要的滚动
const navigateNote = useCallback((direction, shouldScroll = true) => {
// ... navigation logic
// 只在明确需要时滚动(键盘导航)
if (shouldScroll && autoScrollOnNavigation) {
// 检查元素是否已可见
const isVisible = rect.top >= 0 && rect.bottom <= window.innerHeight;
// 只有在元素不可见时才滚动
if (!isVisible) {
noteElement.scrollIntoView({
behavior: scrollBehavior,
block: 'nearest', // 使用保守的滚动策略
inline: 'nearest'
});
}
}
}, [/* deps */]);
// 点击事件中防止意外滚动
const handleNoteClick = useCallback((noteKey, noteContent, event, sentenceIndex) => {
// 记录当前滚动位置
const currentScrollX = window.scrollX;
const currentScrollY = window.scrollY;
// ... note handling logic
// 防护:确保滚动位置不变
requestAnimationFrame(() => {
if (window.scrollX !== currentScrollX || window.scrollY !== currentScrollY) {
window.scrollTo(currentScrollX, currentScrollY);
}
});
}, [/* deps */]);
新增配置选项:
interaction: {
autoScrollOnNavigation: true, // 键盘导航时自动滚动
smoothScrolling: true // 使用平滑滚动
}
2. 改进定位算法 (useNoteHandling.js
)
主要改进:
- 使用配置常量统一管理定位参数
- 增强边界检测逻辑,提高定位精度
- 添加阈值机制,优化定位策略选择
- 改进移动端定位,支持设备安全区域
核心变更:
// 使用常量配置
const noteWidth = NOTE_POSITIONING.DESKTOP_WIDTH;
const noteHeight = NOTE_POSITIONING.DESKTOP_HEIGHT;
const safeMargin = NOTE_POSITIONING.SAFE_MARGIN;
const targetOffset = NOTE_POSITIONING.TARGET_OFFSET;
// 阈值机制
const spaceThreshold = NOTE_POSITIONING.SPACE_THRESHOLD;
if (spaceRight >= (noteWidth + targetOffset) * spaceThreshold) {
// 优先右侧定位
}