解决Lightweight Charts滚动加载新数据后返回起始位置问题
解决图表加载新数据后滚动位置重置的问题
问题说明
使用subscribeVisibleLogicalRangeChange()监听图表可视范围,能成功请求新数据,但设置新数据后图表会重新渲染并回到起始位置。需求是用户滚动时请求并追加新数据,同时保持当前滚动位置不变。
核心原因
- 数据覆盖而非追加:原
fetchData直接用新数据替换旧数据,触发图表全量重绘 - 图表实例重复创建:依赖
symbolData的useEffect在数据更新时会重新创建整个图表,导致滚动状态丢失 - 无滚动位置保存逻辑:加载新数据前未记录当前滚动位置,无法在数据更新后恢复
解决方案
- 分离图表初始化与数据更新:图表实例仅在组件挂载时创建一次,避免重复初始化
- 追加数据而非覆盖:更新状态时保留原有数据,只添加新获取的内容
- 记录并恢复滚动位置:加载新数据前获取当前滚动位置,数据更新后恢复该位置
- 添加加载防抖:用ref标记加载状态,防止重复触发数据请求
修改后的完整代码
1. 优化数据请求函数
// 用于标记是否正在加载,防止重复请求 const isLoading = useRef(false); // 保存图表和系列实例的ref const chartRef = useRef<IChartApi | null>(null); const candleSeriesRef = useRef<ISeriesApi<'Candlestick'> | null>(null); const fetchData = async (record: number) => { if (isLoading.current) return; isLoading.current = true; try { const response = await getSymbolTimeSeries( "AAPL", selectedInterval, record, ); const { values } = response; const newSeriesData = values.map((value: SymbolDataType) => { return createData( value.datetime, Number(value.open), Number(value.close), Number(value.high), Number(value.low), ); }).sort((a: ChartDataType, b: ChartDataType) => a.time - b.time); // 追加新数据,而非覆盖原有数据 setSymbolData(prev => [...prev, ...newSeriesData]); } finally { isLoading.current = false; } };
2. 分离图表初始化与数据更新逻辑
// 仅在组件挂载和选中时间间隔变化时初始化图表 useEffect(() => { const width = tradeRef.current?.clientWidth; const height = tradeRef.current?.clientHeight; if (!tradeRef.current) return; const chart = createChart(tradeRef.current, { width, height, layout: { background: { color: "#000000" }, textColor: "rgba(255, 255, 255, 0.9)", }, grid: { vertLines: { color: "rgba(197, 203, 206, 0.5)" }, horzLines: { color: "rgba(197, 203, 206, 0.5)" }, }, crosshair: { mode: CrosshairMode.Normal }, rightPriceScale: { borderColor: "rgba(197, 203, 206, 0.8)" }, timeScale: { borderColor: "rgba(197, 203, 206, 0.8)" }, }); const candleSeries = chart.addCandlestickSeries({ upColor: "rgb(255, 24, 98)", downColor: "#000", borderDownColor: "rgb(60, 179, 113)", borderUpColor: "rgb(255, 24, 98)", wickDownColor: "rgb(255, 24, 98)", wickUpColor: "rgb(60, 179, 113)", }); chartRef.current = chart; candleSeriesRef.current = candleSeries; // 初始加载数据 fetchData(300).catch(console.error); const handleResize = () => { chart.applyOptions({ width: tradeRef.current?.clientWidth, height: tradeRef.current?.clientHeight, }); }; async function onVisibleLogicalRangeChanged( newVisibleLogicalRange: { from: number } | null, ) { if ( newVisibleLogicalRange !== null && newVisibleLogicalRange.from < -106 && !isLoading.current ) { // 记录当前滚动位置 const currentPosition = chart.timeScale().scrollPosition(); // 请求更多数据 await fetchData(symbolData.length + 10); // 恢复滚动位置(false表示不带动画) chart.timeScale().scrollToPosition(currentPosition, false); } } chart.timeScale().subscribeVisibleLogicalRangeChange(onVisibleLogicalRangeChanged); window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); chart.timeScale().unsubscribeVisibleLogicalRangeChange(onVisibleLogicalRangeChanged); chart.remove(); }; }, [selectedInterval]); // 仅在数据更新时更新图表系列数据 useEffect(() => { if (candleSeriesRef.current && symbolData.length > 0) { candleSeriesRef.current.setData(symbolData); } }, [symbolData]);
3. 组件结构(保持不变)
<Box ref={tradeRef} sx={{ width: "calc(100vw - 550px)", height: "90%", mt: 1.5, }} />
内容的提问来源于stack exchange,提问作者Omar mero
相关产品推荐
相关产品推荐

