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

如何用React Native Reanimated或Skia实现图形沿路径带动画移动?

React Native Skia实现红球沿路径弹簧动画移动的正确方法

如何让红球沿黑色路径移动,并应用React Native Reanimated的withSpring等动画效果?

我尝试用React Native Skia编写如下代码:

const progress = useSharedValue(0);
const geo = new PathGeometry(path);
const totalLength = geo.getTotalLength();
const transform = useDerivedValue(() => {
const currentLen = interpolate(progress.value, [0, 1], [0, totalLength]);
const pos = geo.getPointAtLength(currentLen);
return translate({x:pos.x,y:pos.y});
});
const handlePress = () => {
progress.value = withSpring(1);
};

但收到错误提示:

ReanimatedError: [Reanimated] Trying to access property getPointAtLength of an object which cannot be sent to the UI runtime. js engine: reanimated

之后我改用如下代码实现:

const geo = new PathGeometry(path);
const totalLength = geo.getTotalLength();
const pos = geo.getPointAtLength(0);
const posX = useSharedValue(pos.x);
const posY = useSharedValue(pos.y);
let i = 0;
setInterval(() => {
const nPos = geo.getPointAtLength(i);
posX.value = nPos.x;
posY.value = nPos.y;
i++;
}, 16);

该代码虽能实现移动但效果生硬,且无法应用withSpring这类动画效果。


问题原因

第一个方案报错是因为PathGeometry属于JS线程对象,无法直接在Reanimated的UI线程(useDerivedValue运行环境)中调用其方法。第二个方案用setInterval逐帧更新,本质是手动模拟动画帧,无法和Reanimated的弹簧动画逻辑结合,所以效果生硬。

正确实现思路

要在Reanimated的UI线程中使用路径信息,需使用Reanimated兼容的PathMeasure工具类,它支持在UI线程中直接获取路径长度、指定位置坐标等信息,完美适配withSpring这类动画API。

完整代码示例

import React from 'react';
import { View, TouchableOpacity, Text } from 'react-native';
import { Canvas, Path, Circle } from '@shopify/react-native-skia';
import { useSharedValue, useDerivedValue, withSpring, interpolate, translate } from 'react-native-reanimated';

const PathAnimation = () => {
  // 替换为你自己的目标路径
  const path = `M 50 50 C 150 50, 150 150, 250 150 C 350 150, 350 50, 450 50`;
  
  // 控制动画进度的共享值(0表示起点,1表示终点)
  const progress = useSharedValue(0);

  // 在UI线程中初始化PathMeasure,绑定目标路径
  const pathMeasure = useDerivedValue(() => {
    const measure = new PathMeasure();
    measure.setPath(path);
    return measure;
  }, [path]);

  // 根据进度计算红球的位置变换
  const transform = useDerivedValue(() => {
    const measure = pathMeasure.value;
    const totalLength = measure.getLength();
    // 插值计算当前进度对应的路径长度
    const currentLength = interpolate(progress.value, [0, 1], [0, totalLength]);
    // 获取当前长度对应的坐标点
    const point = measure.getPosTan(currentLength);
    // 平移红球到目标点,减去半径让球心对准路径点
    return translate({ x: point.x - 10, y: point.y - 10 });
  }, [pathMeasure]);

  // 触发弹簧动画,点击后在起点/终点间切换
  const handlePress = () => {
    progress.value = withSpring(progress.value === 1 ? 0 : 1, {
      damping: 10,
      stiffness: 100,
    });
  };

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <TouchableOpacity 
        onPress={handlePress} 
        style={{ marginBottom: 20, padding: 12, backgroundColor: '#2196F3', borderRadius: 6 }}
      >
        <Text style={{ color: 'white', fontSize: 16 }}>触发动画</Text>
      </TouchableOpacity>
      <Canvas style={{ width: 500, height: 200 }}>
        {/* 绘制黑色路径 */}
        <Path path={path} strokeWidth={2} color="black" />
        {/* 绘制红球并应用位置变换 */}
        <Circle cx={0} cy={0} r={10} color="red" transform={transform} />
      </Canvas>
    </View>
  );
};

export default PathAnimation;

关键说明

  • PathMeasure适配性:它是Reanimated官方支持的工具类,可直接在UI线程的useDerivedValue中使用,避免跨线程访问错误。
  • getPosTan方法:该方法能获取路径上指定长度位置的坐标点和切线方向,这里仅用坐标点实现平移。
  • 弹簧动画结合:通过progress共享值控制整体动画进度,直接用withSpring更新progress,就能让红球沿路径自然呈现弹簧运动效果。
  • 坐标修正:将红球的圆心设为(0,0),通过translate移动到路径点时减去球的半径,确保球心与路径点重合,避免视觉偏移。

内容的提问来源于stack exchange,提问作者YiSong

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 04:41:17