TRY

Here is a complete, single-file solution for a modern and responsive Image Compressor. It uses HTML5, Tailwind CSS (via CDN) for styling, and vanilla JavaScript to handle the compression logic completely within the browser using the <canvas> element.

You can save the code below as an .html file (e.g., index.html) and open it directly in any modern web browser.

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Modern Image Compressor</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        /* Custom drag-active state styling */
        .drag-active {
            border-color: #3b82f6 !important;
            background-color: #eff6ff !important;
        }
    </style>
</head>
<body class="bg-gray-100 min-h-screen text-gray-800 font-sans p-4 md:p-8 flex items-center justify-center">

    <div class="max-w-4xl w-full bg-white rounded-2xl shadow-lg overflow-hidden flex flex-col transition-all duration-300">
        
        <div class="bg-blue-600 p-6 text-center">
            <h1 class="text-2xl md:text-3xl font-bold text-white mb-2">Image Compressor</h1>
            <p class="text-blue-100 text-sm md:text-base">Compress your images locally in the browser. Fast, secure, and private.</p>
        </div>

        <div class="p-6 md:p-8">
            <div id="upload-area" class="border-2 border-dashed border-gray-300 rounded-xl p-10 text-center cursor-pointer hover:bg-gray-50 hover:border-blue-500 transition-colors duration-200">
                <input type="file" id="file-input" class="hidden" accept="image/jpeg, image/png, image/webp" />
                <svg class="mx-auto h-12 w-12 text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
                </svg>
                <p class="text-lg font-medium text-gray-700">Click to upload or drag and drop</p>
                <p class="text-sm text-gray-500 mt-2">Supports JPG, PNG, and WebP</p>
            </div>

            <div id="workspace" class="hidden mt-8 flex flex-col gap-8">
                
                <div class="bg-gray-50 p-6 rounded-xl border border-gray-200 grid grid-cols-1 md:grid-cols-2 gap-6">
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-2">
                            Compression Quality: <span id="quality-value" class="font-bold text-blue-600">80%</span>
                        </label>
                        <input type="range" id="quality-slider" min="0.1" max="1" step="0.05" value="0.8" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-600">
                    </div>
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-2">Output Format</label>
                        <select id="format-select" class="block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
                            <option value="image/jpeg">JPEG</option>
                            <option value="image/webp">WebP</option>
                            <option value="image/png">PNG (Lossless)</option>
                        </select>
                    </div>
                </div>

                <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                    <div class="flex flex-col border border-gray-200 rounded-xl overflow-hidden bg-gray-50">
                        <div class="p-3 bg-gray-100 border-b border-gray-200 flex justify-between items-center text-sm">
                            <span class="font-semibold text-gray-700">Original</span>
                            <span id="original-size" class="text-gray-500">0 KB</span>
                        </div>
                        <div class="p-4 flex items-center justify-center bg-checkered min-h-[250px]">
                            <img id="original-img" class="max-w-full max-h-[300px] object-contain rounded shadow-sm" alt="Original Preview">
                        </div>
                    </div>

                    <div class="flex flex-col border border-gray-200 rounded-xl overflow-hidden bg-gray-50">
                        <div class="p-3 bg-blue-50 border-b border-blue-100 flex justify-between items-center text-sm">
                            <span class="font-semibold text-blue-700">Compressed</span>
                            <span id="compressed-size" class="text-blue-600 font-medium">0 KB</span>
                        </div>
                        <div class="p-4 flex items-center justify-center min-h-[250px]">
                            <img id="compressed-img" class="max-w-full max-h-[300px] object-contain rounded shadow-sm" alt="Compressed Preview">
                        </div>
                    </div>
                </div>

                <div class="flex flex-col sm:flex-row justify-end gap-4 mt-4">
                    <button id="reset-btn" class="px-6 py-2.5 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
                        Upload Another Image
                    </button>
                    <a id="download-btn" class="px-6 py-2.5 shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors text-center cursor-pointer flex items-center justify-center gap-2">
                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
                        Download Compressed
                    </a>
                </div>
            </div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            // DOM Elements
            const uploadArea = document.getElementById('upload-area');
            const fileInput = document.getElementById('file-input');
            const workspace = document.getElementById('workspace');
            
            const qualitySlider = document.getElementById('quality-slider');
            const qualityValue = document.getElementById('quality-value');
            const formatSelect = document.getElementById('format-select');
            
            const originalImg = document.getElementById('original-img');
            const compressedImg = document.getElementById('compressed-img');
            const originalSizeTxt = document.getElementById('original-size');
            const compressedSizeTxt = document.getElementById('compressed-size');
            
            const resetBtn = document.getElementById('reset-btn');
            const downloadBtn = document.getElementById('download-btn');

            let currentFile = null;
            let currentImageObject = new Image();

            // Utility to format file sizes
            const formatBytes = (bytes, decimals = 2) => {
                if (bytes === 0) return '0 Bytes';
                const k = 1024;
                const dm = decimals < 0 ? 0 : decimals;
                const sizes = ['Bytes', 'KB', 'MB', 'GB'];
                const i = Math.floor(Math.log(bytes) / Math.log(k));
                return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
            };

            // Event Listeners for Upload
            uploadArea.addEventListener('click', () => fileInput.click());

            uploadArea.addEventListener('dragover', (e) => {
                e.preventDefault();
                uploadArea.classList.add('drag-active');
            });

            uploadArea.addEventListener('dragleave', () => {
                uploadArea.classList.remove('drag-active');
            });

            uploadArea.addEventListener('drop', (e) => {
                e.preventDefault();
                uploadArea.classList.remove('drag-active');
                if (e.dataTransfer.files.length) {
                    handleFile(e.dataTransfer.files[0]);
                }
            });

            fileInput.addEventListener('change', (e) => {
                if (e.target.files.length) {
                    handleFile(e.target.files[0]);
                }
            });

            // Process the uploaded file
            function handleFile(file) {
                if (!file.type.startsWith('image/')) {
                    alert('Please upload an image file (JPEG, PNG, WebP).');
                    return;
                }

                currentFile = file;
                originalSizeTxt.textContent = formatBytes(file.size);

                const reader = new FileReader();
                reader.onload = (e) => {
                    currentImageObject.src = e.target.result;
                    currentImageObject.onload = () => {
                        originalImg.src = currentImageObject.src;
                        uploadArea.classList.add('hidden');
                        workspace.classList.remove('hidden');
                        compressImage(); // Initial compression run
                    };
                };
                reader.readAsDataURL(file);
            }

            // Core Compression Logic
            function compressImage() {
                if (!currentImageObject.src) return;

                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                
                // Keep original dimensions
                canvas.width = currentImageObject.width;
                canvas.height = currentImageObject.height;
                
                // Draw image on canvas
                ctx.drawImage(currentImageObject, 0, 0, canvas.width, canvas.height);

                // Get settings
                const quality = parseFloat(qualitySlider.value);
                const format = formatSelect.value;

                // Compress
                const compressedDataUrl = canvas.toDataURL(format, quality);
                compressedImg.src = compressedDataUrl;

                // Calculate rough size from base64 string
                const base64Length = compressedDataUrl.length - (compressedDataUrl.indexOf(',') + 1);
                const sizeInBytes = Math.ceil((base64Length * 3) / 4);
                compressedSizeTxt.textContent = formatBytes(sizeInBytes);

                // Setup Download
                const ext = format.split('/')[1];
                const originalName = currentFile.name.split('.')[0];
                downloadBtn.href = compressedDataUrl;
                downloadBtn.download = `${originalName}-compressed.${ext}`;
            }

            // Controls Listeners
            qualitySlider.addEventListener('input', (e) => {
                qualityValue.textContent = `${Math.round(e.target.value * 100)}%`;
                compressImage();
            });

            formatSelect.addEventListener('change', () => {
                // PNG compression via canvas is lossless, slider won't affect it much.
                if(formatSelect.value === 'image/png') {
                    qualitySlider.disabled = true;
                    qualitySlider.classList.add('opacity-50');
                    qualityValue.textContent = 'N/A (Lossless)';
                } else {
                    qualitySlider.disabled = false;
                    qualitySlider.classList.remove('opacity-50');
                    qualityValue.textContent = `${Math.round(qualitySlider.value * 100)}%`;
                }
                compressImage();
            });

            // Reset Logic
            resetBtn.addEventListener('click', () => {
                currentFile = null;
                fileInput.value = '';
                uploadArea.classList.remove('hidden');
                workspace.classList.add('hidden');
            });
        });
    </script>
</body>
</html>
Scroll to Top