Cropping Images to a specific Aspect Ratio with JavaScript

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 before posting to social media timelines or uploading profile pictures as these are often required to be of a certain aspect ratio.

In this tutorial we’ll by modifying image data. For example, when a user is about to upload a an image we crop it to a certain aspect ratio. If we just want to present images in a certain aspect ratio we can use a CSS only solution.

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:

If you need a solution for these issues you could explore Doka, an easy to use image editor that solves these edge cases and features a wide range of additional functionality.

Rik Schennink

Web enthusiast

to pqina.nl