Skip to content

Commit ca5a21e

Browse files
Ravi Tejatime-to-program
authored andcommitted
Crop Image Component in React
1 parent b5c7b9b commit ca5a21e

File tree

5 files changed

+119
-83
lines changed

5 files changed

+119
-83
lines changed

src/App.css

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap');
2+
3+
* {
4+
font-family: "Poppins", sans-serif;
5+
margin: 0;
6+
}
7+
8+
.container {
9+
width: 100vw;
10+
height: 100vh;
11+
display: flex;
12+
align-items: center;
13+
justify-content: center;
14+
}
15+
16+
.cropper {
17+
position: relative;
18+
width: 80vw;
19+
height: 90vh;
20+
display: flex;
21+
align-items: flex-end;
22+
justify-content: center;
23+
border-radius: 8px;
24+
overflow: hidden;
25+
/* box-shadow: 0px 10px 9px 0px rgba(0, 0, 0, 0.09); */
26+
}
27+
28+
input[type='radio']{
29+
accent-color: #1895B0;
30+
}
31+
32+
.btn {
33+
font-size: 1rem;
34+
font-weight: 500;
35+
color: #ffffff;
36+
background: #1895B0;
37+
padding: 0.5rem 1.5rem;
38+
border-radius: 0.2rem;
39+
border: 2px solid #1895B0;
40+
cursor: pointer;
41+
margin: 1rem;
42+
}
43+
44+
.btn-outline {
45+
background: #ffffff;
46+
color: #1895B0;
47+
border: 2px solid #1895B0;
48+
}
49+
50+
.action-btns {
51+
padding-bottom: 1rem;
52+
}
53+
54+
.btn-container {
55+
display: flex;
56+
align-items: center;
57+
justify-content: center;
58+
}
59+
60+
.cropped-img {
61+
width: 300px;
62+
height: 300px;
63+
object-fit: contain;
64+
}
65+
66+
.aspect-ratios {
67+
display: flex;
68+
align-items: center;
69+
gap:0.5rem;
70+
font-size: 1rem;
71+
font-weight: 500;
72+
}
73+

src/App.js renamed to src/App.jsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
11
import React, { useState } from "react";
2+
import "./App.css";
23
import FileInput from "./components/FileInput";
34
import ImageCropper from "./components/ImageCropper";
45

