I am trying to crop an image in react by using the react-image-crop library, and I got the cropping functionality working.
import React, { useCallback, useRef, useState } from "react";
import ReactCrop, { Crop } from "react-image-crop";
import "react-image-crop/dist/ReactCrop.css";
export const ImageCropper = () => {
const [upImg, setUpImg] = useState<string>(
"https://www.vintagemovieposters.co.uk/wp-content/uploads/2020/04/IMG_5274-scaled.jpeg"
);
const imgRef = useRef<HTMLImageElement | null>(null);
const [crop, setCrop] = useState<Partial<Crop>>({
unit: "%",
aspect: 0.68,
height: 100
});
const onLoad: (image: HTMLImageElement) => boolean | void = useCallback(
(img) => {
imgRef.current = img;
const aspect = 0.68;
const width =
img.width / aspect < img.height * aspect
? 100
: ((img.height * aspect) / img.width) * 100;
const height =
img.width / aspect > img.height * aspect
? 100
: (img.width / aspect / img.height) * 100;
const y = (100 - height) / 2;
const x = (100 - width) / 2;
setCrop({
unit: "%",
width,
height,
x,
y,
aspect
});
},
[]
);
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<ReactCrop
src={upImg}
onImageLoaded={onLoad}
crop={crop}
onChange={(crop, percentageCrop) => {
setCrop(percentageCrop);
}}
keepSelection={true}
/>
<div
style={{
width: imgRef.current?.width! * (crop.width! / 100),
height: imgRef.current?.height! * (crop.height! / 100),
overflow: "hidden"
}}
>
<img
alt="cropped_image"
src={upImg}
style={{
width: imgRef.current?.width!,
height: imgRef.current?.height!,
transform: `translate(-${
(crop.x! / 100) * imgRef.current?.width!
}px, -${(crop.y! / 100) * imgRef.current?.height!}px )`
}}
/>
</div>
</div>
);
};
However, what I am trying to achieve is:
keep the original image after cropping
put the image in a preview div with specific dimensions (235px x 346px)
transform the image to fit within that preview div with the same defined crop
make sure the preview div matches with the highlighted crop
what I tried is the code above, but the issue with it is that the width + height change dynamically.
I tried to use fixed width and height values, but the cropping is off.
I also tried using the scale property in transform, but it was off too:
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<ReactCrop
src={upImg}
onImageLoaded={onLoad}
crop={crop}
onChange={(crop, percentageCrop) => {
console.log("percent", percentageCrop);
setCrop(percentageCrop);
}}
keepSelection={true}
/>
<div
style={{
width: 235,
height: 346,
overflow: "hidden"
}}
>
<img
alt="cropped_image"
src={upImg}
style={{
width: imgRef.current?.width!,
height: imgRef.current?.height!,
transform: `translate(-${
(crop.x! / 100) * imgRef.current?.width!
}px, -${(crop.y! / 100) * imgRef.current?.height!}px) scale(${
crop.width/100
}, ${crop.height/100})`
}}
/>
</div>
</div>
);
I need to figure out how to constrain them to (235px x 346px), and "zoom" the image to match the crop from react-image-crop.
How can I do that?
Example in code sandbox
Related
Here is a sample code sandbox for this issue.
Things I want to do is
1, Click open modal button to open the modal.
2, This opened modal shows a whole image.
(No margin, padding just want to show the whole image itself)
So I need to get image width and height that is different based on which images get passed.
Right now, I can get width and height like this.
function getMeta(url) {
const img = new Image();
img.addEventListener("load", function () {
console.log(this.naturalWidth + " " + this.naturalHeight);
setWidth(this.naturalWidth);
setHeight(this.naturalHeight);
});
img.src = url;
}
To execute this function like this
getMeta("https://cdn.pixabay.com/photo/2022/03/08/07/08/water-7055153_1280.jpg");
But I don't know how to set width and height to Box component dynamically.
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 500, // this needs dynamic
height: 500, // this needs dynamic
bgcolor: "background.paper",
boxShadow: 24,
p: 4
};
export default function BasicModal() {
const [open, setOpen] = React.useState(false);
const handleOpen = () => {
setOpen(true);
getMeta(
"https://cdn.pixabay.com/photo/2022/03/08/07/08/water-7055153_1280.jpg"
);
};
const handleClose = () => setOpen(false);
const [width, setWidth] = React.useState(0);
console.log("width", width);
const [height, setHeight] = React.useState(0);
console.log("height", height);
function getMeta(url) {
const img = new Image();
img.addEventListener("load", function () {
console.log(this.naturalWidth + " " + this.naturalHeight);
setWidth(this.naturalWidth);
setHeight(this.naturalHeight);
});
img.src = url;
}
return (
<div>
<Button onClick={handleOpen}>Open modal</Button>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<div>This is an image</div>
</Box>
</Modal>
</div>
);
}
Attempts
I thought I could simply use setState for width and height
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: width, // changed
height: height, // changed
bgcolor: "background.paper",
boxShadow: 24,
p: 4
};
export default function BasicModal() {
const [open, setOpen] = React.useState(false);
const handleOpen = () => {
setOpen(true);
getMeta(
"https://cdn.pixabay.com/photo/2022/03/08/07/08/water-7055153_1280.jpg"
);
};
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
function getMeta(url) {
const img = new Image();
img.addEventListener("load", function () {
console.log(this.naturalWidth + " " + this.naturalHeight);
setWidth(this.naturalWidth); // set width
setHeight(this.naturalHeight); // set height
});
img.src = url;
}
...
But this didn't work.
Attempt2
This kind works. I use makeStyles but I'm just wondering why I need two variables for styling.
const useStyles = makeStyles({
root: (props) => ({
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: props.width, // this needs dynamic
height: props.height, // this needs dynamic
bgcolor: "background.paper",
boxShadow: 24
})
});
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 500, // this needs dynamic
height: 500, // this needs dynamic
bgcolor: "background.paper",
boxShadow: 24
};
.
.
.
return (
<div>
<Button onClick={handleOpen}>Open modal</Button>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box
sx={style} // here
className={classes.root} // here
component="img"
src="https://cdn.pixabay.com/photo/2022/06/13/15/36/grain-7260250__340.jpg"
></Box>
</Modal>
</div>
);
It seems like I need both useStyles and style. If I remove one of them, it starts broking the style.
Here is the code for this at the moment.
You can pass the width and height as a parameter to the sx prop in Box.
<Box
sx={{
...style,
width: width,
height: height
}}
>
<div>This is an image</div>
</Box>
This can be shortened even further to:
<Box sx={{ ...style, width, height }}>
<div>This is an image</div>
</Box>
By spreading style, you are able to update width and height while maintaining the rest of the styling.
You can view this in action in a reworked version of your sandbox example: https://codesandbox.io/s/basicmodal-demo-material-ui-forked-pqtdz0?file=/demo.js
Im trying to make my modal (react-modal node package) scrollable (There is an img inside)
here is my code:
content: {
position: 'absolute',
backgroundColor: '#FFF',
padding: '15px',
zIndex: '1000',
width: '90%',
borderRadius: '.5em',
},
overlay: {
position: 'fixed',
display: 'flex',
justifyContent: 'center',
top: '0',
left: '0',
width: '100%',
height: '100%',
backgroundColor: 'rgba(0,0,0, .8)',
zIndex: '1000',
overflowY: 'auto',
},
}
Modal.setAppElement('#__next')
export const langContext = React.createContext()
export default function Home() {
const [isEn, setIsEn] = useState(true)
const [modalIsOpen, setIsOpen] = useState(false)
const { width } = useWindowDimensions()
function openModal() {
setIsOpen(true)
}
function closeModal() {
setIsOpen(false)
}
function updateLang() {
setIsEn((isEn) => !isEn)
}
return (
<langContext.Provider value={{ isEn, updateLang }}>
<div id="modal" className="overflow-hidden relative">
<Header />
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
style={customStyles}>
<div className="relative m-h-[1000px] h-full w-full overflow-y-scroll">
{isEn ? (
<Image
layout="fill"
objectFit={width >= 750 ? 'fill' : ' cover'}
quality={100}
src={
width >= 750 ? '/assets/menu-en.png' : '/assets/Menu_en_m.png'
}
alt="Menu"
/>
) : (
<Image
layout="fill"
objectFit={width >= 750 ? 'fill' : ' cover'}
quality={100}
src={
width >= 750 ? '/assets/menu-he.png' : '/assets/Menu_he_m.png'
}
alt="Menu"
/>
)}
</div>
</Modal>
</div>
</langContext.Provider>
)
}
Any ideas on how can I do it? (I'm trying to play with the overflow and position element but I can't find the proper solution
The scrolling suppose to happen on Mobile and the Image dimensions are: 550*1550 (I can resize it if necessary)
Right now the image is cut
Thanks for the helpers!
I think your modal is good in terms of vertical scrolling, but the problem has may come from this line
objectFit={width >= 750 ? 'fill' : 'cover'}
Your mobile dimension is 500*1550 which means the image will take object-fit:cover;
You can check object-fit:cover; definition here
The image keeps its aspect ratio and fills the given dimension. The image will be clipped to fit
If you already check the dimension to load the device-respective image, you can remove the condition to check for cover.
object-fit:fill is sufficient for your case
The change can be
objectFit="fill"
Another potential problem I can see here that you have layout="fill" for that image which will make it absolute. The image container has relative which restricts the original size of your image.
To fix it, you may need to remove relative from the image container too.
I am using the react-rnd library to drag and resize blocks. I created a page. It creates a gray container on it and I click on the "add global container" button and a container appears on the field that I can move and resize within the parent gray container
in the left corner of the created container there is a purple button, clicking on it, another container will be created inside this container and now the first container will be the parent for the new one created.
the problem is that I can resize the inner container, but I can not move it, or rather, it moves with the parent component. the inner component will move inside the outer only when the parent component touches the borders of its parent, the gray container
in code it looks like this
I have a component , which in itself contains a component
the Box component is called inside Rdn - this is the block that you see on the screen, Rdn makes it move
type Props = {
width: string;
height: string;
color: string;
};
class BoxWrapper extends React.Component<Props> {
state = {
width: "100",
height: "40",
x: 0,
y: 0,
};
render() {
const { width, height, color } = this.props;
return (
<Rnd
size={{ width: this.state.width, height: this.state.height }}
position={{ x: this.state.x, y: this.state.y }}
bounds="parent"
onDragStop={(e: any, d: any) => {
e.stopPropagation();
this.setState({ x: d.x, y: d.y });
}}
minHeight={16}
minWidth={16}
onResize={(
e: any,
direction: any,
ref: any,
delta: any,
position: any
) => {
this.setState({
width: ref.style.width,
height: ref.style.height,
...position,
});
}}
>
<Box
x={this.state.x}
y={this.state.y}
width={this.state.width}
height={this.state.height}
externalHeight={height}
externalWidth={width}
color={color}
/>
</Rnd>
);
}
}
export default BoxWrapper;
inside the Box component, the purple button is checked and if it is pressed, you need to create the BoxWrapper component
type BoxProps = {
x: number;
y: number;
width: string;
height: string;
externalWidth: string;
externalHeight: string;
color: string;
};
class Box extends React.Component<BoxProps> {
state = {
isClick: false,
isCreate: false,
};
render() {
const {
x,
y,
width,
height,
externalWidth,
externalHeight,
color,
} = this.props;
const externalH = parseInt(externalHeight);
const externalW = parseInt(externalWidth);
const boxWidth = parseInt(width);
const boxHeight = parseInt(height);
const xUpperLeft = x;
const yUpperLeft = y;
const xUpperRight = x + boxWidth;
const yUpperRight = y;
const xDownLeft = x;
const yDownLeft = y + boxHeight;
const xDownRight = x + boxWidth;
const yDownRight = y + boxHeight;
return (
<>
<div
style={{
margin: 0,
height: "100%",
padding: 0,
backgroundColor: color,
}}
>
<div className="wrapper">
<button
style={{
width: 0,
height: "14px",
borderRadius: "1px",
backgroundColor: "#fff",
display: "flex",
justifyContent: "center",
fontSize: 9,
}}
onClick={() => this.setState({ isClick: !this.state.isClick })}
>
?
</button>
<button
style={{
width: 0,
height: "14px",
borderRadius: "1px",
backgroundColor: "#a079ed",
display: "flex",
justifyContent: "center",
fontSize: 9,
}}
onClick={() => this.setState({ isCreate: !this.state.isCreate })}
/>
</div>
{this.state.isCreate && (
<BoxWrapper
width={width}
height={height}
color="#42d5bc"
/>
)}
</div>
{this.state.isClick && (
<Tooltip
leftDistance={xUpperLeft}
topDistance={yUpperLeft}
rightDistance={externalW - xUpperRight}
bottomDistanse={externalH - yDownLeft}
/>
)}
</>
);
}
}
how can I make it so that I can freely move the inner container without automatically dragging the parent container
I tried in the onDragStop method in Rnd to specify event.stopPropafgation(), but it doesn’t work at all, I don’t know what to do
this is a working example of my problem the inner Rnd container has the bounds of the bounds = "parent" and the outer one is "window"
problem resolved
you need to replace the onDragStop method with onDrag and specify
event.stopImmediatePropagation();
working example with corrections of the previous
I'm using the claraifai API I've retrieved the regions for the face to form the bounding box but actually drawing the box gives me seriously off values as seen in the image.
code:
return {
topRow: face.top_row * height,
leftCol: face.left_col * width,
bottomRow: (face.bottom_row * height) - (face.top_row * height),
rightCol: (face.right_col * width) - (face.left_col * width)
}
Try to add a div relatively positioned inside the parent div.
Before:
const FaceImage = ({image, boxs}) => {
return (
<div className="faceimage">
<img id="imgA" alt='' src={image}/>
<div className="bounding-box" style={{
top: boxs.top_row,
left: boxs.left_col,
bottom: boxs.bottom_row,
right: boxs.right_col
}}></div>
</div>
);
}
After:
const FaceImage = ({image, boxs}) => {
return (
<div className="faceimage">
<div className="relative">
<img id="imgA" alt='' src={image}/>
<div className="bounding-box" style={{
top: boxs.top_row,
left: boxs.left_col,
bottom: boxs.bottom_row,
right: boxs.right_col
}}></div>
</div>
</div>
);
}
Css:
.relative {
position: relative;
}
I am developing something similar to this
I have many ways to acheive this using css and Scss but i couldn't find anything for react native ,if anyone has any idea how to do this.Their help will be much appreciated.Thank you.
Use cos and sin function to place the images
Working example: https://snack.expo.io/#msbot01/trusting-bagel
import React, { Component } from 'react';
import { Text, View, StyleSheet, ScrollView, FlatList, Image } from 'react-native';
import Constants from 'expo-constants';
import Highlighter from 'react-native-highlight-words';
const size = 200 ;
const symbolSize = 16;
const radius = size / 2;
const center = radius;
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
higightedTexts:''
}
}
componentDidMount(){
this.setupA();
this.setupB();
this.setupC();
this.setupD();
}
degToRad(deg) {
return deg * Math.PI / 180;
}
setupA(){
const angleRad = this.degToRad(0);
const x = radius * Math.cos(angleRad) + center - symbolSize / 2;
const y = radius * Math.sin(angleRad) + center - symbolSize / 2;
this.setState({
x: x,
y: y
})
}
setupB(){
const angleRad = this.degToRad(90);
const x = radius * Math.cos(angleRad) + center - symbolSize / 2;
const y = radius * Math.sin(angleRad) + center - symbolSize / 2;
this.setState({
x2: x,
y2: y
})
}
setupC(){
const angleRad = this.degToRad(180);
const x = radius * Math.cos(angleRad) + center - symbolSize / 2;
const y = radius * Math.sin(angleRad) + center - symbolSize / 2;
this.setState({
x3: x,
y3: y
})
}
setupD(){
const angleRad = this.degToRad(270);
const x = radius * Math.cos(angleRad) + center - symbolSize / 2;
const y = radius * Math.sin(angleRad) + center - symbolSize / 2;
this.setState({
x4: x,
y4: y
})
}
render() {
return (
<View style={{ flex: 1, justifyContent:'center', alignItems:'center' }}>
<View
style={[{backgroundColor:'red',
width: size,
height: size,
borderRadius: size / 2,
}]}
>
<Image
style={{width: 40,
height: 40,
borderRadius:20,
left: this.state.x-20,
top: this.state.y ,
position:'absolute'}}
source={{
uri:
'https://icons.iconarchive.com/icons/graphicloads/100-flat/256/home-icon.png',
}}
/>
<Image
style={{width: 40,
height: 40,
borderRadius: 20,
left: this.state.x2,
top: this.state.y2-20 ,
position:'absolute'}}
source={{
uri:
'https://icons.iconarchive.com/icons/graphicloads/100-flat/256/home-icon.png',
}}
/>
<Image
style={{width: 40,
height: 40,
borderRadius: 20,
left: this.state.x3,
top: this.state.y3 ,
position:'absolute'}}
source={{
uri:
'https://icons.iconarchive.com/icons/graphicloads/100-flat/256/home-icon.png',
}}
/>
<Image
style={{width: 40,
height: 40,
borderRadius: 20,
left: this.state.x4-10,
top: this.state.y4,
position:'absolute'}}
source={{
uri:
'https://icons.iconarchive.com/icons/graphicloads/100-flat/256/home-icon.png',
}}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({});
React native doesn't have any special library do this you can do this by using CSS:
You can do something like this:
import { Dimensions, View, Image } from 'react-native';
import React, { Component } from 'react';
class App extends Component{
render(){
return(
<View style={{flex:1,justifyContent:"center",alignItems:"center"}}>
<View
style = {{
borderRadius: Math.round(Dimensions.get('window').width + Dimensions.get('window').height) / 2,
width: Dimensions.get('window').width * 0.8,
height: Dimensions.get('window').width * 0.8,
borderWidth:5,
borderColor:"red",
justifyContent: 'center',
alignItems: 'center'
}}
underlayColor = '#ccc'
>
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",bottom:Dimensions.get('window').width * 0.7}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",bottom:Dimensions.get('window').width * 0.6,right: 20}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",bottom:Dimensions.get('window').width * 0.6,left: 20}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",top:Dimensions.get('window').width * 0.7}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",top:Dimensions.get('window').width * 0.6,right: 20}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",top:Dimensions.get('window').width * 0.6,left: 20}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",bottom:Dimensions.get('window').width * 0.3,left:-20}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",bottom:Dimensions.get('window').width * 0.3,right:-20}} />
</View>
</View>
)
}
}
export default App;
Hope this helps!