How to Crop Images in React (react-image-crop)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's going on guys welcome back to another video today I'll be building a react image Cropper we'll have this profile page where the user can change their profile picture by clicking this edit icon that'll bring up this modal where they can upload an image from their computer I'll select this image then we'll get a preview of the image in the modal and this cropping tool then the user can adjust the cropping selection and once they're satisfied with it they can click crop image and that will update their profile picture we'll be using a third party Library called react image crop to help us with the cropping functionality and then we'll style the rest of the app using Tailwind CSS without further Ado let's get [Music] started so I have the starting files open here and you can see it's just a basic app with one component the profile component and inside that we have an avatar URL that's stored in a reference and then some state to control whether our modal is open or not and then we have our image tag to display our Avatar URL here and this button to open the modal it just sets set modal open to true and we're displaying the user's name and bio below that and then we're doing a check if the modal is open we're going to Output this component in the modal component we just have a bunch of tailin classes to style our modal and a close button to close it so right now we just have an empty modal with no functionality let's begin by adding the file input button at the top left so when the user clicks it it'll open up the file picker under components let's create a new file called image Cropper instead of returning this div let's return a fragment and inside I'm going to paste in our label this just consists of a span and an input element with a bunch of tail andnd classes to style it I also have this onchange function that runs when the user selects a file I'll comment this out for now and save then let's import this image Cropper into our modal so right under our button here we're just going to open up a new tag and import our image Cropper then if we save we should see our file button and when we click it it will open up our file picker now let's build the event handler that runs when the user uploads an image back in the image copper let's uncomment the onchange function and let's define this function in our component so we'll do const on select file and that's going to be a function it's going to take in an event as a parameter first we want to grab the file that was uploaded so we could access that through event. target. files and then grab the first element then we'll check if there's a file and if there's no file we'll just return out of the function next we'll create a file reader and on that file file reader we want to attach an event listener the event we want to listen to is the load event and this will fire when the image is fully loaded into our file reader once it's fully loaded we want to get the image URL by calling reader. res. twring and that's going to return a large data URL and we're just going to log that out for now then we just want to call reader. read as data URL and pass in the file that was uploaded this method will trigger the read reader to start reading the file and once it's done it's going to call our load Handler and then run this code here so let's save that and go back to the browser and upload an image and if I open up the console we'll see here is our data URL now instead of logging it to the console I want to set this string in local state and then create an image element within our model that's going to read from that data URL and output an image element let's go back to the code and instead of logging the image URL to the console let's store it in a piece of local state let's define that state above we'll call it image source and by default it'll just be an empty string and in this call back let's just set that state so we'll call set image source and pass in the URL and then in our component let's go below the label and check if image source exists and if that exists we're going to Output a component we'll create a div for its container and give it a few class names we're going to make it a flex container and align the items in the center and then we want to Output a react crop component if we look at the docs we could see all the properties that we can pass to it to use the library we need to import a CSS file that they give us and then import a component called react crop when we output the component we need to pass it a crop property and and an onchange function then we need to pass an image element as a child and pass in the image source so it knows what image we want to crop let's scroll down and take a look at what this crop property looks like you can see here that it's an object with a few properties there's a unit that could be pixels or percent the X position the Y position and the width and height of our crop box so let's go create some state for our crop and add this react component first let's import the CSS file before we forget so we'll come up to the top and just grab this line and then we just need to go to our app.jsx and import it at the top once we pasted that in we're ready to use the component now let's go back to our image Cropper after the div we'll open up a new tag and say react crop we'll import that at the top and then pass it the props first we want to pass the crop prop property and for now we'll just leave it at crop we'll create this state in a second then let's pass the circular prop and that's just going to tell us that the crop should be a circle instead of a square we also want to pass keep selection so that the user can't disable the selection then let's pass the aspect prop and that's going to refer to the aspect ratio which we want to be one since it's a perfect square or Circle rather and then we want to pass the Min width prop and this is going to be 150 pixels but I'm to store this in a constant above because we're going to reuse it quite a bit so let's go up to the top and Define our Min width so we'll do const Min Dimension that's going to be 150 and then let's also declare a constant for aspect ratio so we could change it easy later on and we'll set that to one then let's use these constants we'll set the aspect ratio and then the minimum Dimension now let's create this crop state right below the source we'll create another piece of State call it crop and by default it will just be empty now let's create our image element so we'll scroll down to the react crop component and as a child we're going to Output an image tag for the source we want to use the image source that is stored in our state for the alt tag we could just put upload then we want to pass it a style prop we'll pass an object and set the max height to 70 VH now let's save and see how this looks like in the browser let's open up the modal and upload an image and we can see that the image is displayed however we don't see the crop tool and the reason for that is because we need to set our crop in local state right now it's empty and therefore it doesn't display any tool we need to pass it an object with the X Y unit and width and height so when when the image is loaded in we want to set our local state to an object like this and then the cropping tool will show up however we need to wait till our image tag has fully loaded in before we can output the cropping tool to do that we can pass a call back to the image element that will run only once the image is loaded the name of that callback is onload and we're going to pass a function called on image load then let's go ahead and create this function above right below our select file Handler we'll do const on image load it's going to take in an HTML event when this function runs we want to set our crop State here to the crop object to create the crop object the library gives us a utility function that makes it really easy so let's do const crop is equal to make aspect crop and that comes from the library and now we need to pass it some parameters the first one is an object and we need to pass the unit property so we'll say unit and we'll set it to pixels for now and then we need to pass a width and that would refer to the width of the crop so we'll just pass our minimum Dimension which is 150 the second parameter is the aspect ratio so we'll just pass in our constant above which is just one the next parameter is the width of our image and we could get that off of this event so right above let's grab the width and height and that's going to come from event. current Target and then we'll pass these parameters in as the last two now that we have this crop object we could set it in our state so we'll do set crop and pass that in so now when our image gets rendered after we've set the image source it's going to load the image into the tag and once it's done loading it's going to call on image load then it'll run this function which will make a crop object and and store it in local state so we should see the cropping tool after the image is uploaded let's save and give it a try so I'm just going to refresh and upload the same image and once it's loaded here's our crop and it's 150 pixels wide in the top left of the image if we want the crop circle to default to be in the middle of the image we could call another utility function from the library so instead of setting the crop right here let's create another variable called centered crop and that's going to be equal to Center crop invoked and that comes from the library and we need to pass it our crop object that we created above and then a width and height of our image tag so let's pass those in and then when we set our state we'll pass in the cented crop so now if we save and try again we should see the crop tool be in the center of the image and there it is however we can't move the crop around and the reason for that is we don't have an onchange function in this react crop component so let's add that onchange function below we'll pass on change and this is going to be a function and the first parameter we get is the pixel crop so that'll refer to the crop object in pixel units and the second one will be the percent crop and depending on what you're working with either pixels or percent you'll want to use the specific parameter so in our case we use pixels because we Define that unit up here so we want to keep that consistent so let's go back and call set crop and we're going to pass in the pixel crop now we should be able to move the crop tool around wherever we want and there we go now let's make sure our crop tool scales as the image scales for example if we add our crop tool to be around this guy's face and we resize this screen to make the image smaller you see our crop gets messed up and the reason for that is because we defined it to be in pixels so when we said that our unit is pixels here it's always going to be the pixel value which is 150 even if the image shrinks the crop will stay the same if we want the crop to scale as the image scales we need to use percent so let's go back to our unit and add percent here and now this width property is going to refer to the percent of the image so we don't want 150% of the image we could say 25% for now and since we change this unit to percent let's also change the onchange function down here so instead of passing the pixel crop we want to pass a percent crop so let's save that and just refresh and re-upload the same image and let's drag our crop tool over the face and now when we resize the crop tool should also resize and there we go it scales nicely now let's add some validation so our Avatar image here is 150 pixels x 150 so we want to ensure that the image the user upload is at least 150 by 150 otherwise it'll look blurry so let's go back to our code and on the event that comes in when the image has loaded we could grab the natural width and the natural height and these values will refer to the original image that was uploaded once we have these properties we could do a quick if check so we'll do if the natural width is less than our minimum Dimension or the natural height is less than our minimum Dimension which is 150 then we want to return out and set an error State let's define the error state above we'll just put it below our other pieces of state and call it error and by default it'll be an empty string then let's set the error state if our image is too small so we'll call set error and we'll just pass it a message saying image must be at least 150 by 150 pixels then we need to set our image source back to an empty string so that it hides our image element and return out of the function since we don't want to keep going and set the crop State now let's output our error message if it exists so we'll grab the error from the state and right below the label let's do a check we'll check if the error exists if it does we're going to Output this paragraph tag and just output the error itself and I'm going to change this color to 400 and let's save this and test it out so I'm going to come back and just refresh the page and upload a smaller image that is less than 150 so this image here is 100 by 100 and when we upload we should see the error message however you'll notice that sometimes when you upload an image that's too small you'll see it display for a split second and then the error message comes up and it doesn't make for a really great user experience the reason that happens is because in our file reader when we load the image initially on the reader it'll wait for it to finish loading and once it's done it's going to set the image source that's stored in local state and this image source is being used in our image element down here so we're checking if it exists then and it's going to Output the image element and load the image that was uploaded at this point we haven't done any validation so the image tag will actually load even if the image is too small once it's done loading it's going to call our on image load function and when that runs here is where we do the check and then set the error State and the image source back to an empty string so there is a small amount of time where our image source is the actual data URL that we set right here even though the image isn't valid and sometimes you'll see that jump in the UI a better way to handle that error would be to actually catch the image before we set the source here so instead of doing the if check here when the image is already loaded we want to do this logic here so that if the image is too small we don't even set the state and it never loads our image element down below so let's refactor this code a bit and move the logic to our reader call back first let's programmatically create an image element so we'll do const image element is going to be equal to New Image and you'll see that that creates the element then we'll grab the URL and we want to set the image URL on our element so we'll do image element. source equals image URL and then we want to set the image load listener so just like in our image element here we have this onload we need to set this call back on our dynamic Ally created element so right below this we'll do image element. addevent listener and we want to listen for the load function and here is where we want to do our check so let's just move this if block right below and paste it in the function above and now we need to grab the natural width and natural height so we'll just copy this line and paste it above we could get rid of the width and height and we also need to take the event in so we could get access to the current Target and I'm also going to move this return statement and now our image source will not be set if the image is too small so if we make it in this if block we return out and never set the image source so now we're preventing from the image element from even being loaded if the image is not big enough let's also get rid of these properties here since we don't need them anymore and let's just make sure nothing broke so I'll refresh the page and upload an image that is too small and there's our error message and now let's upload a valid image and there we go however we still see this error message from the image above so let's add another quick check once the image has loaded we want to check if there is an error then we're going to set the error back to an empty string and that should do the trick for us so I'll upload the small image again and we see the error message and now let's upload a normal image and it goes away there we go now what if I upload an image that's exactly 150 by 150 so I'll upload this dog image and you'll see that our crop tool is super small and when we try to adjust it it kind of jumps and the reason that happens is when our image loads and we create this aspect crop you see that we pass the unit as per % and we set it to 25 so what that is basically saying is make the crop tool 25% of the image so our width was 150 and 25% of that is 30 something and that's why it was so small so instead of hardcoding this to 25 we need to dynamically calculate the width that we need so that this width is always at least 150 pixels to do that we can divide 150 by the width of the image and then multiply it by 100 so let's declare a variable called prop width in percent and that's going to be equal to the minimum Dimension which is 150 divided by the width of the image and multiplied by 100 so for example say our width was exactly 150 like in this case it'll be 150 / 150 which is 1 * 100 which is 100 so our width here will be 100 and you'll see that it takes up the entire width of our image if our width was 200 we'll do 150 / 200 which is 75 and then we'll multiply that by 100 so we'll get 75 so this percent will be 75% and 75% of 200 is 150 so this always ensures that our calculated width will always at least be 150 pixels even though we're using a percent here so let's replace the 25 with this variable above and now our crop should always be at least 150 pixels now let's add the crop image button right below and when we click it we should see a preview of what the cropped image will look like on the left let's begin by creating the button under the image element so right below our react crop element I'll create a new button and I'll put the text saying crop image and then I want to add a few class names to it and I'm just going to paste those in but it just changes the font and sets the colors and some padding so now we see that button here now we need to add an onclick Handler that will generate the output of this crop the way that this will work is we're going to create an HTML canvas that's going to sit right below the HTML canvas has a bunch of methods that allow us to paint a picture using a source image so we're going to take this Source image and all the values of this crop box so it's positioning the width the height and paint only the cropped version of The Source image onto our canvas if you look on the documentation for the draw image method you'll see that we can call draw image pass it a source image which is this for example and then the source exp position The Source y Source width and height and then pass the destination X and Y width and height and it'll translate the image from the source onto our destination can I'm going to run through a basic example to help you guys understand this so I have a basic HTML page here where I'm loading an image that's 416 pixels wide and 234 pixels high right below it I'm outputting an HTML canvas that's 150x 150 and I'm cropping this top left corner of the original image so this box here is exactly 150 pixels out and 150 pixels from the top so let's jump into the code for this example it's just the basic HTML page where I'm outputting the image and then a canvas right below and then I have a script tag here where I'm grabbing the canvas and image creating an image object and setting a load call back on it once the image loads in which is just the same URL that I had above it's going to run this function first I'm going to get the context off of our canvas element and the the context is what allows us to call methods like translate or Draw image which will actually paint on the canvas so after grabbing the context I set the crop size to 150 and then set the canvas width and height to 150 so now our canvas is 150 by 150 the next thing I do is get the X and Y position of our crop so in this example it's 0 0 and that corresponds to this top corner so on the image when you're thinking in terms of the canvas this top left point is 0 0 this point all the way at the top right would be 416 0 because the width is 416 pixels this point on the bottom right would be 416 234 and this point in the bottom left would be 0 on the X and 234 on the Y so if our crop starts at the top left then we want 0 0 then we're going to translate the context so that its starting point here will be modified in this example We're translating it by ne0 and0 so in effect it doesn't do anything if I was to change this to 10 pixels so it would translate the starting point of the canvas to 10 0 instead of 0 0 you'll see that there's a little white space that gets displayed and that's because we've taken this starting point which used to be 0 0 and we've translated 10 pixels to the right so now the new starting point is 10 0 and our cropped image starts from 10 0 and paints the rest of the width and height so if you think back to our real example where we're subtracting the X and Y position you could think of it as this Source image starts out like this right in line with the canvas where 0 0 is in the top left but then We're translating by the crop X X and the crop y position so if we wanted to crop this bottom left corner right here we would want to translate the starting point by this remaining space at the top for example it starts like this and if we want to get the bottom left corner we would need to translate this to be above the image so that the new starting point begins with our cropped section likewise if we wanted to start our CR drop at this player's face then we'd go to the starting position and then move ITX pixels until we start at his face right about there so We're translating in both X and Y so that this position is the start of our cropped box on the original image and it only Paints the width and height of our cropped box so I hope that makes sense and the last thing we're doing here is just calling draw image where we pass in The Source image which is our actual image element in the HTML and then we're saying the starting point of the image is 0 Z so this top left corner the width and height of the image is the natural width and height or the original width and height and then we need to pass the canvas destination X and Y so we want to start with 0 0 in the top left and then we want to make sure to pass the natural width and height of the original image so that it doesn't get distorted when you're painting in it over so by setting both of these values of the original Source image and the canvas output it will retain the aspect ratio and not become blurry I hope that made sense and let's jump back into our code and add a few things we'll go back to our button down here and add an onclick Handler and we want to set that to a function then we want to run a function called set canvas preview let's create this function so under Source we'll create a new file called set canvas preview and I'm going to paste in a function that I wrote earlier and let's walk through it so as a parameter we're going to get the image element which is this right here then we'll get the canvas element which is going to sit right below and then the crop state so this will be an object with the X and Y and all these other properties one thing to note is this will be the pixel crop so in our state we have the percent crop because that's what we're setting here but when we call this canvas preview we want to convert the pixels to percent and fortunately we don't have to do that ourselves the library provides a function that will convert pixel crop to percent crop and we don't have to do any work ourselves so this incoming parameter will be the pixel crop State the first thing we're doing is getting the context and just throwing an error if it doesn't exist for whatever reason the next thing we do is grab the pixel ratio so each device has a pixel ratio and if you're on a 4k monitor then the pixel ratio will be higher than if you were on a 1920x 1080 monitor if we don't take into account the pixel ratio then the canvas will be blurry on 4K monitors so when we Define the canvas width and height we want to take the crop width which is the width of our crop tool right here and the height multiply it by the pixel ratio and the scale we get the scale by dividing the natural width and the image element width so if our image here the original size was 300 pixels And We Shrunk the screen size so that it scaled down to 150 then our scale would be two because we've shrunken down to 150 but the original image was 300 so we need to account for the scaling so that's all we're doing right here we're just grabbing our scale values and using those to calculate the width and height if you want to see the effects of not using the pixel ratio then you can set this to one or just remove it from this calculation and if you're on a 4k monitor you will notice the difference the next thing we do is call context. scale and pass in our pixel ratio by default one CSS pixel would equal one pixel in the canvas but since the pixel ratio could be higher like four pixels on the monitor to equal one CSS pixel we want to make sure we get that scale so by setting the pixel ratio it'll make sure the image scales properly with the amount of pixels rendered on the Monitor and then we set the image smoothing quality to high we could set this to low or medium if we don't need as high of a quality and and then we're saving all these options on the canvas we then grab the X and Y position of our crop tool and multiply it by the scale because say our crop was a little bit smaller than this but the original image was much bigger we need to multiply the width of this crop to get the new calculated width at the original image size so we need to multiply it by our scale and once we have these values we translate our canvas so that the starting point of our canvas in the top left will be at the start of our crop tool you can look back at the example I walked through earlier to really understand this lastly we call the draw image function and pass in the source image and set the source X and Y to 0 0 and the width and height of the source image to the Natural width and height and set those same values on the canvas so that it starts at 0 0 on the canvas and uses the image's original natural width and height preserving the aspect ratio then we just store the context now we need to go and use this function in our click Handler so let's go back to our button and add that in our click Handler right here we need to pass it some options so the first was the image then the canvas and then the crop so let's come back to these two parameters and in our crop instead of passing in our percent crop that's in our state we want to call a function called convert to pixel crop and that comes from the library and we want to pass in our crop as the first parameter and then the image width and height to do that we need to actually get this image element that is rendered above so let's create a ref and pass it to our image and let's also create our canvas right below and pass a canvas ref to it so that we could pass it to our function here I'll start by creating the canvas so right below this div that has all of our cropping components we want to go right below that and check if there's a crop in our state we're going to Output a preview canvas so I'll create a canvas element and then pass it a few props first I'll set the class name and give it a margin top of four units and then pass some inline Styles and these styles are going to override the canvas styles that we apply in our function over here like the canvas width and height these inline styles are going to override that so that the preview is displayed at 150 pixels instead of the scaled width I'm going to paste in these Styles so we're setting a border of one pixel setting the object fit to contain and then the width and height to 150 so if we save this we should see this preview canvas here and I'm going to just upload a bigger picture so we can use that for our example there we go now let's create our references so we can pass it to the canvas and image elements I'll come up to the top and just say const image ref is equal to use ref and that's going to come from react and then I'll pass the preview canvas ref so I'll just call this preview canvas ref and now let's pass these refs to our elements so in our image here we'll say ref and pass in the image ref then in our canvas we'll do the same thing now that we've attached the these references to the elements we can go back in our set canvas preview here and instead of passing image we'll do image ref do current and this current property will actually be the element that it's attached to so we're going to get this image element and the same thing for the preview canvas ref we want the current property and then in our convert to pixel crop we want to pass the image ref. current. width so we don't want the natural width we just want the element width and then the same thing for the height so now that we're passing all those we should see the canvas get updated by calling this function and our cropped image would be rendered in this element so let's give it a test I'm going to refresh to make sure our state is clean upload an image I'll move the crop box to be just at its head there and when I hit crop image we should see that crop there and if I move this around to the logo we'll see that updated and that's all working as we want it to the last thing we need to do is actually update our profile image on the homepage so we can either add a new button here that says confirm or update that way the user will get a preview image as they select their crop but I'm just going to crop the image when they hit the button and get rid of the preview so let's go back to the code and on the canvas I'm going to set the display to none and that's going to hide this preview over here but it will still paint the crop onto the canvas we just won't be able to see it the next thing we need to do is get the data URL from our canvas so right after we call set canvas preview here the canvas element is going to have the source data URL of the picture that was painted we can get access to it by calling preview canvas ref. current. to data URL and that's going to return the data URL so I'll do const data URL equals the output of this function and now we need to take this data URL and set it in our profile component Avatar URL let's create an updator function that we can then pass through to our component I'll call it update Avatar so let's do const update Avatar and it's going to take an image source as a parameter and then we're going to call Avatar url. current and set it to whatever was passed in now let's take this function and pass it all the way down to our image Cropper where we need to call it first we'll pass it to the modal here then let's go into our modal and destructure it from the props and pass that into our image Cropper then we also need to pass this close Modo function because we want to close the modal after we update the Avatar and now let's save and destructure both of those functions from the image Cropper so we'll get update Avatar and now let's call this update Avatar function right after we get the data URL so right below this line we want to call update Avatar pass in the data URL and then close the modal so now when we hit the button it'll update the URL close the modal and we should see our new cropped image so when we hit crop there it is and now we can upload a different image and all those updates should be reflected so I'll get another picture here make sure that's all working and if I get a really small image it should still look good on the 150 by 150 Avatar Photo so I'll upload this image and just crop that and that looks good there's one more thing I need to mention and it's that this cropped image is not actually resized to be 150 by 150 so for example if I upload a big image and select a big crop out of that image I'll just select the face and the logo and I hit crop you'll see here it looks like it's pretty small but if I actually open this image in a new tab you'll see that the image even though it's cropped is still pretty big it's about 623 by 623 so after you do the cropping you'd want to resize this image before storing it on the server so that's just one thing to keep in note that this cropping doesn't actually resize the image it just crops it and the minimum crop has to be at least 150 but it could be much bigger as we just saw so that's it for this video thank you guys so much for watching leave a like if you enjoyed And subscribe if you haven't already it would help me out a ton let me know in the comments if you have any feedback or what you guys want to see next and I'll see you in the next [Music] one
Info
Channel: Nikita Dev
Views: 1,604
Rating: undefined out of 5
Keywords: javascript, react, react tutorial, react images, image cropping, js, reactjs
Id: odscV57kToU
Channel Id: undefined
Length: 38min 25sec (2305 seconds)
Published: Mon Dec 04 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.