56
function App() {
6-
const [image, setImage] = useState("");
7-
const [currentPage, setCurrentPage] = useState("choose-img");
8-
const [imgAfterCrop, setImgAfterCrop] = useState("");
7+
// Define state variables
8+
const [image, setImage] = useState("");
9+
const [currentPage, setCurrentPage] = useState("choose-img");
10+
const [imgAfterCrop, setImgAfterCrop] = useState("");
911

10-
// Invoked when new image file is selected
12+
// Callback function when an image is selected
1113
const onImageSelected = (selectedImg) => {
1214
setImage(selectedImg);
13-
setCurrentPage("crop-img");
15+
setCurrentPage("crop-img");
1416
};
1517

16-
// Generating Cropped Image When Done Button Clicked
18+
// Callback function when cropping is done
1719
const onCropDone = (imgCroppedArea) => {
20+
21+
// Create a canvas element to crop the image
1822
const canvasEle = document.createElement("canvas");
1923
canvasEle.width = imgCroppedArea.width;
2024
canvasEle.height = imgCroppedArea.height;
2125

2226
const context = canvasEle.getContext("2d");
2327

28+
// Load the selected image
2429
let imageObj1 = new Image();
2530
imageObj1.src = image;
2631
imageObj1.onload = function () {
32+
// Draw the cropped portion of the image onto the canvas
2733
context.drawImage(
2834
imageObj1,
2935
imgCroppedArea.x,
@@ -36,38 +42,41 @@ function App() {
3642
imgCroppedArea.height
3743
);
3844

45+
// Convert the canvas content to a data URL (JPEG format)
3946
const dataURL = canvasEle.toDataURL("image/jpeg");
4047

4148
setImgAfterCrop(dataURL);
4249
setCurrentPage("img-cropped");
4350
};
4451
};
4552

46-
// Handle Cancel Button Click
53+
// Callback function when cropping is canceled
4754
const onCropCancel = () => {
4855
setCurrentPage("choose-img");
49-
setImage("");
56+
setImage("");
5057
};
5158

5259
return (
5360
<div className="container">
5461
{currentPage === "choose-img" ? (
55-
<FileInput setImage={setImage} onImageSelected={onImageSelected} />
62+
<FileInput onImageSelected={onImageSelected} />
5663
) : currentPage === "crop-img" ? (
5764
<ImageCropper
5865
image={image}
5966
onCropDone={onCropDone}
6067
onCropCancel={onCropCancel}
6168
/>
6269
) : (
70+
// Display the cropped image and options to crop a new image or start over
6371
<div>
6472
<div>
6573
<img src={imgAfterCrop} className="cropped-img" />
6674
</div>
6775

6876
<button
6977
onClick={() => {
70-
setCurrentPage("crop-img");
78+
// Allow cropping the current image again
79+
setCurrentPage("crop-img");
7180
}}
7281
className="btn"
7382
>
@@ -76,7 +85,8 @@ function App() {
7685

7786
<button
7887
onClick={() => {
79-
setCurrentPage("choose-img");
88+
// Start over by choosing a new image
89+
setCurrentPage("choose-img");
8090
setImage("");
8191
}}
8292
className="btn"

src/components/FileInput.js renamed to src/components/FileInput.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, { useRef } from "react";
33
function FileInput({ onImageSelected }) {
44
const inputRef = useRef();
55

6+
// Handle the change event when a file is selected
67
const handleOnChange = (event) => {
78
if (event.target.files && event.target.files.length > 0) {
89
const reader = new FileReader();
@@ -19,6 +20,7 @@ function FileInput({ onImageSelected }) {
1920

2021
return (
2122
<div>
23+
{/* Hidden file input element */}
2224
<input
2325
type="file"
2426
accept="image/*"
@@ -27,6 +29,7 @@ function FileInput({ onImageSelected }) {
2729
style={{ display: "none" }}
2830
/>
2931

32+
{/* Button to trigger the file input dialog */}
3033
<button className="btn" onClick={onChooseImg}>
3134
Choose Image
3235
</button>

src/components/ImageCropper.js renamed to src/components/ImageCropper.jsx

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,28 @@ import React, { useState } from "react";
22
import Cropper from "react-easy-crop";
33

44
function ImageCropper({ image, onCropDone, onCropCancel }) {
5-
const [crop, setCrop] = useState({ x: 0, y: 0 });
5+
// Define state variables
6+
const [crop, setCrop] = useState({ x: 0, y: 0 });
67
const [zoom, setZoom] = useState(1);
8+
79
const [croppedArea, setCroppedArea] = useState(null);
810
const [aspectRatio, setAspectRatio] = useState(4 / 3);
911

12+
// Callback when cropping is completed
1013
const onCropComplete = (croppedAreaPercentage, croppedAreaPixels) => {
14+
// Store the cropped area in pixels
1115
setCroppedArea(croppedAreaPixels);
1216
};
1317

18+
// Callback when the user changes the aspect ratio
1419
const onAspectRatioChange = (event) => {
1520
setAspectRatio(event.target.value);
1621
};
1722

1823
return (
1924
<div className="cropper">
2025
<div>
26+
{/* Image Cropper component */}
2127
<Cropper
2228
image={image}
2329
aspect={aspectRatio}
@@ -37,6 +43,7 @@ function ImageCropper({ image, onCropDone, onCropCancel }) {
3743
</div>
3844

3945
<div className="action-btns">
46+
{/* Aspect ratio selection */}
4047
<div className="aspect-ratios" onChange={onAspectRatioChange}>
4148
<input type="radio" value={1 / 1} name="ratio" /> 1:1
4249
<input type="radio" value={5 / 4} name="ratio" /> 5:4
@@ -47,18 +54,21 @@ function ImageCropper({ image, onCropDone, onCropCancel }) {
4754
<input type="radio" value={3 / 1} name="ratio" /> 3:1
4855
</div>
4956

50-
<button className="btn btn-outline" onClick={onCropCancel}>
51-
Cancel
52-
</button>
57+
{/* Buttons for canceling or applying the crop */}
58+
<div className="btn-container">
59+
<button className="btn btn-outline" onClick={onCropCancel}>
60+
Cancel
61+
</button>
5362

54-
<button
55-
className="btn"
56-
onClick={() => {
57-
onCropDone(croppedArea);
58-
}}
59-
>
60-
Done
61-
</button>
63+
<button
64+
className="btn"
65+
onClick={() => {
66+
onCropDone(croppedArea);
67+
}}
68+
>
69+
Crop & Apply
70+
</button>
71+
</div>
6272
</div>
6373
</div>
6474
);

src/index.css

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +0,0 @@
1-
* {
2-
font-family: "Montserrat", sans-serif;
3-
margin: 0;
4-
}
5-
6-
.container {
7-
width: 100vw;
8-
height: 100vh;
9-
display: flex;
10-
align-items: center;
11-
justify-content: center;
12-
}
13-
14-
.cropper {
15-
position: relative;
16-
width: 80vw;
17-
height: 90vh;
18-
display: flex;
19-
align-items: flex-end;
20-
justify-content: center;
21-
border-radius: 8px;
22-
overflow: hidden;
23-
box-shadow: 0px 10px 9px 0px rgba(0, 0, 0, 0.09);
24-
}
25-
26-
.btn {
27-
font-size: 1rem;
28-
font-weight: 500;
29-
color: #ffffff;
30-
background: #1a7ff8;
31-
padding: 0.5rem 1.5rem;
32-
border-radius: 0.2rem;
33-
border: 2px solid #1a7ff8;
34-
cursor: pointer;
35-
margin: 1rem;
36-
}
37-
38-
.btn-outline {
39-
background: #ffffff;
40-
color: #1a7ff8;
41-
border: 2px solid #1a7ff8;
42-
}
43-
44-
.action-btns {
45-
padding-bottom: 1rem;
46-
}
47-
48-
.cropped-img {
49-
width: 300px;
50-
height: 300px;
51-
object-fit: contain;
52-
}
53-
54-
.aspect-ratios {
55-
display: flex;
56-
align-items: center;
57-
gap:0.5rem;
58-
font-size: 1rem;
59-
font-weight: 500;
60-
}

0 commit comments

Comments
 (0)