How to Build a Browser-Based Image Compressor Using JavaScript

image compressor

You may have noticed that a maximum file size is specified when uploading documents online on government or college websites. Uploading limits are placed so that large image files do not take up too much storage space on the server.

That's why people take the help of online image compression tools. But there's a problem. Many of these tools upload your images to a remote server. In this case, many users don't want their private photos to be stored on someone else's server.

The good news is that you can compress images using JavaScript in your browser.

Hi, I'm Soumya Roy. In this tutorial you will learn how to create a browser-based image compressor. The compressor reduces image file sizes using JavaScript, without any data sent to a server, and allows the user to download the compressed file.

Table of Contents

How Image Size Compression Works

Before you start writing code, you should understand what actually happens when an compressed image in the browser. This will help you take the next step.

Image compression is a method to reduce the size of an image file, while trying to keep as much of the visible quality as possible. There are two main methods to compressing images:

  1. Lossless: The pixels of the image are not changed, the file is reorganised to occupy less space.
  2. Lossy: This method removes some pixels in the image which are usually not visible to the eye.

The easy way to compress an image in the browser is to use the Canvas API. Here's the process:

  • Load the image into an <img> element.
  • Draw the image onto the canvas.
  • Export the canvas as a new image using canvas.toBlob() or canvas.toDataURL()
canvas.toBlob(
  (blob) => {
    console.log("Compressed size:", blob.size);
  },
  "image/jpeg",
  0.8 // Quality setting
);

Setting quality to 0.8 will use the browser's built-in encoder to remove subtle color differences. It will produce a image file that looks similar, but is much smaller.

The Canvas API is fast and needs no external libraries. However, it doesn't provide detailed control over features like color profiles or metadata. It's more than enough for an image compressor.

Another method is to use WebAssembly to run encoders directly in the browser. WebAssembly provides the best performance and can produce smaller file sizes than the Canvas encoder. However it is a little trickier to set up. In this guide we will use Canvas API.

Project Setup

Let's build your image size compression tool from scratch. You'll need nothing more than a text editor and a modern browser like Chrome or Edge, ect.

Create a new folder on your computer. Inside it, create three files:

  • index.html - the structure of our tool.
  • style.css - some basic styling.
  • script.js - all the JavaScript magic.

Here's a index.html to get started:

<div class="container">
  <div class="upload-area" id="uploadArea">
    <p>Click or drag and drop an image here</p>
    <input type="file" id="fileInput" accept="image/jpeg,image/png,image/webp" style="display: none;">
  </div>

  <div class="controls" id="controls" style="display: none;">
    <div class="quality-slider">
      <label>Compression quality: <span id="qualityValue">0.8</span></label>
      <input type="range" id="qualitySlider" min="0.1" max="1.0" step="0.01" value="0.8">
    </div>
    <div class="format-selector">
      <label>Output format:</label>
      <select id="formatSelect">
        <option value="image/jpeg">JPEG</option>
        <option value="image/webp" selected>WebP (recommended)</option>
        <option value="image/png">PNG (lossless, often larger)</option>
      </select>
    </div>
    <button id="compressBtn">Compress and Download</button>
  </div>
</div>

A little style.css code to make it look nice:

body {
    background: #f5f7fb;
    margin: 0;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 20px;
}

.container {
    max-width: 500px;
    width: 100%;
    background: white;
    border-radius: 24px;
    box-shadow: 0 12px 30px rgba(0,0,0,0.1);
    padding: 2rem;
}

.upload-area {
    border: 2px dashed #cbd5e1;
    border-radius: 16px;
    padding: 2rem;
    text-align: center;
    cursor: pointer;
    transition: all 0.2s ease;
    background: #f8fafc;
}

.upload-area:hover {
    border-color: #3b82f6;
    background: #eff6ff;
}

.controls {
    margin-top: 1.5rem;
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    align-items: flex-end;
    background: #f1f5f9;
    padding: 1rem;
    border-radius: 16px;
}

.quality-slider {
    flex: 2;
    min-width: 180px;
}

.quality-slider label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: 500;
}

input[type="range"] {
    width: 100%;
}

.format-selector {
    flex: 1;
}

