如何在React Native中实现无卡顿的超大Skia Canvas滚动?
解决React Native Skia超大可滚动图表的崩溃与卡顿问题
核心问题分析
- 直接给Canvas设置超大宽度会触发OpenGL纹理尺寸限制,导致应用崩溃
- 用React状态
useState同步滚动偏移会频繁触发组件重渲染,引发滚动卡顿
优化方案
1. 用Reanimated驱动滚动偏移,避免React重渲染
放弃useState存储滚动偏移,改用Reanimated的useSharedValue,结合Animated.ScrollView实现原生级别的偏移同步,完全绕开React的重渲染流程:
import { Animated } from 'react-native'; import { useSharedValue } from 'react-native-reanimated'; import { Canvas, Group } from '@shopify/react-native-skia'; const scrollOffset = useSharedValue(0); // 直接在ScrollView事件中更新共享值,无需JS线程介入 const handleScroll = Animated.event( [{ nativeEvent: { contentOffset: { x: scrollOffset } } }], { useNativeDriver: true } ); // Canvas中直接使用共享值做变换 return ( <> <Canvas style={{ width: 800, height: 200 }}> <Group transform={[{ translateX: scrollOffset.value * -1 }]}> <GetCircles /> </Group> </Canvas> <Animated.ScrollView horizontal style={{ height: 100 }} onScroll={handleScroll} scrollEventThrottle={16} // 控制事件触发频率,平衡流畅度与性能 contentContainerStyle={{ width: 1200 }} // 直接设置总滚动宽度,替代多个View /> </> );
2. 只渲染可视区域内的图形
不管怎么平移,渲染全部圆形都会造成Skia绘制压力。根据当前滚动偏移和画布宽度,计算出可视区域的范围,只渲染该范围内的圆形:
// 假设每个圆形宽度(含间距)是150 const ITEM_WIDTH = 150; const CANVAS_WIDTH = 800; // 计算可视区域的起始和结束索引 const startIndex = Math.floor(scrollOffset.value / ITEM_WIDTH); const endIndex = Math.ceil((scrollOffset.value + CANVAS_WIDTH) / ITEM_WIDTH) + 1; // 多渲染1个避免边缘空白 // 在GetCircles中只渲染[startIndex, endIndex]范围内的圆形 function GetCircles() { return ( <> {data.slice(startIndex, endIndex).map((item, idx) => { const x = (startIndex + idx) * ITEM_WIDTH + ITEM_WIDTH / 2; return <Circle cx={x} cy={100} r={40} color={item.color} />; })} </> ); }
3. 额外性能优化建议
- 禁用ScrollView的过度滚动(
bounces={false}),减少不必要的动画计算 - 对于静态图形,使用Skia的
Picture组件预渲染,复用绘制结果:import { Picture } from '@shopify/react-native-skia'; // 预渲染单个圆形的Picture const circlePicture = useMemo(() => { const recorder = Picture.Recorder(); const canvas = recorder.beginRecording(ITEM_WIDTH, 200); canvas.drawCircle(ITEM_WIDTH/2, 100, 40, paint); return recorder.finishRecording(); }, []); // 在GetCircles中复用Picture <Picture picture={circlePicture} x={x - ITEM_WIDTH/2} y={0} />
最终效果
通过以上方案,既避免了超大Canvas的纹理崩溃问题,又通过原生驱动的偏移同步+可视区域渲染,彻底解决滚动卡顿,实现流畅的超大图表滚动体验。
内容的提问来源于stack exchange,提问作者Victor Chelaru
相关产品推荐
相关产品推荐

