宽度感知React组件侧边栏切换时出现渲染跳动问题求助
解决侧边栏切换时React组件布局跳动问题
问题场景
我通过ResizeObserver实现了一个可感知自身宽度的React组件,内部两个子div均设置flex: 1以平分容器宽度。当组件宽度小于700px且处于编辑状态(isEditing为true)时,会隐藏dashboard_monitorsdiv,让dashboard_activitydiv占据全部宽度。
但应用包含可开合侧边栏,关闭侧边栏后组件宽度恢复,本应同步显示dashboard_monitors,实际却会先绘制一次(此时dashboard_activity仍占满宽度),之后组件才更新状态显示dashboard_monitors,导致页面出现明显跳动。
核心原因
ResizeObserver的回调是异步触发的,侧边栏切换导致组件宽度变化后,ResizeObserver不会立即更新dashboardWidth状态,React的渲染周期滞后于DOM宽度变化,从而出现两次绘制的跳动现象。
解决方案
1. 用CSS过渡优化布局变化
将display: none替换为visibility: hidden配合flex属性控制,让元素在隐藏时仍保留布局空间,同时添加过渡动画让变化平滑:
.dashboard { display: flex; height: 100%; transition: all 0.2s ease; } .dashboard.single .dashboard_monitors { visibility: hidden; flex: 0 0 0; opacity: 0; pointer-events: none; } .dashboard.single .dashboard_activity { margin-left: 0; flex: 1 1 100%; } .dashboard > * { flex: 1; overflow-x: hidden; overflow-y: auto; transition: flex 0.2s ease, opacity 0.2s ease, visibility 0.2s ease; } .dashboard_activity { margin-left: var(--padding-2); } .dashboard_monitors { display: grid; gap: var(--padding-2); grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); grid-auto-rows: 200px; padding-right: var(--padding-0); padding-bottom: var(--padding-0); }
2. 优化useWidth Hook的更新时机
让Hook初始化时主动获取宽度,并用requestAnimationFrame确保状态更新与浏览器重绘同步,同时添加主动刷新方法:
import { useState, useRef, useEffect } from "react"; export const useWidth = <T extends HTMLElement>() => { const [width, setWidth] = useState(0); const ref = useRef<T>(null); const observerRef = useRef<ResizeObserver | null>(null); useEffect(() => { const updateWidth = (entries: ResizeObserverEntry[]) => { requestAnimationFrame(() => { setWidth(entries[0].contentRect.width); }); }; observerRef.current = new ResizeObserver(updateWidth); const refElement = ref.current; if (refElement) { observerRef.current.observe(refElement); setWidth(refElement.getBoundingClientRect().width); } return () => { if (observerRef.current && refElement) { observerRef.current.unobserve(refElement); } }; }, []); const refreshWidth = () => { if (ref.current) { setWidth(ref.current.getBoundingClientRect().width); } }; return { width, ref, refreshWidth }; };
3. 侧边栏切换时主动同步宽度状态
在Dashboard组件中监听侧边栏状态变化,主动调用refreshWidth确保状态与DOM宽度同步:
import './Dashboard.css' import MonitorComponent from '../../components/Monitor/Monitor'; import DetailComponent from '../../components/Monitor/Detail'; import { Monitor } from '../../monitor' import { useEffect, useMemo, useState } from 'react'; import { useWidth } from '../../hooks/useWidth'; interface DashboardProps { monitors: Monitor[] focused: Monitor | null, drafting: boolean, sidebarOpen?: boolean; } export default function Dashboard(props: DashboardProps) { const { monitors, focused, drafting, sidebarOpen, } = props; const isEditing = useMemo(() => focused || drafting, [focused, drafting]); const { width: dashboardWidth, ref: dashboardRef, refreshWidth } = useWidth<HTMLDivElement>(); const [sorted, setSorted] = useState<Monitor[]>([]) useEffect(() => { const sorted = monitors.sort((a, b) => { if (a.strategy.name > b.strategy.name) { return 1; } else { return -1; } }); setSorted(sorted); }, [monitors]) useEffect(() => { refreshWidth(); }, [sidebarOpen, refreshWidth]) const dashboardClasses = ['dashboard', isEditing && dashboardWidth < 700 ? 'single' : ''] return ( <div className={dashboardClasses.join(' ')} ref={dashboardRef}> <div className="dashboard_monitors"> {sorted.map((n, i) => <MonitorComponent key={i} monitor={n} />) } </div> {isEditing ? <div className="dashboard_activity"> <DetailComponent monitor={focused} /> </div> : null} </div> ) }
效果说明
- CSS过渡让布局变化平滑,消除生硬跳动;
- 主动刷新宽度确保侧边栏切换时状态与DOM同步,避免滞后渲染;
- 保留布局空间的隐藏方式减少了重排次数,提升页面稳定性。
内容的提问来源于stack exchange,提问作者user12822512
相关产品推荐
相关产品推荐

