keepcalculator

06 Numbers and Powers

Compress Images in React Before Uploading

Card No. 06·55636 · Updated · Standard formula, shown below

This guide shows you how to compress images in a React app before they reach your server, saving bandwidth, storage, and upload time. You’ll use the browser’s built-in Canvas API to resize and re-encode the image, so no extra libraries are required. By the end, you’ll have a reusable function that takes a File object and returns a compressed version ready for upload.

What You’ll Need

  • A React project (any setup: Create React App, Vite, Next.js, etc.)
  • Basic familiarity with React hooks and event handlers
  • An image input element (<input type="file" accept="image/*" />)

Step-by-Step

  1. Create a utility file. Make a new file, e.g., utils/imageCompression.js, to keep the compression logic separate from your components.
  2. Write the compression function. Start with a function that accepts the original File, a max width/height, and a quality factor (0 to 1). The function will return a Promise that resolves with a compressed File.
  3. Load the image into an HTMLImageElement. Use URL.createObjectURL(file) to get a temporary URL, then create an Image object and set its src to that URL. Wait for the onload event.
  4. Calculate new dimensions. If the image is larger than your max dimension, scale it down proportionally. For example, if maxWidth is 800 and the image is 1200px wide, new width = 800, new height = 800 * (originalHeight / originalWidth).
  5. Draw to a canvas. Create a canvas element, set its width and height to the new dimensions, and call ctx.drawImage(img, 0, 0, newWidth, newHeight).
  6. Convert canvas to a Blob. Use canvas.toBlob(callback, 'image/jpeg', quality). The callback receives a Blob. If you want to preserve transparency for PNGs, you may need to handle that separately, but JPEG is fine for photos.
  7. Create a new File from the Blob. Use new File([blob], fileName, { type: 'image/jpeg', lastModified: Date.now() }). This gives you a File object that you can upload just like the original.
  8. Clean up the object URL. Call URL.revokeObjectURL(tempUrl) in a finally block to free memory.
  9. Use the function in your component. In your file input’s onChange handler, call the compression function, await the result, and then use the compressed file (e.g., append to FormData).
  10. Test with different images. Try photos, screenshots, and large images to see how the quality and file size change. Adjust the quality parameter to suit your needs.

Common Mistakes

  • Not waiting for the image to load. If you try to draw to canvas before onload fires, you’ll get a blank or broken image.
  • Ignoring EXIF orientation. Some photos have orientation metadata. The canvas may ignore this, causing rotated images. Use a library like exif-js or handle it manually if this is a concern.
  • Using a quality of 1.0. That still reduces file size if you resize, but for maximum compression, use 0.7 or lower. Quality below 0.5 may show visible artifacts.
  • Forgetting to revoke the object URL. This causes memory leaks, especially in long sessions.
  • Assuming PNG transparency survives JPEG conversion. JPEG doesn’t support transparency; the background becomes black. If you need transparency, keep the image as PNG.

Tips

  • Use a max dimension that fits your use case. For profile photos, 500px is plenty. For gallery thumbnails, 200px. For full-screen images, 1920px.
  • Compress in a Web Worker for large images. Canvas operations are synchronous and can block the UI. For a smoother experience, you can move the compression to a worker, but it’s overkill for most apps.
  • Show a preview. Use the compressed blob with URL.createObjectURL to show the user what will be uploaded.
  • Compare file sizes. Log the original and compressed sizes to verify the compression is working.
  • Handle multiple files. If you allow multiple uploads, use Promise.all to compress them in parallel.

Troubleshooting

  • Canvas is tainted (SecurityError). This happens if you load an image from a different origin without CORS. Make sure your image is from the same origin or the server sends Access-Control-Allow-Origin headers.
  • toBlob returns null. This can happen if the canvas is too large (some browsers have limits) or if the image is corrupted. Check the dimensions and try lowering the quality.
  • The compressed file is larger than the original. This can happen with already-compressed images (like small PNGs). If the file is already small, skip compression or use a lower quality.
  • Image appears black after JPEG conversion. As mentioned, JPEG doesn’t support transparency. If your image has transparency, either keep it as PNG or fill the background with white before drawing.
  • The image is rotated incorrectly. Use a library like exif-js to read orientation and rotate the canvas accordingly, or use the createImageBitmap API with imageOrientation: 'from-image' (supported in modern browsers).

Frequently Asked Questions

Does this work for all image types?

It works for any image the browser can decode (JPEG, PNG, WebP, etc.). The output is always JPEG unless you change the type in toBlob. For PNGs with transparency, you’d use image/png but the file size may not shrink much.

Can I compress without resizing?

Yes, you can set maxWidth and maxHeight to the original dimensions, but the quality reduction alone often doesn’t reduce size much. Resizing is the most effective way to shrink file size.

Is there a way to compress video before upload?

Video compression is more complex and typically requires a library like ffmpeg.wasm or a server-side solution. The Canvas API only handles images.

Will this work on mobile browsers?

Yes, all modern mobile browsers support Canvas and toBlob. However, large images may cause memory issues on older devices, so consider limiting the max dimension.

Can I use this with FormData?

Absolutely. After compression, you can append the new File to FormData: formData.append('image', compressedFile). That’s the whole point.

What’s the best quality setting?

It depends on your use case. For profile photos, 0.6-0.7 is a good balance. For images that will be viewed full-screen, 0.8-0.9. Test with a few values and check the visual difference.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →