Search code examples
javascriptjqueryimageinputresize

Resize images using JavaScript


I have an input of file type with multiple selection property. When the user selects a file, I compress the files using the JavaScript function. Now I wanted to change the length and width of the images using this function. What should I add to the function for this?

My function is as follows

    const compressImage = async (file, { quality = 1, type = file.type }) => {
            // Get as image data
            const imageBitmap = await createImageBitmap(file);

            // Draw to canvas
            const canvas = document.createElement('canvas');
            canvas.width = imageBitmap.width;
            canvas.height = imageBitmap.height;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(imageBitmap, 0, 0);

            // Turn into Blob
            const blob = await new Promise((resolve) =>
                canvas.toBlob(resolve, type, quality)
            );

            // Turn Blob into File
            return new File([blob], file.name, {
                type: blob.type,
            });
        };

        // Get the selected file from the file input
        const input = document.querySelector('.my-image-field');
        input.addEventListener('change', async (e) => {
            // Get the files
            const { files } = e.target;

            // No files selected
            if (!files.length) return;

            // We'll store the files in this data transfer object
            const dataTransfer = new DataTransfer();

            // For every file in the files list
            for (const file of files) {
                // We don't have to compress files that aren't images
                if (!file.type.startsWith('image')) {
                    // Ignore this file, but do add it to our result
                    dataTransfer.items.add(file);
                    continue;
                }

                // We compress the file by 50%
                const compressedFile = await compressImage(file, {
                    quality: 0.3,
                    type: 'image/jpeg',
                });

                // Save back the compressed file instead of the original file
                dataTransfer.items.add(compressedFile);
            }

            // Set value of the file input to our new files list
            e.target.files = dataTransfer.files;
        });


Solution

  • There are at least two options:

    1. Context scale() method:

    The CanvasRenderingContext2D.scale() method of the Canvas 2D API adds a scaling transformation to the canvas units horizontally and/or vertically.

    1. The 5-argument syntax of drawImage():
    drawImage(image, dx, dy, dWidth, dHeight)
    

    The width to draw the image in the destination canvas. This allows scaling of the drawn image.

    The height to draw the image in the destination canvas. This allows scaling of the drawn image.

    In the following example a scale() method is used with the same scaling factor for width and height. A modification of the image input's files is disabled to avoid repetitive scaling, but it can be reenabled by uncommenting e.target.files = dataTransfer.files;.

    const compressImage = async(file, {
      quality = 1,
      type = file.type,
      scalingFactor = 1
    }) => {
      // Get as image data
      const imageBitmap = await createImageBitmap(file);
    
      // Draw to canvas
      const canvas = document.createElement('canvas');
      canvas.width = imageBitmap.width * scalingFactor;
      canvas.height = imageBitmap.height * scalingFactor;
    
      const ctx = canvas.getContext('2d');
      ctx.scale(scalingFactor, scalingFactor);
      ctx.drawImage(imageBitmap, 0, 0);
    
      // Turn into Blob
      const blob = await new Promise((resolve) =>
        canvas.toBlob(resolve, type, quality)
      );
    
      // Turn Blob into File
      return new File([blob], file.name, {
        type: blob.type,
      });
    };
    
    // Get the selected file from the file input
    const input = document.querySelector('.my-image-field');
    input.addEventListener('change', async(e) => {
      // Get the files
      const {
        files
      } = e.target;
    
      // No files selected
      if (!files.length) return;
    
      // We'll store the files in this data transfer object
      const dataTransfer = new DataTransfer();
    
      // Clear the image previews container
      previewsContainer.innerHTML = '';
    
      // For every file in the files list
      for (const file of files) {
        // We don't have to compress files that aren't images
        if (!file.type.startsWith('image')) {
          // Ignore this file, but do add it to our result
          dataTransfer.items.add(file);
          continue;
        }
    
        // We compress the file by 50%
        const compressedFile = await compressImage(file, {
          quality: parseFloat(qualityInput.value),
          type: 'image/jpeg',
          scalingFactor: parseFloat(scalingInput.value)
        });
    
        // Save back the compressed file instead of the original file
        dataTransfer.items.add(compressedFile);
    
        // Show scaled images
        const img = document.createElement('img');
        img.src = URL.createObjectURL(compressedFile);
        previewsContainer.appendChild(img);
      }
    
      // UNCOMMENT TO:
      // Set value of the file input to our new files list
      // e.target.files = dataTransfer.files;
    });
    
    const previewsContainer = document.querySelector('.image-previews');
    
    const scalingInput = document.querySelector('.scaling-factor');
    const qualityInput = document.querySelector('.quality');
    const updateRangeOutput = (inp) => {
      inp.nextElementSibling.innerText = inp.value;
    };
    [scalingInput, qualityInput].forEach(inp => {
      inp.addEventListener('change', () => {
        updateRangeOutput(inp);
        input.dispatchEvent(new Event('change'));
      });
      updateRangeOutput(inp);
    });
    <p>
      Choose images:
      <input type="file" accept="image/*" multiple class="my-image-field">
    </p>
    <p>
      Scaling factor:
      <input type="range" class="scaling-factor" value="0.5" min="0.1" max="2" step="0.1">
      <span>0.5</span>
    </p>
    <p>
      Quality:
      <input type="range" class="quality" value="0.5" min="0.1" max="1" step="0.1">
      <span>0.5</span>
    </p>
    <p class="image-previews"></p>