Applying a circular crop mask to an image

Profile pictures are often masked by a circle. How can we present our images in such a circle. Lets explore two options, one where we use CSS and one where we actually mask the image data.

Applying the mask using CSS

Applying a circular mask to an image using CSS is quite straightforward. We only have to set the border-radius property and we’re done.

<style>
img {
    border-radius: 50%;
}
</style>

<img src="/media/cat.jpeg" alt="">

But what if our image isn’t a square?

To prevent elliptic cats we have to make sure our image is a square.

<style>
img {
    width: 128px;
    height: 128px;
    border-radius: 50%;
}
</style>

<img src="/media/cat.jpeg" alt="">

Okay, but now our cat is squished. Let’s unsquish it using CSS object-fit.

<style>
img {
    width: 128px;
    height: 128px;
    object-fit: cover;
    border-radius: 50%;
}
</style>

<img src="/media/cat.jpeg" alt="">

Done!

Applying a Circular Mask using Canvas

To modify the actual image data we can use the <canvas> element and apply the circular mask to the image itself.

Let’s use a file input element and apply the mask to the added file.

<input type="file" accept="image/*"/>

<canvas hidden></canvas>

<script>
// get a reference to the file input
const fileInput = document.querySelector('input');

// get a reference to the output canvas
const canvas = document.querySelector('canvas');

// listen for the change event so we can capture the file
fileInput.addEventListener('change', (e) => {

    // get a reference to the file
    const file = e.target.files[0];

    // let's load the image data
    const image = new Image();
    image.onload = () => {

        // use min size so we get a square
        const size = Math.min(image.naturalWidth, image.naturalHeight);

        // let's update the canvas size
        canvas.width = size;
        canvas.height = size;

        // draw image to canvas
        const ctx = canvas.getContext('2d');
        ctx.drawImage(image, 0, 0);

        // only draw image where mask is
        ctx.globalCompositeOperation = 'destination-in';

        // draw our circle mask
        ctx.fillStyle = '#000';
        ctx.beginPath();
        ctx.arc(
            size * .5,          // x
            size * .5,          // y
            size * .5,          // radius
            0,                  // start angle
            2 * Math.PI         // end angle
        );
        ctx.fill();

        // restore to default composite operation (is draw over current image)
        ctx.globalCompositeOperation = 'source-over';

        // show canvas
        canvas.hidden = false;
    }
    image.src = URL.createObjectURL(file);

});
</script>

Try it below!

Rik Schennink

Web enthusiast

to pqina.nl