Resize image with javascript canvas (smoothly)

Here is my code, which I hope may be useful for someone out there in the SO community:

You can include your target image dimension as a param in your script call. That will be the result value of your image width or height, whichever is bigger. The smaller dimension is resized keeping your image aspect ratio unchanged. You can also hard-code your default target size in the script.

You can easily change the script to suit your specific needs, such as the image type you want (default is “image/png”) for an output and decide in how many steps percentwise you want to resize your image for a finer result (see const percentStep in code).

   const ResizeImage = ( _ => {

const MAX_LENGTH = 260;     // default target size of largest dimension, either witdth or height
const percentStep = .3;     // resizing steps until reaching target size in percents (30% default)
const canvas = document.createElement("canvas");
const canvasContext = canvas.getContext("2d");
const image = new Image();

const doResize = (callback, maxLength) => {

    // abort with error if image has a dimension equal to zero
    if(image.width == 0 || image.height == 0) {
        return {blob: null, error: "either image width or height was zero "};
    }

    // use caller dimension or default length if none provided
    const length = maxLength == null  ? MAX_LENGTH : maxLength;

    canvas.width = image.width;
    canvas.height = image.height;
    canvasContext.drawImage(image, 0, 0, image.width, image.height);
    // if image size already within target size, just copy and return blob
    if(image.width <= length && image.height <= length) {
        canvas.toBlob( blob => {
            callback({ blob: blob, error: null });
        }, "image/png", 1);
        return;
    }

    var startDim = Math.max(image.width, image.height);
    var startSmallerDim = Math.min(image.width, image.height);

    // gap to decrease in size until we reach the target size,
    // be it by decreasing the image width or height,
    // whichever is largest
    const gap = startDim - length;
    // step length of each resizing iteration
    const step = parseInt(percentStep*gap);
    //  no. of iterations
    var nSteps = 0;
    if(step == 0) {
        step = 1;
    } else {
        nSteps = parseInt(gap/step);
    }
    // length of last additional resizing step, if needed
    const lastStep = gap % step;
    // aspect ratio = value by which we'll  multiply the smaller dimension
    // in order to keep the aspect ratio unchanged in each iteration
    const ratio = startSmallerDim/startDim;

    var newDim;          // calculated new length for the bigger dimension of the image, be it image width or height
    var smallerDim;     // length along the smaller dimension of the image, width or height
    for(var i = 0; i < nSteps; i++) {
        // decrease longest dimension one step in pixels
        newDim = startDim - step;
        // decrease shortest dimension proportionally, so as to keep aspect ratio
        smallerDim = parseInt(ratio*newDim);
        // assign calculated vars to their corresponding canvas dimension, width or height
        if(image.width > image.height) {
            [canvas.width, canvas.height]  = [newDim, smallerDim];
        } else {
            [canvas.width, canvas.height] = [smallerDim, newDim];
        }
        // draw image one step smaller
        canvasContext.drawImage(canvas, 0, 0, canvas.width, canvas.height);
        // cycle var startDim for new loop
        startDim = newDim;
    }

    // do last missing resizing step to finally reach target image size
    if(lastStep > 0) {
        if(image.width > image.height) {
            [canvas.width, canvas.height]  = [startDim - lastStep, parseInt(ratio*(startDim - lastStep))];
        } else {
            [canvas.width, canvas.height] = [parseInt(ratio*(startDim -lastStep)), startDim - lastStep];
        }
        canvasContext.drawImage(image, 0, 0, canvas.width, canvas.height);
    }

    // send blob to caller
    canvas.toBlob( blob => {
        callback({blob: blob, error: null});
    }, "image/png", 1);

};

const resize = async (imgSrc, callback, maxLength) => {
    image.src = imgSrc;
    image.onload = _ => {
       doResize(callback, maxLength);
    };
};

return { resize: resize }

})();

Usage:

ResizeImage.resize("./path/to/image/or/blob/bytes/to/resize", imageObject => {
    if(imageObject.error != null) {
      // handle errors here
      console.log(imageObject.error);
      return;
    }
    // do whatever you want with the blob, like assinging it to
    // an img element, or uploading it to a database
    // ...
    document.querySelector("#my-image").src = imageObject.blob;
    // ...
}, 300);