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

React Native Skia结合Gesture Handler实现可缩放平移画布时,捏合缩放焦点异常问题求助

React Native Skia结合Gesture Handler实现可缩放平移画布时,捏合缩放焦点异常问题求助

嘿,我帮你分析下问题所在,你现在的代码里有两个关键问题导致缩放总是围绕(0,0)而不是捏合焦点:

问题根源

  1. 焦点坐标没有转换为画布本地坐标:你直接用了手势返回的focalX/focalY(这是屏幕坐标),但缩放需要围绕的是画布上对应屏幕焦点的那个点,而不是屏幕坐标本身。
  2. 变换顺序错误:矩阵变换的顺序非常关键,你当前的变换步骤把全局平移放在了最前面,导致缩放的原点被全局平移干扰了。

修复方案

我直接给你修改后的完整代码,同时解释每一步的调整:

import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { Canvas, Group, useDerivedValue, useSharedValue } from '@shopify/react-native-skia';
// 保留你原有的processTransform3d依赖
import { processTransform3d } from '你的依赖路径';

function MyCanvas({
  imageView,
  canvasWidth,
  canvasHeight,
}: {
  imageView: Element[];
  canvasWidth: number;
  canvasHeight: number;
}) {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const scale = useSharedValue(1);
  const savedScale = useSharedValue(1);
  // 新增:保存画布本地的焦点坐标(对应屏幕捏合点的画布位置)
  const focalCanvasX = useSharedValue(0);
  const focalCanvasY = useSharedValue(0);
  const context = useSharedValue({ x: 0, y: 0 });

  const panGesture = Gesture.Pan()
    .onStart(() => {
      context.value = { x: translateX.value, y: translateY.value };
    })
    .onUpdate((event) => {
      translateX.value = event.translationX + context.value.x;
      translateY.value = event.translationY + context.value.y;
    });

  const pinchGesture = Gesture.Pinch()
    .onUpdate((event) => {
      // 关键:把屏幕焦点转换为画布本地坐标
      // 公式:画布本地坐标 = (屏幕坐标 - 当前平移) / 当前缩放比例
      const currentScale = savedScale.value;
      focalCanvasX.value = (event.focalX - translateX.value) / currentScale;
      focalCanvasY.value = (event.focalY - translateY.value) / currentScale;
      
      scale.value = event.scale * currentScale;
    })
    .onEnd(() => {
      savedScale.value = scale.value;
    });

  const computedMatrix = useDerivedValue(() => {
    const ox = focalCanvasX.value;
    const oy = focalCanvasY.value;
    
    // 修正变换顺序:先围绕画布本地焦点缩放,再应用全局平移
    return processTransform3d([
      // 4. 最后应用全局平移(拖动的位移)
      { translateX: translateX.value },
      { translateY: translateY.value },
      // 3. 平移回画布焦点位置
      { translateX: ox },
      { translateY: oy },
      // 2. 应用缩放
      { scale: scale.value },
      // 1. 先把画布焦点平移到原点
      { translateX: -ox },
      { translateY: -oy },
    ]);
  }, [scale, translateX, translateY, focalCanvasX, focalCanvasY]);

  const composedGesture = Gesture.Simultaneous(panGesture, pinchGesture);

  return (
    <GestureDetector gesture={composedGesture}>
      <View className="bg-gray-950 flex-1 overflow-visible">
        <Canvas style={{ flex: 1 }}>
          <Group matrix={computedMatrix}>{imageView}</Group>
        </Canvas>
      </View>
    </GestureDetector>
  );
}

关键调整说明

  1. 新增画布本地焦点变量:用focalCanvasX/focalCanvasY保存转换后的画布本地坐标,而不是直接用屏幕坐标。
  2. 坐标转换公式:(屏幕焦点坐标 - 当前平移量) / 当前缩放比例,这个公式能帮你找到屏幕焦点对应的画布上的原始位置。
  3. 修正变换顺序:
    • 先把画布焦点平移到原点
    • 执行缩放
    • 再平移回焦点位置
    • 最后应用全局的拖动平移
      这样组合出来的矩阵,能保证缩放时屏幕焦点对应的画布内容始终停留在原屏幕位置,实现类似Figma/谷歌地图的缩放体验。

额外小提示

  • 可以给缩放加个最小/最大限制,比如scale.value = Math.max(0.5, Math.min(5, event.scale * savedScale.value)),避免缩放过度。
  • 如果你的processTransform3d是自己实现的,要确保它是按数组顺序从后往前组合矩阵(因为矩阵乘法是右结合的),如果它是从前往后组合的,你需要把变换数组的顺序反过来。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:13:06