You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 10:20:02