Щепотка для увеличения SVG всегда увеличивает в середине просмотра - PullRequest
0 голосов
/ 08 мая 2019

Я получаю изображение SVG с сервера. Я использую веб-просмотр для отображения svg и задаю размер после извлечения исходных svg-измерений из файла.

У меня есть две основные функции: 1) ущипнуть, чтобы увеличить 2) увеличить конкретное место в SVG, учитывая определенные координаты

метод onPlaceHighlight позволяет мне увеличивать определенную точку, и все здесь отлично работает.

Проблема в том, когда я использую два пальца для увеличения. Он всегда будет приближаться к центру изображения svg, в котором я поместил красный вид, который использовал для справки.

class SvgMap extends Component {
static navigationOptions = ({ navigation: { dispatch } }) => ({
    title: 'Maps',
    headerRight: <HeaderRight onPress={() => dispatch(navigateTo('AddFavorite'))}/>,
    // headerRight: <HeaderRight/>,
})
// document.getElementById('parsed').innerHTML= typeof dataJSON.scrollByX;
//scrollTo(dataJSON.scrollByX,dataJSON.scrollByY);
//
svgContent = undefined
scaleFactor = 1
constructor(props){
    super(props)
    this.state = {
        pan: new Animated.ValueXY(),
        scale: new Animated.Value(this.scaleFactor),
        svgReady: false,
    }
    this.translateX = 0
    this.translateY = 0
}
setScaleFactor = (scaleFactor) => {
    this.scaleFactor = scaleFactor
    this.state.scale.setValue(scaleFactor)
}
getScaleFactor = () => this.scaleFactor
setupPanResponder = () => {
    // distance when pinch starts
    let pinchDistance0 = undefined
    // scale when pinch starts
    let pinchScale0 = undefined
    this._panResponder = PanResponder.create({
        onStartShouldSetPanResponder: (evt, gestureState) => true,
        onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
        onMoveShouldSetPanResponder: (evt, gestureState) => true,
        onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
        onPanResponderGrant: () => {},
        onPanResponderMove: (evt, gestureState) => {
            // scale.__getValue()
            const scaleValue = this.getScaleFactor()
            // pinch detected
            if (gestureState.numberActiveTouches >= 2) {
                const {
                    pageX: pageX0, pageY: pageY0
                } = evt.nativeEvent.touches[0]
                const {
                    pageX: pageX1, pageY: pageY1
                } = evt.nativeEvent.touches[1]
                const distance = Math.sqrt(
                    Math.pow(pageX0 - pageX1, 2) + Math.pow(pageY0 - pageY1, 2)
                )
                const distanceDelta = distance - pinchDistance0
                // set distance0 if not set
                if (pinchDistance0 === undefined) {
                    pinchDistance0 = distance
                }
                if (pinchScale0 === undefined) {
                    pinchScale0 = scaleValue
                }
                let scaleFactor = (distanceDelta * 1 / 500) + pinchScale0
                if (scaleFactor < 1) {
                    scaleFactor = 1
                } else
                if (scaleFactor > 2) {
                    scaleFactor = 2
                } else
                if (!scaleFactor) {
                    scaleFactor = scaleValue
                }
                this.setScaleFactor(scaleFactor)
            } else
            if (gestureState.numberActiveTouches < 2) {
                // reset pinch distance
                pinchDistance0 = undefined
                // reset pinch scale
                pinchScale0 = undefined
            }
            const dxMax = (this.state.svgViewStyle.width * (scaleValue - 1)) / 2
            const dxMin = (0 - dxMax) - (this.state.svgViewStyle.width - this.state.rootViewLayout.width)
            const dyMax = (this.state.svgViewStyle.height * (scaleValue - 1)) / 2
            const dyMin = (0 - dyMax) - (this.state.svgViewStyle.height - this.state.rootViewLayout.height)
            let dx = this.translateX + gestureState.dx
            if (dx > dxMax) {
                dx = dxMax
            }
            if (dx < dxMin) {
                dx = dxMin
            }
            let dy = this.translateY + gestureState.dy
            if (dy > dyMax) {
                dy = dyMax
            }
            if (dy < dyMin) {
                dy = dyMin
            }
            console.log(dx,dy)
            this.state.pan.setValue({
                x: dx,
                y: dy
            })
        },
        onPanResponderRelease: (e, gestureState) => {
            // reset pinch distance
            pinchDistance0 = undefined
            // reset pinch scale
            pinchScale0 = undefined
            // scale.__getValue()
            const scaleValue = this.getScaleFactor()
            const dxMax = (this.state.svgViewStyle.width * (scaleValue - 1)) / 2
            const dxMin = (0 - dxMax) - (this.state.svgViewStyle.width - this.state.rootViewLayout.width)
            const dyMax = (this.state.svgViewStyle.height * (scaleValue - 1)) / 2
            const dyMin = (0 - dyMax) - (this.state.svgViewStyle.height - this.state.rootViewLayout.height)
            this.translateX = this.translateX + gestureState.dx
            if (this.translateX > dxMax) {
                this.translateX = dxMax
            }
            if (this.translateX < dxMin) {
                this.translateX = dxMin
            }
            this.translateY = this.translateY + gestureState.dy
            if (this.translateY > dyMax) {
                this.translateY = dyMax
            }
            if (this.translateY < dyMin) {
                this.translateY = dyMin
            }
        }
    })
}
onPlaceHiglight = (place) => {
    console.log('onPlaceHiglight', place)
    let { placeCoordinateX, placeCoordinateY } = place
    if (
        placeCoordinateX !== undefined && placeCoordinateX !== null &&
        placeCoordinateY !== undefined && placeCoordinateY !== null
    ) {
        if (placeCoordinateX === "") {
            placeCoordinateX = 0
        }
        if (placeCoordinateY === "") {
            placeCoordinateY = 0
        }
        const newScaleFactor = 1.6
        Animated.timing(
            this.state.scale, {
                toValue: newScaleFactor,
                useNativeDriver: true,
                easing: Easing.linear,
                duration: 300
            }
        ).start(
            () => {
                this.setScaleFactor(newScaleFactor)
                console.log(this.translateX,this.translateY)
                const scaleValue = this.getScaleFactor()
                const xMax = (this.state.svgViewStyle.width * (scaleValue - 1)) / 2
                const xMin = (0 - xMax) - (this.state.svgViewStyle.width - this.state.rootViewLayout.width)
                const yMax = (this.state.svgViewStyle.height * (scaleValue - 1)) / 2
                const yMin = (0 - yMax) - (this.state.svgViewStyle.height - this.state.rootViewLayout.height)
                // translateRatio assuming no scale
                const translateRatio0 = (
                    this.state.svgViewStyle.width / this.state.svgViewBox.width
                )
                // xMax is x0
                this.translateX = xMax - (
                    ((parseInt(placeCoordinateX) * translateRatio0) * newScaleFactor)
                    - (this.state.rootViewLayout.width / 2)
                )
                // yMax is y0
                this.translateY = yMax - (
                    ((parseInt(placeCoordinateY) * translateRatio0) * newScaleFactor)
                    - (this.state.rootViewLayout.height / 2)
                )
                Animated.timing(
                    this.state.pan, {
                        toValue: {
                            x: this.translateX || 0,
                            y: this.translateY || 0
                        },
                        easing: Easing.elastic(1),
                        duration: 800,
                        useNativeDriver: true
                    }
                ).start()
            }
        )
    }
}
componentWillMount() {
    this.setupPanResponder()
    axios.get(this.props.source.uri)
        .then(result => {
            this.svgContent = result.data
            try {
                const matches = String(result.data)
                    .match(/[.\n]*(<svg[^>]*>)[.\n]*/im)[1]
                    .replace("\n", "")
                    .match(/viewbox="([^"]*)"/i)[1].split(/\s/i)
                console.log("matches",matches)
                this.setState({
                    svgViewBox: {
                        x: parseInt(matches[0]),
                        y: parseInt(matches[1]),
                        width: parseInt(matches[2]),
                        height: parseInt(matches[3])
                    }
                })
            } catch (err) {
                Alert.alert(
                    this.props.t('maps:error_cannot_render_correctly_title'),
                    this.props.t('maps:error_cannot_render_correctly'),
                    [
                        {text: 'OK', onPress: () => {}, style: 'cancel'},
                    ],
                    { cancelable: false }
                )
                console.error(err)
            }
            this.setState({
                svgReady: true
            })
            this.props.onMapLoaded && this.props.onMapLoaded()
        })
}
componentWillUpdate(nextProps, nextState) {
    if (
        // state changes
        !_.isEqual(this.state, nextState) &&
        (
            // and rootViewLayout changes
            !_.isEqual(
                _.get(this.state, 'rootViewLayout'),
                _.get(nextState, 'rootViewLayout')
            ) ||
            // or svgViewBox changes
            !_.isEqual(
                _.get(this.state, 'svgViewBox'),
                _.get(nextState, 'svgViewBox')
            )
        ) &&
        // and are objects both rootViewLayout and svgViewBox
        _.isObject(nextState.rootViewLayout) &&
        _.isObject(nextState.svgViewBox)
    ) {
        const rootView = nextState.rootViewLayout
        const svgViewBox = nextState.svgViewBox
        this.setState({
            svgViewStyle: {
                height: rootView.height,
                width: (svgViewBox.width * (rootView.height / svgViewBox.height))
            }
        })
    }
    if(
        // placeHighlighted changes
        _.get(this.props, 'placeHighlighted') !== _.get(nextProps, 'placeHighlighted') &&
        // placeHighlighted exists and is an Object
        _.isObject(_.get(nextProps, 'placeHighlighted'))
    ) {
        this.onPlaceHiglight(_.get(nextProps, 'placeHighlighted'))
    }
}
render() {
    const {
        styles,
        source,
    } = this.props
    return (
        <View style={[styles.root]}>
            <View style={[styles.root, {
                display: this.state.svgReady ? 'none' : undefined
            }]}>
                <View style={{
                    flex: 1,
                    alignItems: 'center',
                    justifyContent: 'center'
                }}>
                    <Image
                        style={{
                            resizeMode: 'contain',
                            width: 60,
                            height: 60
                        }}
                        source={require('../../assets/gif/loader_inpage.gif')}
                    />
                </View>
            </View>
            <View
                ref={ref => this.rootViewRef = ref}
                onLayout={(event) => {
                    const {x, y, width, height} = event.nativeEvent.layout
                    this.setState({
                        rootViewLayout: {x, y, width, height}
                    })
                }}
                style={[styles.root, {
                    display: this.state.svgReady ? undefined : 'none'
                }]}
                {...this._panResponder.panHandlers}
            >
                <Animated.View
                    style={{
                        transform: [
                            ...this.state.pan.getTranslateTransform(),
                            {scale: this.state.scale || 2}
                        ],
                        position: 'absolute',
                        top: 0,
                        left: 0
                    }}
                >
                    <View style={{
                        width: 50,
                        height: 50,
                        borderRadius: 25,
                        backgroundColor: 'red',
                        position: 'absolute',
                        zIndex:100,
                        top: 275,
                        left: 1000
                    }}/>
                    <WebView
                        ref={ref => this.webViewref = ref}
                        style={[{
                            height: '100%',
                            width: '100%'
                        }, this.state.svgViewStyle]}
                        source={this.props.source}
                        scalesPageToFit={true}
                        bounces={false}
                        javaScriptEnabled={false}
                    />
                </Animated.View>
            </View>
        </View>
    )
}
}

Корневой стиль следующий:

root: {
    flex: 1,
    //backgroundColor: new AttrReference('primaryBackground').value,
    backgroundColor: '#fff',
    width: '100%',
    height: '100%'
},

Есть предложения? Я бы хотел, чтобы щепотка масштабировалась для увеличения масштаба между двумя точками касания, не всегда в центре экрана.

Ценю любые отзывы!

...