Cropping Images to a specific Aspect Ratio with JavaScript
source link: https://www.tuicool.com/articles/Brq6RrI
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
In this 3 minute tutorial we’ll write a tiny JavaScript function that helps us crop images to various aspect ratios. Super useful for cropping photos posted to social media timelines or uploaded as profile pictures as these are often required to be of a certain aspect ratio.
Loading the Image Data
To get started we’ll need a source image. Let’s use a generic image URL as our source.
const imageURL = 'path/to/our/image.jpeg';
To crop an image we need to access the actual image data. We can get to this data by loading the URL to an <img>
element.
const inputImage = new Image(); inputImage.src = imageURL;
Our next step is drawing the image to a <canvas>
, the canvas will allow us to modify the image data. We’ll add the onload
callback right before setting the src
so we can capture the moment the image has loaded.
// this image will hold our source image data const inputImage = new Image(); // we want to wait for our image to load inputImage.onload = () => { // create a canvas that will present the output image const outputImage = document.createElement('canvas'); // set it to the same size as the image outputImage.width = inputImage.naturalWidth; outputImage.height = inputImage.naturalHeight; // draw our image at position 0, 0 on the canvas const ctx = outputImage.getContext('2d'); ctx.drawImage(inputImage, 0, 0); // show both the image and the canvas document.body.appendChild(inputImage); document.body.appendChild(outputImage); }; // start loading our image inputImage.src = imageURL;
Running this code results in a <canvas>
that is presenting the same image as the image located at our imageURL
.
Cropping The Image to a Square Aspect Ratio
Now that we have gained access to the image data we can start manipulating it.
Let’s start with a square crop. A square crop has an aspect ratio of 1:1. This means each side of the output image has the same length. We can represent this numerically as an aspect ratio of 1
. The aspect ratio of a 200x200 image is 1
, the aspect ratio of a 400x300 image can be calculated by dividing the width and height, which equals 1.333
(400/300).
Let’s edit our code and view the results.
// the desired aspect ratio of our output image (width / height) const outputImageAspectRatio = 1; // this image will hold our source image data const inputImage = new Image(); // we want to wait for our image to load inputImage.onload = () => { // let's store the width and height of our image const inputWidth = inputImage.naturalWidth; const inputHeight = inputImage.naturalHeight; // get the aspect ratio of the input image const inputImageAspectRatio = inputWidth / inputHeight; // if it's bigger than our target aspect ratio let outputWidth = inputWidth; let outputHeight = inputHeight; if (inputImageAspectRatio > outputImageAspectRatio) { outputWidth = inputHeight * outputImageAspectRatio; } else if (inputImageAspectRatio < outputImageAspectRatio) { outputHeight = inputHeight / outputImageAspectRatio; } // create a canvas that will present the output image const outputImage = document.createElement('canvas'); // set it to the same size as the image outputImage.width = outputWidth; outputImage.height = outputHeight; // draw our image at position 0, 0 on the canvas const ctx = outputImage.getContext('2d'); ctx.drawImage(inputImage, 0, 0); // show both the image and the canvas document.body.appendChild(inputImage); document.body.appendChild(outputImage); }; // start loading our image inputImage.src = imageURL;
The result is a square image, great! But let’s take a closer look. It seems our crop is not positioned in the center of the input image. This is because we have not update the drawImage
call. The drawImage
call takes 3 (or more) arguments, the inputImage
, and the x
and y
position to draw the image at.
Our input image is still drawn at location 0
, 0
we need to adjust this so we get a center crop instead of a top left crop.
To do this we need to move the image slightly to the left. Suppose our input image is 400
pixels wide and our output image is 300
pixels wide, to center it we’d need to move the input image 50
pixels to the left (negative 50 pixels). -50
pixels is 300
minus 400
divided by 2
. This results in the following code snippet.
const outputX = (outputWidth - inputWidth) * .5
Let’s update the code snippet, we can use the same code for both the x
and the y
offset.
// the desired aspect ratio of our output image (width / height) const outputImageAspectRatio = 1; // this image will hold our source image data const inputImage = new Image(); // we want to wait for our image to load inputImage.onload = () => { // let's store the width and height of our image const inputWidth = inputImage.naturalWidth; const inputHeight = inputImage.naturalHeight; // get the aspect ratio of the input image const inputImageAspectRatio = inputWidth / inputHeight; // if it's bigger than our target aspect ratio let outputWidth = inputWidth; let outputHeight = inputHeight; if (inputImageAspectRatio > outputImageAspectRatio) { outputWidth = inputHeight * outputImageAspectRatio; } else if (inputImageAspectRatio < outputImageAspectRatio) { outputHeight = inputHeight / outputImageAspectRatio; } // calculate the position to draw the image at const outputX = (outputWidth - inputWidth) * .5; const outputY = (outputHeight - inputHeight) * .5; // create a canvas that will present the output image const outputImage = document.createElement('canvas'); // set it to the same size as the image outputImage.width = outputWidth; outputImage.height = outputHeight; // draw our image at position 0, 0 on the canvas const ctx = outputImage.getContext('2d'); ctx.drawImage(inputImage, outputX, outputY); // show both the image and the canvas document.body.appendChild(inputImage); document.body.appendChild(outputImage); }; // start loading our image inputImage.src = imageURL;
With this update our crop is now centered on the input image.
Creating a Reusable JavaScript Crop Function
As a final step let’s turn our code into a reusable function so we can quickly crop images with various crop aspect ratios. Our JavaScript snippet is already suitable to be used for any aspect ratio, not just squares.
/** * @param {string} url - The source image * @param {number} aspectRatio - The aspect ratio * @return {Promise<HTMLCanvasElement>} A Promise that resolves with the resulting image as a canvas element */ function crop(url, aspectRatio) { // we return a Promise that gets resolved with our canvas element return new Promise(resolve => { // this image will hold our source image data const inputImage = new Image(); // we want to wait for our image to load inputImage.onload = () => { // let's store the width and height of our image const inputWidth = inputImage.naturalWidth; const inputHeight = inputImage.naturalHeight; // get the aspect ratio of the input image const inputImageAspectRatio = inputWidth / inputHeight; // if it's bigger than our target aspect ratio let outputWidth = inputWidth; let outputHeight = inputHeight; if (inputImageAspectRatio > aspectRatio) { outputWidth = inputHeight * aspectRatio; } else if (inputImageAspectRatio < aspectRatio) { outputHeight = inputHeight / aspectRatio; } // calculate the position to draw the image at const outputX = (outputWidth - inputWidth) * .5; const outputY = (outputHeight - inputHeight) * .5; // create a canvas that will present the output image const outputImage = document.createElement('canvas'); // set it to the same size as the image outputImage.width = outputWidth; outputImage.height = outputHeight; // draw our image at position 0, 0 on the canvas const ctx = outputImage.getContext('2d'); ctx.drawImage(inputImage, outputX, outputY); resolve(outputImage); }; // start loading our image inputImage.src = url; }) }
Our new and shiny crop
function can be called like so:
crop('path/to/our/image.jpeg', 1)
Or, to get a “16:9” crop:
crop('path/to/our/image.jpeg', 16/9)
As the function returns a Promise
we can get the results like this:
crop('path/to/our/image.jpeg', 16/9).then(canvas => { // `canvas` is the resulting image })
Or, using async/await:
const canvas = await crop('path/to/our/image.jpeg', 16/9)
View a demo of the end result on CodePen
Conclusion
By using the HTML canvas API and some basic math we build a tiny crop helper function that makes it easy to quickly crop images in various aspect ratios. This helps us prepare images for social media posts, profile pictures, familiar document sizes, or other popular media formats.
To keep the article concise our current solution does not cover these edge cases:
- Browsers being confused by mobile photos EXIF orientation header.
- Canvas memory overflowing on mobile devices for very big images.
- Poor image quality when downscaling images.
If you need a solution for these issues you could exploreDoka, an easy to use image editor that solves these edge cases and features a wide range of additional functionality.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK