React Native Skia结合Gesture Handler实现可缩放平移画布时,捏合缩放焦点异常问题求助
React Native Skia结合Gesture Handler实现可缩放平移画布时,捏合缩放焦点异常问题求助
嘿,我帮你分析下问题所在,你现在的代码里有两个关键问题导致缩放总是围绕(0,0)而不是捏合焦点:
问题根源
- 焦点坐标没有转换为画布本地坐标:你直接用了手势返回的
focalX/focalY(这是屏幕坐标),但缩放需要围绕的是画布上对应屏幕焦点的那个点,而不是屏幕坐标本身。 - 变换顺序错误:矩阵变换的顺序非常关键,你当前的变换步骤把全局平移放在了最前面,导致缩放的原点被全局平移干扰了。
修复方案
我直接给你修改后的完整代码,同时解释每一步的调整:
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> ); }
关键调整说明
- 新增画布本地焦点变量:用
focalCanvasX/focalCanvasY保存转换后的画布本地坐标,而不是直接用屏幕坐标。 - 坐标转换公式:
(屏幕焦点坐标 - 当前平移量) / 当前缩放比例,这个公式能帮你找到屏幕焦点对应的画布上的原始位置。 - 修正变换顺序:
- 先把画布焦点平移到原点
- 执行缩放
- 再平移回焦点位置
- 最后应用全局的拖动平移
这样组合出来的矩阵,能保证缩放时屏幕焦点对应的画布内容始终停留在原屏幕位置,实现类似Figma/谷歌地图的缩放体验。
额外小提示
- 可以给缩放加个最小/最大限制,比如
scale.value = Math.max(0.5, Math.min(5, event.scale * savedScale.value)),避免缩放过度。 - 如果你的
processTransform3d是自己实现的,要确保它是按数组顺序从后往前组合矩阵(因为矩阵乘法是右结合的),如果它是从前往后组合的,你需要把变换数组的顺序反过来。
内容来源于stack exchange
相关产品推荐
相关产品推荐