button {
    background: #3b82f6;
    color: white;
    border: none;
    padding: 0.6rem 1.2rem;
    border-radius: 40px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.2s;
}

button:hover {
    background: #2563eb;
}

This gives us a responsive interface, with space for uploading and format control.

Which Library Will You Use?

We have two options for this project: writing the compression logic from scratch using the Canvas API or using a helper library. For this tutorial, we will use the CDN version. It exposes a global function imageCompression that we will call.

Let me tell you one thing clearly. Writing your own is fine for learning, but in production, I strongly recommend using a library. Here we will use one such tested browser-image-compression library.

  • Auto resize based on maximum width or height.
  • Targeting a specific file size.
  • Support for JPEG, PNG, WebP, and BMP.

If you use a build system, you can install it via npm:

npm install browser-image-compression

Or add it using a CDN:

<script src="https://cdn.jsdelivr.net/npm/browser-image-compression@2.0.2/dist/browser-image-compression.js"></script>

For this tutorial, we'll use the CDN version. It provides a global imageCompression function, which we will call.

But wait. Shouldn't you learn the basic canvas method first? Of course, and I'll show you both. Understanding how the backend works will help you become a better developer.

So in our script.js, we'll start with a custom Canvas based compressor. Then I'll show you how to use a library.

Here is the canvas base method:

async function compressWithCanvas(file, quality, mimeType) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');

        img.onload = () => {
            canvas.width = img.width;
            canvas.height = img.height;
            ctx.drawImage(img, 0, 0);
            canvas.toBlob(
                (blob) => {
                    if (blob) resolve(blob);
                    else reject(new Error('Compression failed'));
                },
                mimeType,
                quality
            );
        };
        img.onerror = () => reject(new Error('Failed to load image'));
        img.src = URL.createObjectURL(file);
    });
}

For most use cases, this is enough. Sometimes the page may freeze up with large files.

Now let's first create the complete tool using our function. Next we will discuss why you might go to the library.

Creating the Upload Interface

We need to capture the image that the user selects. Our HTML already has a hidden file input and a clickable upload area. Along with this, we will also add drag and drop facility.

Open script.js and start with the DOM elements:

const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const controlsDiv = document.getElementById('controls');
const qualitySlider = document.getElementById('qualitySlider');
const qualityValue = document.getElementById('qualityValue');
const formatSelect = document.getElementById('formatSelect');
const compressBtn = document.getElementById('compressBtn');

let currentFile = null;

Now, handle click to open file picker:

uploadArea.addEventListener('click', () => {
    fileInput.click();
});
Handle file selection from the input:
fileInput.addEventListener('change', (e) => {
    if (e.target.files && e.target.files[0]) {
        handleNewFile(e.target.files[0]);
    }
});

Add drag & drop:

uploadArea.addEventListener('dragover', (e) => {
    e.preventDefault();
    uploadArea.style.borderColor = '#3b82f6';
    uploadArea.style.background = '#eff6ff';
});

uploadArea.addEventListener('dragleave', () => {
    uploadArea.style.borderColor = '#cbd5e1';
    uploadArea.style.background = '#f8fafc';
});

uploadArea.addEventListener('drop', (e) => {
    e.preventDefault();
    uploadArea.style.borderColor = '#cbd5e1';
    uploadArea.style.background = '#f8fafc';
    const file = e.dataTransfer.files[0];
    if (file && file.type.startsWith('image/')) {
        handleNewFile(file);
    } else {
        alert('Please drop an image file (JPEG, PNG, WebP).');
    }
});

Now let's talk about the handleNewFile function. It shows the controls and stores the file.

function handleNewFile(file) {
    if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
        alert('Only JPEG, PNG, and WebP images are supported.');
        return;
    }

    currentFile = file;
    controlsDiv.style.display = 'flex';

    uploadArea.replaceChildren(
        Object.assign(document.createElement('p'), {
            textContent: `Selected: ${file.name}`
        }),
        Object.assign(document.createElement('p'), {
            textContent: `Size: ${(file.size / 1024 / 1024).toFixed(2)} MB`
        })
    );
}

We also need to update the quality value display:

qualitySlider.addEventListener('input', () => {
    qualityValue.textContent = qualitySlider.value;
});

