I am working on a React Native project where I need to implement a camera functionality with a specific requirement. Below I had shared a image I Want to implement below shown functionality, The centred box is draggable and resizable
Here is reference image:
Here is working code and this might help you
import { StyleSheet, Text, View, Dimensions, PanResponder, Animated }
from 'react-native'
import React, { useEffect, useRef, useState } from 'react'
import { Svg, Defs, Rect, Mask, Circle } from 'react-native-svg';
import { AppColor, deviceDisplaySize } from '../Theme/AppTheme';
import { IcnScaleSize } from '../Assets/Icons/SvgIcon';
let _previousLeft = 0;
let _previousTop = 0;
let bottomRightW = 0;
let bottomRightH = 0
let viewMaskingPosition = { x: 0, y: 0 }
let viewParentMaskingSize = { height: 0, width: 0 }
const MaskView = ({ onChangeUpdated, maskStyle = {} }) => {
const maskingViewCornerRadius = 20
const [boxPosition, setBoxPosition] = useState({ x: 0, y: 0 });
const [maskingScaleSize, setMaskingScaleSize] = useState({ height: 100, width: 100 })
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (evt, gestureState) => {
const { dx, dy } = gestureState;
const left = _previousLeft + dx;
const top = _previousTop + dy;
const minX = 10;
const maxX = viewParentMaskingSize.width - maskingScaleSize.width
const minY = 0;
const maxY = viewParentMaskingSize.height - maskingScaleSize.height
const boundedX = Math.min(Math.max(left, minX), maxX);
const boundedY = Math.min(Math.max(top, minY), maxY);
viewMaskingPosition = { x: boundedX, y: boundedY }
setBoxPosition({ x: boundedX, y: boundedY });
},
onPanResponderRelease: (evt, gestureState) => {
_previousLeft += gestureState.dx;
_previousTop += gestureState.dy;
},
onPanResponderEnd: (evt, gestureState) => {
parseCoordinates(gestureState)
}
}),
).current;
const scalePanResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (event, gestureState) => {
const minimumLimit = 100
const maximumLimit = deviceDisplaySize().width - 50
const width = bottomRightW + gestureState.dx;
const height = bottomRightH + gestureState.dy;
const minX = 100;
const maxX = viewParentMaskingSize.width
const minY = 100;
const maxY = viewParentMaskingSize.height
const boundedX = Math.min(Math.max(width, minX), maxX);
const boundedY = Math.min(Math.max(height, minY), maxY);
setMaskingScaleSize({
width: boundedX,
height: boundedY
})
},
onPanResponderRelease: (evt, gestureState) => {
bottomRightW += gestureState.dx;
bottomRightH += gestureState.dy;
},
onPanResponderEnd: (evt, gestureState) => {
parseCoordinates(gestureState)
}
}),
).current;
const onLayout = (event) => {
const { width, height } = event.nativeEvent.layout;
viewParentMaskingSize = { width: width, height: height }
bottomRightW = width - 40
bottomRightH = height / 2.5
const latestSize = { width: bottomRightW, height: bottomRightH }
setMaskingScaleSize(latestSize)
const newY = (height / 2) - (height / 3)
viewMaskingPosition = { x: 20, y: newY }
setBoxPosition({ x: 20, y: newY })
onChangeUpdated({ ...latestSize, ...viewMaskingPosition })
};
return (
<View style={{ flex: 1, }} onLayout={onLayout} >
<Svg height="100%" width="100%" style={[StyleSheet.absoluteFill]}>
<Defs>
<Mask id="mask" x="0" y="0" height="100%" width="100%">
<Rect height="100%" width="100%" fill="#fff" />
<Rect
x={boxPosition.x}
y={boxPosition.y}
height={maskingScaleSize.height}
width={maskingScaleSize.width}
rx={maskingViewCornerRadius} ry={maskingViewCornerRadius}
>
</Rect>
</Mask>
</Defs>
<Rect
height="100%"
width="100%"
fill={AppColor.cameraMasking}
mask="url(#mask)"
fill-opacity="0"
/>
</Svg>
<Animated.View
style={[
{ height: maskingScaleSize.height, width: maskingScaleSize.width },
styles.viewMasking,
{ transform: [{ translateX: boxPosition.x }, { translateY: boxPosition.y }] }
]}
{...panResponder.panHandlers}
>
<View style={{ height: '100%', width: '100%', }}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }} >
<View style={{ height: 25, borderRadius: 2, borderWidth: 1.5, backgroundColor: 'white', borderColor: 'white', opacity: 0.4 }} />
<View style={{ width: 25, borderRadius: 2, borderWidth: 1.5, backgroundColor: 'white', borderColor: 'white', opacity: 0.4, position: 'absolute' }} />
</View>
<View style={{ position: 'absolute', height: 45, width: 45, top: -2, left: -2, borderColor: '#FED422', borderTopWidth: 4, borderLeftWidth: 4, borderTopLeftRadius: maskingViewCornerRadius }} />
<View style={{ position: 'absolute', height: 45, width: 45, top: -2, right: -2, borderColor: '#FED422', borderTopWidth: 4, borderRightWidth: 4, borderTopRightRadius: maskingViewCornerRadius }} />
<View style={{ position: 'absolute', height: 45, width: 45, bottom: -2, left: -2, borderColor: '#FED422', borderBottomWidth: 4, borderLeftWidth: 4, borderBottomLeftRadius: maskingViewCornerRadius }} />
<View style={{ position: 'absolute', height: 45, width: 45, bottom: -2, right: -2, borderColor: '#FED422', borderBottomWidth: 4, borderRightWidth: 4, borderBottomRightRadius: maskingViewCornerRadius, backgroundColor: 'red' }}
{...scalePanResponder.panHandlers}
>
<IcnScaleSize />
</View>
</View>
</Animated.View>
</View>
)
function parseCoordinates(gestureState) {
//Position
const left = _previousLeft + gestureState.dx;
const top = _previousTop + gestureState.dy;
//Scaling
const width = bottomRightW + gestureState.dx;
const height = bottomRightH + gestureState.dy;
const objSize = { width: width, height: height }
const objCoordinate = { x: left, y: top }
onChangeUpdated({ ...objSize, ...viewMaskingPosition })
}
}
export default MaskView
const styles = StyleSheet.create({
viewMasking: {
borderRadius: 23,
position: 'relative',
borderWidth: 1,
borderColor: '#FED422'
}
})