React Native Swiper缩放第二张图时显示首张图问题修复咨询
React Native图片轮播缩放时显示其他图片的修复方案
使用React Native 0.70.1、react-native-gesture-handler 2.8和react-native-reanimated 3.0实现了支持左右滑动切换、捏合缩放的图片轮播组件,但遇到问题:捏合缩放第二张图片时,画面会显示第一张图片的部分内容。
原组件代码如下:
import React, { useEffect, useMemo} from "react"; import { Dimensions, StyleSheet, View, Platform, TouchableWithoutFeedback } from "react-native"; import CachedImage from '../app/CachedImage'; import Animated, { useAnimatedStyle, useSharedValue, useAnimatedRef, withTiming, withSpring, useDerivedValue, runOnJS, runOnUI } from "react-native-reanimated"; import { snapPoint, useVector, } from "react-native-redash"; import { Gesture, GestureDetector, GestureHandlerRootView, } from "react-native-gesture-handler"; const { width, height } = Dimensions.get("window"); export default Swipe = ({route, navigation}) => { const {images, initIndex} = route.params; console.log("route params screen dim swipe ", `${width}, ${height}`); const index = useSharedValue(initIndex); const snapPoints = images.map((_, i) => i * -width); const offsetX = useSharedValue(initIndex * -width); const translationX = useSharedValue(0); const translateX = useSharedValue(0); //pinch const scale = useSharedValue(1); const focalX = useSharedValue(0); const focalY = useSharedValue(0); //for snap in swipe const lowVal = useDerivedValue(() => {return (index.value + 1) * -width}); const highVal = useDerivedValue(() => {return (index.value - 1) * -width}); const snapPt = (e) => { "worklet"; return snapPoint(translateX.value, e.velocityX, snapPoints); }; const pan = useMemo(() => Gesture.Pan() .onUpdate((event) => { translateX.value = offsetX.value + event.translationX; translationX.value = event.translationX; console.log("pan event : ", event); }) .onEnd((event)=>{ let val = snapPt(event); let sp = Math.min(Math.max(lowVal.value, val), highVal.value); if (event.translationX !== 0) { translateX.value = withTiming(sp); offsetX.value = event.translationX < 0 ? Math.max(Math.min(translateX.value, sp), (images.length - 1) * -width) : Math.min(Math.max(translateX.value, sp), 0); }; index.value = Math.abs(sp)/width; console.log("onEnd offsetx.value : ", offsetX.value) }),[]); const pinch = useMemo(() => Gesture.Pinch() .onUpdate((event) => { console.log("pinch event : ", event); focalX.value = event.focalX - width / 2; focalY.value = event.focalY - height / 2; console.log("index in pinch :", `${focalX.value}, ${focalY.value}, ${index.value}`); scale.value = event.scale; }) .onEnd((event) => { scale.value = withTiming(1); focalX.value = withTiming(offsetX.value); focalY.value = withTiming(0); }) .simultaneousWithExternalGesture(pan) ); const swipeStyle = useAnimatedStyle(() => { return { transform: [{ translateX: translateX.value}] } }, []); useEffect(() => { translateX.value = offsetX.value; }, [initIndex]); return ( <GestureHandlerRootView> <GestureDetector gesture={pinch}> <GestureDetector gesture={pan}> <Animated.View style={styles.container}> <Animated.View style={[{width: width * images.length, height, flexDirection: "row"}, swipeStyle]}> {images.map((img_source, i) => { const pinchStyle = useAnimatedStyle(() => { return { transform: [ { translateX: (-width / 2 + focalX.value + offsetX.value)}, { translateY: (-height / 2 + focalY.value)}, { scale: scale.value }, { translateX: (width / 2 - focalX.value - offsetX.value)}, { translateY: (height / 2 - focalY.value)}, ], }; }, []); return ( <Animated.View key={i} style={[styles.picture, pinchStyle]}> <View style={[styles.image]}> <TouchableWithoutFeedback onPress={() => navigation.goBack()} style={styles.button}> <View style={styles.button}> <CachedImage source={{uri:img_source.path.split("?")[0]}} sourceAlt={{uri:img_source.path}} resizeMode={"center"} style={[styles.image]} /> </View> </TouchableWithoutFeedback> </View> </Animated.View> ); })} </Animated.View> </Animated.View> </GestureDetector> </GestureDetector> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: 'black', }, picture: { width: width, height: height, }, image: { width: '100%', height: '100%', }, button: { flex: 1, }, });
问题原因分析
- 图片容器未设置
overflow: hidden,缩放时超出容器的内容(包括相邻图片)会直接显示 - 缩放变换逻辑冗余且错误,多次叠加的
translateX导致图片容器偏移,暴露其他图片区域 - 所有图片都应用了相同的缩放变换,未区分当前活跃的图片
修复方案
1. 添加容器裁剪规则
修改styles.picture,新增overflow: hidden,确保每个图片只显示自身容器内的内容:
picture: { width: width, height: height, overflow: 'hidden', // 裁剪超出容器的内容 },
2. 修正缩放变换逻辑
只对当前活跃的图片应用缩放,同时简化变换逻辑,确保基于捏合焦点缩放时不会偏移到其他图片区域:
{images.map((img_source, i) => { const pinchStyle = useAnimatedStyle(() => { // 仅当前选中的图片应用缩放 const isActive = index.value === i; return { transform: isActive ? [ // 基于焦点的缩放:先平移到焦点位置,缩放后再平移回去 { translateX: focalX.value * (scale.value - 1) }, { translateY: focalY.value * (scale.value - 1) }, { scale: scale.value }, ] : [], }; }, []); return ( <Animated.View key={i} style={[styles.picture, pinchStyle]}> <View style={[styles.image]}> <TouchableWithoutFeedback onPress={() => navigation.goBack()} style={styles.button}> <View style={styles.button}> <CachedImage source={{uri:img_source.path.split("?")[0]}} sourceAlt={{uri:img_source.path}} resizeMode={"center"} style={[styles.image]} /> </View> </TouchableWithoutFeedback> </View> </Animated.View> ); })}
3. 优化捏合焦点计算
调整手势更新逻辑,确保焦点计算基于当前屏幕中心,避免滚动容器的位置干扰:
const pinch = useMemo(() => Gesture.Pinch() .onUpdate((event) => { // 计算相对于屏幕中心的焦点位置 focalX.value = event.focalX - width / 2; focalY.value = event.focalY - height / 2; scale.value = event.scale; }) .onEnd((event) => { scale.value = withTiming(1); focalX.value = withTiming(0); focalY.value = withTiming(0); }) .simultaneousWithExternalGesture(pan) );
内容的提问来源于stack exchange,提问作者user938363
相关产品推荐
相关产品推荐