Selecting Image Quality

The quality slider is already set. But what does quality actually mean? For JPEG and WebP, it's a number between 0 and 1, where 1 is the best and 0 is the worst. The sweet spot is usually 0.7 to 0.85. This can reduce file size by up to 70-90% without any visible difference.

Let's test this with a real example. I took a 4.2 MB iPhone photo. Then I compressed it at different quality settings using our canvas function:

Quality File size Visual quality
1.0 3.8 MB Similar to the original
0.85 520 KB Can't tell the difference.
0.7 340 KB Okay for the web.
0.5 210 KB Noticeable
0.3 130 KB Blocky

So for most use cases, a default of 0.8 or 0.85 is excellent.

We also need to understand the output format. WebP always performs 20 to 30% better than JPEG at the same quality level.

For example, a JPEG at quality 0.85 might be 520 KB, while a WebP at the same quality could be 380 KB. That's why I set WebP as the default in our format Selection.

Compressing and Downloading Images

Now it's time for the main event. When the user clicks "Compress and Download", we'll:

  • Take the current file, quality, and selected format.
  • Run the compression
  • Trigger a download of the compressed file.

Let's add the compression and download handler:

compressBtn.addEventListener('click', async () => {
    if (!currentFile) {
        alert('Please select an image first.');
        return;
    }

    const quality = parseFloat(qualitySlider.value);
    const mimeType = formatSelect.value;
    compressBtn.disabled = true;
    compressBtn.textContent = 'Compressing...';

    try {
        // Use our custom Canvas compressor
        const compressedBlob = await compressWithCanvas(currentFile, quality, mimeType);
        
        // Trigger download
        const downloadLink = document.createElement('a');
        const downloadUrl = URL.createObjectURL(compressedBlob);
        downloadLink.href = downloadUrl;
        const originalName = currentFile.name.split('.')[0];
        const extension = mimeType.split('/')[1];
        downloadLink.download = `${originalName}_compressed.${extension}`;
        document.body.appendChild(downloadLink);
        downloadLink.click();
        document.body.removeChild(downloadLink);
        URL.revokeObjectURL(downloadUrl);
    } catch (error) {
        console.error(error);
        alert('Compression failed: ' + error.message);
    } finally {
        compressBtn.disabled = false;
        compressBtn.textContent = 'Compress & Download';
    }
});

That's a fully functional image compressor! At this point, you can test it by opening the index.html file in the browser.

Compress image using library

Now, I will show you how to compress an image using a browser-image-compression library.

async function compressWithLibrary(file, quality, mimeType) {
    return imageCompression(file, {
        fileType: mimeType,
        initialQuality: quality,
        useWebWorker: true
    });
}

The library set up is very simple. You just need to set the compressWithLibrary() function instead of compressWithCanvas().

Preview: How the Image Size Compression Tool Works

Here's a preview of image size compression tool. Click the button to see the tool works:


Important Notes from Use

I have used this method in many real projects. So here are the lessons I learned that you won't find in the official documentation:

EXIF metadata is removed

Canvas compression removes EXIF metadata, which includes GPS location, camera details, and orientation data. This is generally beneficial for privacy.

WebP support

WebP works in all modern browsers. It only doesn't support very old browsers, such as older versions of Internet Explorer and Safari.

function supportsWebP() {
    const canvas = document.createElement('canvas');
    return canvas.toDataURL('image/webp').indexOf('image/webp') === 5;
}

Then adjust your default format.

Memory load

Very large images can use 25MB of memory when loaded into the canvas, which can cause the device to crash. To avoid this, resize the image before compressing it. Libraries like browser-image-compression provide maxWidthOrHeight options to auto resize images.

Conclusion

Reducing image size in the browser using JavaScript is not a fantasy. This is a reliable strategy that you can implement. Using the simple Canvas API, you can create a tool that: Protects privacy, Works offline, and More control

We built a full compressor together. It includes drag and drop upload, quality selection, and automatic download.

Now, what will be your next step?

  • Add a resize option
  • Implement batch compression
  • Integrate WebAssembly encoders

So why wait? Give your users to upload images quickly.

Happy Compressing and Coding!

Post a Comment

0 Comments