Compress Image Ultimate
:root {
–ciu-primary: #8b5cf6;
–ciu-primary-dark: #7c3aed;
–ciu-primary-light: #a78bfa;
–ciu-secondary: #06b6d4;
–ciu-bg-dark: #0f172a;
–ciu-bg-card: #1e293b;
–ciu-bg-input: #334155;
–ciu-bg-hover: #475569;
–ciu-text-primary: #f8fafc;
–ciu-text-secondary: #cbd5e1;
–ciu-text-muted: #94a3b8;
–ciu-success: #10b981;
–ciu-warning: #f59e0b;
–ciu-error: #ef4444;
–ciu-info: #3b82f6;
–ciu-gradient-primary: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 50%, #06b6d4 100%);
–ciu-gradient-card: linear-gradient(145deg, #1e293b 0%, #0f172a 100%);
–ciu-gradient-button: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
–ciu-gradient-success: linear-gradient(135deg, #10b981 0%, #059669 100%);
–ciu-shadow-lg: 0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);
–ciu-shadow-glow: 0 0 40px rgba(139,92,246,.3);
–ciu-radius-sm: .375rem;
–ciu-radius-md: .5rem;
–ciu-radius-lg: .75rem;
–ciu-radius-xl: 1rem;
–ciu-radius-2xl: 1.5rem;
–ciu-transition-fast: 150ms ease;
–ciu-transition-normal: 250ms ease;
}
.ciu-container * { margin:0; padding:0; box-sizing:border-box; }
.ciu-container {
font-family: ‘Inter’, -apple-system, BlinkMacSystemFont, sans-serif;
background: var(–ciu-bg-dark);
color: var(–ciu-text-primary);
line-height: 1.6;
min-height: 100vh;
width: 100%;
overflow-x: hidden;
}
.ciu-container h1,.ciu-container h2,.ciu-container h3 { font-weight:700; line-height:1.2; }
.ciu-container h1 {
font-size: clamp(2rem,5vw,3.5rem);
background: var(–ciu-gradient-primary);
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
}
.ciu-container p { color: var(–ciu-text-secondary); }
.ciu-wrapper { max-width:1200px; margin:0 auto; padding:2rem; }
/* HEADER */
.ciu-header { text-align:center; margin-bottom:3rem; padding:2rem 0; }
.ciu-badge {
display:inline-flex; align-items:center; gap:.5rem;
background:rgba(139,92,246,.1); border:1px solid rgba(139,92,246,.3);
padding:.5rem 1rem; border-radius:9999px; font-size:.875rem;
color:var(–ciu-primary-light); margin-bottom:1.5rem;
animation: ciu-pulse 2s infinite;
}
@keyframes ciu-pulse { 0%,100%{opacity:1} 50%{opacity:.7} }
.ciu-header h1 { margin-bottom:1rem; }
.ciu-header-subtitle { font-size:1.125rem; max-width:600px; margin:0 auto; color:var(–ciu-text-muted); }
.ciu-formats { display:flex; justify-content:center; gap:1rem; margin-top:2rem; flex-wrap:wrap; }
.ciu-format-item {
display:flex; align-items:center; gap:.25rem;
background:var(–ciu-bg-card); padding:.5rem 1rem; border-radius:var(–ciu-radius-md);
color:var(–ciu-text-muted); font-size:.75rem; font-weight:600; text-transform:uppercase;
border:1px solid rgba(255,255,255,.1); transition:var(–ciu-transition-normal);
}
.ciu-format-item:hover { border-color:var(–ciu-primary); color:var(–ciu-primary-light); transform:translateY(-2px); }
/* MAIN CARD */
.ciu-main-card {
background:var(–ciu-gradient-card); border:1px solid rgba(255,255,255,.1);
border-radius:var(–ciu-radius-2xl); padding:3rem;
box-shadow:var(–ciu-shadow-lg),var(–ciu-shadow-glow); margin-bottom:3rem;
}
/* MODE TABS */
.ciu-mode-tabs {
display:flex; gap:.5rem; margin:2rem 0;
background:var(–ciu-bg-input); padding:.5rem; border-radius:var(–ciu-radius-lg);
}
.ciu-mode-tab {
flex:1; padding:1rem 1.5rem; border:none; background:transparent;
color:var(–ciu-text-muted); font-weight:500; border-radius:var(–ciu-radius-md);
cursor:pointer; transition:var(–ciu-transition-fast); font-size:.875rem; font-family:inherit;
}
.ciu-mode-tab:hover { color:var(–ciu-text-primary); }
.ciu-mode-tab.ciu-active { background:var(–ciu-gradient-button); color:#fff; box-shadow:0 4px 6px -1px rgba(0,0,0,.1); }
/* UPLOAD ZONE */
.ciu-upload-zone {
border:2px dashed rgba(139,92,246,.4); border-radius:var(–ciu-radius-xl);
padding:3rem; text-align:center; background:rgba(139,92,246,.05);
transition:var(–ciu-transition-normal); cursor:pointer; position:relative; overflow:hidden;
}
.ciu-upload-zone::before {
content:”; position:absolute; top:0; left:-100%; width:100%; height:100%;
background:linear-gradient(90deg,transparent,rgba(139,92,246,.1),transparent);
transition:left .5s;
}
.ciu-upload-zone:hover::before { left:100%; }
.ciu-upload-zone:hover, .ciu-upload-zone.ciu-drag-over {
border-color:var(–ciu-primary); background:rgba(139,92,246,.1); transform:scale(1.01);
}
.ciu-upload-zone.ciu-drag-over { border-color:var(–ciu-success); background:rgba(16,185,129,.1); }
.ciu-upload-icon { font-size:4rem; color:var(–ciu-primary); margin-bottom:1rem; animation:ciu-float 3s ease-in-out infinite; }
@keyframes ciu-float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-10px)} }
.ciu-upload-text { font-size:1.25rem; font-weight:600; margin-bottom:.5rem; color:var(–ciu-text-primary); }
.ciu-upload-subtext { color:var(–ciu-text-muted); font-size:.875rem; }
.ciu-file-input { display:none; }
/* QUALITY */
.ciu-quality-section { margin:2rem 0; padding:1.5rem; background:var(–ciu-bg-input); border-radius:var(–ciu-radius-lg); }
.ciu-section-label { display:flex; justify-content:space-between; align-items:center; font-weight:600; margin-bottom:1rem; color:var(–ciu-text-primary); }
.ciu-quality-value { background:var(–ciu-gradient-primary); color:#fff; padding:.25rem 1rem; border-radius:var(–ciu-radius-md); font-size:.875rem; font-weight:700; }
.ciu-quality-slider {
width:100%; height:8px; border-radius:9999px; background:var(–ciu-bg-dark);
outline:none; -webkit-appearance:none; margin-bottom:1rem; cursor:pointer;
}
.ciu-quality-slider::-webkit-slider-thumb {
-webkit-appearance:none; width:24px; height:24px; border-radius:50%;
background:var(–ciu-gradient-button); cursor:pointer;
box-shadow:0 0 10px rgba(139,92,246,.5); transition:var(–ciu-transition-fast);
}
.ciu-quality-slider::-webkit-slider-thumb:hover { transform:scale(1.2); }
.ciu-quality-slider::-moz-range-thumb {
width:24px; height:24px; border-radius:50%; background:var(–ciu-gradient-button);
cursor:pointer; border:none; box-shadow:0 0 10px rgba(139,92,246,.5);
}
.ciu-quality-labels { display:flex; justify-content:space-between; font-size:.75rem; color:var(–ciu-text-muted); }
/* OUTPUT FORMAT */
.ciu-output-section { margin:2rem 0; }
.ciu-section-title { display:block; font-weight:600; margin-bottom:1rem; color:var(–ciu-text-primary); }
.ciu-output-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(100px,1fr)); gap:1rem; }
.ciu-output-btn {
background:var(–ciu-bg-input); border:2px solid transparent; color:var(–ciu-text-secondary);
padding:1rem .5rem; border-radius:var(–ciu-radius-md); cursor:pointer; font-weight:500;
transition:var(–ciu-transition-fast); font-size:.875rem; display:flex; flex-direction:column;
align-items:center; gap:.25rem; font-family:inherit;
}
.ciu-output-btn i { font-size:1.25rem; margin-bottom:.25rem; }
.ciu-output-btn:hover { border-color:var(–ciu-primary-light); color:var(–ciu-text-primary); }
.ciu-output-btn.ciu-selected { background:var(–ciu-gradient-button); border-color:var(–ciu-primary); color:#fff; }
/* TARGET SIZE */
.ciu-target-section { margin:2rem 0; }
.ciu-target-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(100px,1fr)); gap:1rem; }
.ciu-target-btn {
background:var(–ciu-bg-input); border:2px solid transparent; color:var(–ciu-text-secondary);
padding:1rem; border-radius:var(–ciu-radius-md); cursor:pointer; font-weight:500;
transition:var(–ciu-transition-fast); font-size:.875rem; font-family:inherit;
}
.ciu-target-btn:hover { border-color:var(–ciu-primary-light); color:var(–ciu-text-primary); }
.ciu-target-btn.ciu-selected { background:var(–ciu-gradient-button); border-color:var(–ciu-primary); color:#fff; }
.ciu-target-custom { grid-column:span 2; display:flex; gap:.5rem; }
.ciu-target-custom input {
flex:1; background:var(–ciu-bg-input); border:2px solid transparent;
color:var(–ciu-text-primary); padding:1rem; border-radius:var(–ciu-radius-md);
font-size:.875rem; font-family:inherit;
}
.ciu-target-custom input:focus { outline:none; border-color:var(–ciu-primary); }
/* FILE LIST */
.ciu-file-list { margin-top:2rem; }
.ciu-file-item {
display:flex; align-items:center; gap:1rem; background:var(–ciu-bg-input);
padding:1rem; border-radius:var(–ciu-radius-lg); margin-bottom:1rem;
animation:ciu-slideIn .3s ease; position:relative; overflow:hidden;
}
@keyframes ciu-slideIn { from{opacity:0;transform:translateX(-20px)} to{opacity:1;transform:translateX(0)} }
.ciu-file-preview {
width:80px; height:80px; border-radius:var(–ciu-radius-md); object-fit:cover;
border:2px solid rgba(255,255,255,.1); background:var(–ciu-bg-dark); cursor:pointer; flex-shrink:0;
}
.ciu-file-icon {
width:80px; height:80px; background:rgba(139,92,246,.2); border-radius:var(–ciu-radius-md);
display:flex; align-items:center; justify-content:center; color:var(–ciu-primary); font-size:2rem; flex-shrink:0;
}
.ciu-file-info { flex:1; min-width:0; }
.ciu-file-name { font-weight:500; color:var(–ciu-text-primary); margin-bottom:.25rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ciu-file-meta { display:flex; gap:1rem; font-size:.75rem; color:var(–ciu-text-muted); flex-wrap:wrap; }
.ciu-file-sizes { display:flex; align-items:center; gap:.5rem; margin-top:.5rem; flex-wrap:wrap; }
.ciu-size-original { color:var(–ciu-text-muted); text-decoration:line-through; }
.ciu-size-arrow { color:var(–ciu-primary-light); }
.ciu-size-compressed { color:var(–ciu-success); font-weight:600; }
.ciu-size-savings { background:var(–ciu-success); color:#fff; padding:.25rem .5rem; border-radius:var(–ciu-radius-sm); font-size:.75rem; font-weight:600; }
.ciu-file-status { display:flex; align-items:center; gap:.5rem; flex-shrink:0; flex-direction:column; }
.ciu-status-badge { padding:.25rem .5rem; border-radius:var(–ciu-radius-sm); font-size:.75rem; font-weight:500; white-space:nowrap; }
.ciu-status-pending { background:rgba(245,158,11,.2); color:var(–ciu-warning); }
.ciu-status-processing { background:rgba(59,130,246,.2); color:var(–ciu-info); }
.ciu-status-completed { background:rgba(16,185,129,.2); color:var(–ciu-success); }
.ciu-status-error { background:rgba(239,68,68,.2); color:var(–ciu-error); }
.ciu-file-remove {
background:none; border:none; color:var(–ciu-text-muted); cursor:pointer;
padding:.5rem; border-radius:var(–ciu-radius-sm); transition:var(–ciu-transition-fast); flex-shrink:0;
}
.ciu-file-remove:hover { color:var(–ciu-error); background:rgba(239,68,68,.1); }
.ciu-file-download {
display:none; background:none; border:1px solid var(–ciu-success); color:var(–ciu-success);
cursor:pointer; padding:.3rem .7rem; border-radius:var(–ciu-radius-sm);
font-size:.7rem; font-weight:600; font-family:inherit; align-items:center; gap:.25rem;
transition:var(–ciu-transition-fast); white-space:nowrap;
}
.ciu-file-download.ciu-visible { display:flex; }
.ciu-file-download:hover { background:var(–ciu-success); color:#fff; }
.ciu-live-preview {
margin-top:.5rem; padding:.5rem; background:rgba(16,185,129,.1);
border-radius:var(–ciu-radius-md); border-left:3px solid var(–ciu-success);
}
.ciu-live-stats { display:flex; justify-content:space-between; align-items:center; font-size:.75rem; }
.ciu-live-label { color:var(–ciu-text-muted); }
.ciu-live-value { color:var(–ciu-success); font-weight:600; }
/* PROGRESS */
.ciu-progress-container { margin-top:1rem; }
.ciu-progress-bar { height:8px; background:var(–ciu-bg-input); border-radius:9999px; overflow:hidden; position:relative; }
.ciu-progress-fill {
height:100%; background:var(–ciu-gradient-primary); border-radius:9999px;
transition:width .3s ease; position:relative; overflow:hidden;
}
.ciu-progress-fill::after {
content:”; position:absolute; top:0; left:0; right:0; bottom:0;
background:linear-gradient(90deg,transparent,rgba(255,255,255,.3),transparent);
animation:ciu-shimmer 1.5s infinite;
}
@keyframes ciu-shimmer { 0%{transform:translateX(-100%)} 100%{transform:translateX(100%)} }
.ciu-progress-text { text-align:center; margin-top:.5rem; font-size:.875rem; color:var(–ciu-text-muted); }
.ciu-batch-progress { margin-top:1rem; padding:1rem; background:var(–ciu-bg-input); border-radius:var(–ciu-radius-md); }
.ciu-batch-header { display:flex; justify-content:space-between; margin-bottom:.5rem; font-size:.875rem; }
.ciu-batch-count { color:var(–ciu-text-muted); }
.ciu-batch-percentage { color:var(–ciu-primary-light); font-weight:600; }
/* ACTIONS */
.ciu-actions { display:flex; gap:1rem; margin-top:2rem; flex-wrap:wrap; }
.ciu-btn {
flex:1; min-width:200px; padding:1rem 2rem; border:none; border-radius:var(–ciu-radius-lg);
font-weight:600; font-size:1rem; cursor:pointer; transition:var(–ciu-transition-normal);
display:flex; align-items:center; justify-content:center; gap:.5rem; font-family:inherit;
}
.ciu-btn-primary { background:var(–ciu-gradient-button); color:#fff; box-shadow:0 4px 14px rgba(139,92,246,.4); }
.ciu-btn-primary:hover:not(:disabled) { transform:translateY(-2px); box-shadow:0 6px 20px rgba(139,92,246,.5); }
.ciu-btn-primary:disabled { opacity:.6; cursor:not-allowed; }
.ciu-btn-secondary { background:var(–ciu-bg-input); color:var(–ciu-text-primary); border:1px solid rgba(255,255,255,.1); }
.ciu-btn-secondary:hover { background:var(–ciu-bg-hover); }
.ciu-btn-success { background:var(–ciu-gradient-success); color:#fff; box-shadow:0 4px 14px rgba(16,185,129,.4); }
.ciu-btn-success:hover { transform:translateY(-2px); box-shadow:0 6px 20px rgba(16,185,129,.5); }
/* RESULTS */
.ciu-results {
margin-top:2rem; padding:2rem; background:rgba(16,185,129,.1);
border:1px solid rgba(16,185,129,.3); border-radius:var(–ciu-radius-xl); display:none;
}
.ciu-results.ciu-visible { display:block; animation:ciu-fadeIn .5s ease; }
@keyframes ciu-fadeIn { from{opacity:0;transform:translateY(10px)} to{opacity:1;transform:translateY(0)} }
.ciu-results-header { display:flex; align-items:center; gap:1rem; margin-bottom:1.5rem; }
.ciu-results-icon { width:56px; height:56px; background:var(–ciu-success); border-radius:50%; display:flex; align-items:center; justify-content:center; color:#fff; font-size:1.5rem; flex-shrink:0; }
.ciu-results-title { font-size:1.25rem; font-weight:700; color:var(–ciu-text-primary); }
.ciu-results-subtitle { color:var(–ciu-text-muted); font-size:.875rem; }
.ciu-stats-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:1rem; margin-bottom:1.5rem; }
.ciu-stat-card { background:var(–ciu-bg-card); padding:1.5rem; border-radius:var(–ciu-radius-lg); text-align:center; }
.ciu-stat-value { font-size:1.5rem; font-weight:700; color:var(–ciu-primary-light); margin-bottom:.25rem; }
.ciu-stat-label { font-size:.75rem; color:var(–ciu-text-muted); text-transform:uppercase; letter-spacing:.05em; }
.ciu-comparison { margin-top:1rem; display:grid; grid-template-columns:1fr 1fr; gap:1rem; }
.ciu-comparison-item { text-align:center; padding:1rem; background:var(–ciu-bg-card); border-radius:var(–ciu-radius-lg); }
.ciu-comparison-label { font-size:.75rem; color:var(–ciu-text-muted); margin-bottom:.5rem; text-transform:uppercase; }
.ciu-comparison-size { font-size:1.25rem; font-weight:700; color:var(–ciu-text-primary); }
.ciu-comparison-size.ciu-reduced { color:var(–ciu-success); }
/* FEATURES */
.ciu-features { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:2rem; margin-top:3rem; }
.ciu-feature-card {
background:var(–ciu-gradient-card); border:1px solid rgba(255,255,255,.1);
border-radius:var(–ciu-radius-xl); padding:2rem; transition:var(–ciu-transition-normal);
}
.ciu-feature-card:hover { transform:translateY(-4px); box-shadow:var(–ciu-shadow-lg); border-color:rgba(139,92,246,.3); }
.ciu-feature-icon {
width:56px; height:56px; background:rgba(139,92,246,.1); border-radius:var(–ciu-radius-lg);
display:flex; align-items:center; justify-content:center; color:var(–ciu-primary); font-size:1.5rem; margin-bottom:1.5rem;
}
.ciu-feature-title { font-size:1.125rem; font-weight:600; margin-bottom:.5rem; color:var(–ciu-text-primary); }
.ciu-feature-desc { color:var(–ciu-text-muted); font-size:.875rem; line-height:1.6; }
/* SECURITY */
.ciu-security-badge {
display:flex; align-items:center; justify-content:center; gap:1rem; margin-top:2rem;
padding:1.5rem; background:rgba(16,185,129,.1); border-radius:var(–ciu-radius-lg);
color:var(–ciu-success); font-size:.875rem;
}
/* TOAST */
.ciu-toast-container { position:fixed; bottom:2rem; right:2rem; z-index:9999; display:flex; flex-direction:column; gap:1rem; }
.ciu-toast {
background:var(–ciu-bg-card); border:1px solid rgba(255,255,255,.1); border-radius:var(–ciu-radius-lg);
padding:1rem 1.5rem; box-shadow:var(–ciu-shadow-lg); display:flex; align-items:center; gap:1rem;
min-width:300px; animation:ciu-toastIn .3s ease; transition:opacity .3s,transform .3s;
}
@keyframes ciu-toastIn { from{opacity:0;transform:translateX(100%)} to{opacity:1;transform:translateX(0)} }
.ciu-toast-success { border-left:4px solid var(–ciu-success); }
.ciu-toast-error { border-left:4px solid var(–ciu-error); }
.ciu-toast-warning { border-left:4px solid var(–ciu-warning); }
/* MODAL */
.ciu-modal {
display:none; position:fixed; top:0; left:0; width:100%; height:100%;
background:rgba(0,0,0,.9); z-index:10000; align-items:center; justify-content:center; padding:2rem;
}
.ciu-modal.ciu-visible { display:flex; }
.ciu-modal-content { max-width:90%; max-height:90%; border-radius:var(–ciu-radius-lg); box-shadow:var(–ciu-shadow-lg); }
.ciu-modal-close {
position:absolute; top:2rem; right:2rem; background:none; border:none; color:#fff;
font-size:2rem; cursor:pointer; width:48px; height:48px; display:flex; align-items:center;
justify-content:center; border-radius:50%; transition:var(–ciu-transition-fast);
}
.ciu-modal-close:hover { background:rgba(255,255,255,.1); }
.ciu-hidden { display:none !important; }
/* RESPONSIVE */
@media(max-width:768px){
.ciu-wrapper{padding:1rem}
.ciu-main-card{padding:1.5rem}
.ciu-target-grid{grid-template-columns:repeat(2,1fr)}
.ciu-target-custom{grid-column:span 2}
.ciu-output-grid{grid-template-columns:repeat(3,1fr)}
.ciu-actions{flex-direction:column}
.ciu-btn{width:100%}
.ciu-file-item{flex-wrap:wrap}
.ciu-file-preview,.ciu-file-icon{width:60px;height:60px}
.ciu-comparison{grid-template-columns:1fr}
.ciu-toast-container{right:1rem;bottom:1rem}
.ciu-toast{min-width:unset;max-width:calc(100vw – 2rem)}
}
@media(max-width:480px){
.ciu-upload-icon{font-size:3rem}
.ciu-upload-text{font-size:1rem}
.ciu-mode-tabs{flex-direction:column}
.ciu-file-sizes{flex-direction:column;align-items:flex-start;gap:.25rem}
.ciu-output-grid{grid-template-columns:repeat(2,1fr)}
}
Single Image
Bulk Images
Drag & Drop your images here
or click to browse • Max 25 MB each • Up to 50 images
Compression Quality
80%
Maximum Compression
Best Quality
Output Format
Original
JPG
PNG
WebP
Target File Size (Optional)
50 KB
100 KB
200 KB
500 KB
Auto
Apply
Compress Images
Download All
Clear All
100% Private: All image processing happens locally in your browser. Your images never leave your device.
Live Size Preview
See estimated compressed file sizes in real-time before processing. Adjust quality and instantly see the impact on file size.
Real Canvas Compression
Actual browser Canvas API compression — not a simulation. True JPG/PNG/WebP output with genuine quality reduction and format conversion.
Bulk Processing + ZIP
Process up to 50 images simultaneously. Download individually per file or all at once as a ZIP archive with one click.
Target Size Mode
Set a specific target size (e.g. 100 KB) and the engine iterates quality via binary search until your output hits the exact target.
×
(function () {
‘use strict’;
/* ═══════════ STATE ═══════════ */
const state = {
files: [],
mode: ‘single’,
quality: 80,
targetSize: 0, // KB, 0 = auto
outputFormat: ‘original’,
isProcessing: false,
processedFiles: [] // sparse array indexed same as state.files
};
/* ═══════════ DOM ═══════════ */
const $ = id => document.getElementById(id);
const el = {
uploadZone: $(‘ciuUploadZone’),
fileInput: $(‘ciuFileInput’),
fileList: $(‘ciuFileList’),
modeTabs: document.querySelectorAll(‘.ciu-mode-tab’),
qualitySlider: $(‘ciuQualitySlider’),
qualityValue: $(‘ciuQualityValue’),
outputBtns: document.querySelectorAll(‘.ciu-output-btn’),
targetBtns: document.querySelectorAll(‘.ciu-target-btn’),
customSizeIn: $(‘ciuCustomSize’),
customSizeBtn: $(‘ciuCustomSizeBtn’),
compressBtn: $(‘ciuCompressBtn’),
downloadBtn: $(‘ciuDownloadBtn’),
clearBtn: $(‘ciuClearBtn’),
progCont: $(‘ciuProgressContainer’),
progFill: $(‘ciuProgressFill’),
progText: $(‘ciuProgressText’),
batchProg: $(‘ciuBatchProgress’),
batchFill: $(‘ciuBatchProgressFill’),
batchCount: document.querySelector(‘.ciu-batch-count’),
batchPct: document.querySelector(‘.ciu-batch-percentage’),
results: $(‘ciuResults’),
statsGrid: $(‘ciuStatsGrid’),
origTotal: $(‘ciuOriginalTotal’),
compTotal: $(‘ciuCompressedTotal’),
toastCont: $(‘ciuToastContainer’),
modal: $(‘ciuModal’),
modalImg: $(‘ciuModalImage’),
modalClose: $(‘ciuModalClose’)
};
/* ═══════════ UTILITIES ═══════════ */
const fmtSize = b => {
if (!b || b {
const icons = { success:’fa-check-circle’, error:’fa-exclamation-circle’, warning:’fa-exclamation-triangle’ };
const t = document.createElement(‘div’);
t.className = `ciu-toast ciu-toast-${type}`;
t.innerHTML = `${msg} `;
el.toastCont.appendChild(t);
setTimeout(() => {
t.style.opacity = ‘0’; t.style.transform = ‘translateX(100%)’;
setTimeout(() => t.remove(), 350);
}, 3200);
};
/* ═══════════ IMAGE HELPERS ═══════════ */
// Load file → HTMLImageElement via FileReader (base64 data URL).
// Using FileReader avoids failures with non-ASCII filenames (Bengali, Arabic, CJK, etc.)
// because the image src never contains the filename — only raw base64 data.
// Also caches the data URL on the file object for thumbnail display.
const loadImg = file => new Promise((res, rej) => {
if (file._dataUrl) {
// Already read — reuse cached data URL
const img = new Image();
img.onload = () => res(img);
img.onerror = rej;
img.src = file._dataUrl;
return;
}
const reader = new FileReader();
reader.onload = e => {
file._dataUrl = e.target.result; // cache for thumbnails & previews
const img = new Image();
img.onload = () => res(img);
img.onerror = rej;
img.src = file._dataUrl;
};
reader.onerror = rej;
reader.readAsDataURL(file);
});
// Canvas compress → Blob
const canvasBlob = (img, mimeOut, q) => new Promise(resolve => {
const c = document.createElement(‘canvas’);
c.width = img.naturalWidth || img.width;
c.height = img.naturalHeight || img.height;
const ctx = c.getContext(‘2d’);
if (mimeOut === ‘image/jpeg’) { ctx.fillStyle = ‘#fff’; ctx.fillRect(0,0,c.width,c.height); }
ctx.drawImage(img, 0, 0);
c.toBlob(b => resolve(b), mimeOut, q / 100);
});
// Binary-search quality to hit targetKB
const compressToTarget = async (img, mimeOut, targetKB) => {
let lo = 1, hi = 95, best = null;
for (let n = 0; n < 14; n++) {
const mid = Math.round((lo + hi) / 2);
const blob = await canvasBlob(img, mimeOut, mid);
if (blob.size / 1024 hi) break;
}
return best || await canvasBlob(img, mimeOut, 1);
};
// Resolve output mime from format setting + file
const resolveMime = (file, fmt) => {
if (fmt === ‘png’) return ‘image/png’;
if (fmt === ‘webp’) return ‘image/webp’;
if (fmt === ‘jpeg’) return ‘image/jpeg’;
// “original” — keep png/webp, convert everything else to jpeg
if (file.type === ‘image/png’) return ‘image/png’;
if (file.type === ‘image/webp’) return ‘image/webp’;
return ‘image/jpeg’;
};
const mimeExt = { ‘image/jpeg’:’jpg’,’image/png’:’png’,’image/webp’:’webp’ };
const getExt = m => mimeExt[m] || ‘jpg’;
const origExt = f => (f.name.split(‘.’).pop() || ‘img’).toUpperCase();
/* ═══════════ LIVE ESTIMATE ═══════════ */
const estimate = (bytes, q, fmt) => {
const r = q / 100;
let f = r>=.9?.93:r>=.8?.72:r>=.7?.57:r>=.6?.43:r>=.5?.33:r>=.3?.22:.13;
if (fmt===’png’) f *= 1.25;
if (fmt===’webp’) f *= 0.75;
return Math.max(512, Math.floor(bytes * f));
};
/* ═══════════ FILE MANAGEMENT ═══════════ */
const handleFiles = async rawFiles => {
const arr = Array.from(rawFiles);
const valid = [];
for (const f of arr) {
if (!supportedMime.has(f.type) && !supportedExt.test(f.name)) { toast(`”${f.name}” not supported`,’error’); continue; }
if (f.size > 25*1024*1024) { toast(`”${f.name}” exceeds 25 MB`,’error’); continue; }
valid.push(f);
}
if (!valid.length) return;
if (state.mode === ‘single’) { state.files = [valid[0]]; }
else {
const slots = 50 – state.files.length;
if (valid.length > slots) toast(`Max 50 files — added first ${slots}`,’warning’);
state.files = […state.files, …valid.slice(0, slots)];
}
// Load dimensions in parallel (loadImg now uses FileReader data URL)
await Promise.all(state.files.map(async f => {
if (f._dims) return;
try {
const img = await loadImg(f);
f._dims = { w: img.naturalWidth, h: img.naturalHeight };
} catch { f._dims = { w:0, h:0 }; }
}));
toast(`${valid.length} image${valid.length>1?’s’:”} added`);
render(); updateBtn();
};
const removeFile = idx => {
state.files.splice(idx, 1);
state.processedFiles.splice(idx, 1);
render(); updateBtn();
toast(‘Image removed’,’warning’);
};
/* ═══════════ RENDER FILE LIST ═══════════ */
const render = () => {
if (!state.files.length) { el.fileList.classList.add(‘ciu-hidden’); return; }
el.fileList.classList.remove(‘ciu-hidden’);
el.fileList.innerHTML = state.files.map((f, i) => {
const isImg = f.type.startsWith(‘image/’);
const prevUrl = isImg ? (f._dataUrl || null) : null;
const pf = state.processedFiles[i];
const est = estimate(f.size, state.quality, state.outputFormat);
const savPct = Math.max(0, ((f.size-est)/f.size*100)).toFixed(0);
const dims = f._dims ? `${f._dims.w}×${f._dims.h}` : ‘…’;
return `
${isImg
? `
`
: `
`}
${f.name}
${dims}
${origExt(f)}
${fmtSize(f.size)}
${pf ? fmtSize(pf.blob.size) : ‘~’+fmtSize(est)}
${pf ? ‘-‘+((f.size-pf.blob.size)/f.size*100).toFixed(1)+’%’ : ‘-‘+savPct+’%’}
${!state.isProcessing && !pf ? `
Live estimate at ${state.quality}% quality
~${fmtSize(est)} expected
` : ”}
${pf ? ‘Done’ : ‘Pending’}
${pf ? ` Save ` : ”}
`;
}).join(”);
};
const updateEstimates = () => {
state.files.forEach((f, i) => {
if (state.processedFiles[i]) return; // already processed, don’t overwrite real values
const est = estimate(f.size, state.quality, state.outputFormat);
const sav = Math.max(0, ((f.size-est)/f.size*100)).toFixed(0);
const c = document.getElementById(`cs${i}`);
const s = document.getElementById(`sv${i}`);
const l = document.getElementById(`lv${i}`);
if (c) c.textContent = ‘~’+fmtSize(est);
if (s) s.textContent = ‘-‘+sav+’%’;
if (l) l.textContent = `~${fmtSize(est)} expected`;
});
};
const updateBtn = () => { el.compressBtn.disabled = !state.files.length || state.isProcessing; };
/* ═══════════ REAL COMPRESSION ENGINE ═══════════ */
const compressAll = async () => {
if (!state.files.length || state.isProcessing) return;
state.isProcessing = true;
state.processedFiles = [];
updateBtn();
el.progCont.classList.remove(‘ciu-hidden’);
el.batchProg.classList.remove(‘ciu-hidden’);
el.results.classList.remove(‘ciu-visible’);
el.downloadBtn.classList.add(‘ciu-hidden’);
const total = state.files.length;
for (let i = 0; i 0) {
blob = await compressToTarget(img, mimeOut, state.targetSize);
} else {
blob = await canvasBlob(img, mimeOut, state.quality);
}
}
const ext = file.type === ‘image/svg+xml’ ? ‘svg’ : getExt(mimeOut);
const base = file.name.replace(/\.[^/.]+$/, ”);
const outName = `compressed_${base}.${ext}`;
state.processedFiles[i] = { blob, name: outName, originalSize: file.size };
// Update the file row
if (stEl) { stEl.className=’ciu-status-badge ciu-status-completed’; stEl.textContent=’Done’; }
const csEl = document.getElementById(`cs${i}`);
const svEl = document.getElementById(`sv${i}`);
const actSav = ((file.size – blob.size) / file.size * 100).toFixed(1);
if (csEl) csEl.textContent = fmtSize(blob.size);
if (svEl) svEl.textContent = ‘-‘+actSav+’%’;
// Inject per-file download button
const row = el.fileList.querySelector(`[data-index=”${i}”]`);
if (row) {
const statusDiv = row.querySelector(‘.ciu-file-status’);
if (statusDiv && !statusDiv.querySelector(‘.ciu-file-download’)) {
const btn = document.createElement(‘button’);
btn.className = ‘ciu-file-download ciu-visible’;
btn.innerHTML = ‘ Save’;
btn.onclick = () => window._ciu.dlSingle(i);
statusDiv.appendChild(btn);
}
// Remove live estimate bar
const lp = row.querySelector(‘.ciu-live-preview’);
if (lp) lp.remove();
}
} catch (err) {
console.error(‘Compress error for’, file.name, err);
if (stEl) { stEl.className=’ciu-status-badge ciu-status-error’; stEl.textContent=’Error’; }
state.processedFiles[i] = null;
toast(`Failed: ${file.name}`, ‘error’);
}
// Yield for UI repaint
await new Promise(r => setTimeout(r, 20));
}
el.batchFill.style.width = ‘100%’;
el.progFill.style.width = ‘100%’;
el.progText.textContent = ‘Compression complete!’;
el.batchCount.textContent = `Processed ${total} of ${total}`;
el.batchPct.textContent = ‘100%’;
state.isProcessing = false;
updateBtn();
setTimeout(() => {
el.progCont.classList.add(‘ciu-hidden’);
el.batchProg.classList.add(‘ciu-hidden’);
showSummary();
el.downloadBtn.classList.remove(‘ciu-hidden’);
toast(‘All images compressed! 🎉’);
}, 400);
};
/* ═══════════ RESULTS SUMMARY ═══════════ */
const showSummary = () => {
const done = state.processedFiles.filter(Boolean);
if (!done.length) return;
const totOrig = done.reduce((s,f) => s+f.originalSize, 0);
const totComp = done.reduce((s,f) => s+f.blob.size, 0);
const saved = ((totOrig-totComp)/totOrig*100).toFixed(1);
el.statsGrid.innerHTML = `
${fmtSize(totOrig)}
Original
${fmtSize(totComp)}
Compressed
`;
el.origTotal.textContent = fmtSize(totOrig);
el.compTotal.textContent = fmtSize(totComp);
el.results.classList.add(‘ciu-visible’);
};
/* ═══════════ DOWNLOADS ═══════════ */
const triggerDl = (blob, name) => {
const url = URL.createObjectURL(blob);
const a = document.createElement(‘a’);
a.href = url; a.download = name;
document.body.appendChild(a); a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 1000);
};
const dlAll = async () => {
const done = state.processedFiles.filter(Boolean);
if (!done.length) { toast(‘Nothing compressed yet’,’warning’); return; }
if (done.length === 1) { triggerDl(done[0].blob, done[0].name); toast(‘Downloaded!’); return; }
toast(‘Building ZIP archive…’);
try {
if (typeof JSZip === ‘undefined’) throw new Error(‘JSZip not loaded’);
const zip = new JSZip();
done.forEach(f => zip.file(f.name, f.blob));
const zBlob = await zip.generateAsync({ type:’blob’, compression:’DEFLATE’, compressionOptions:{level:6} });
triggerDl(zBlob, ‘compressed_images.zip’);
toast(‘ZIP downloaded!’);
} catch (e) {
console.warn(‘ZIP failed, downloading individually:’, e);
done.forEach((f,i) => setTimeout(() => triggerDl(f.blob, f.name), i*350));
toast(‘Downloading files individually…’,’warning’);
}
};
const dlSingle = idx => {
const f = state.processedFiles[idx];
if (!f) { toast(‘Not processed yet’,’warning’); return; }
triggerDl(f.blob, f.name);
toast(`Downloading ${f.name}`);
};
/* ═══════════ CLEAR ═══════════ */
const clearAll = () => {
if (state.isProcessing) { toast(‘Processing in progress…’,’warning’); return; }
state.files = []; state.processedFiles = [];
render();
el.results.classList.remove(‘ciu-visible’);
el.downloadBtn.classList.add(‘ciu-hidden’);
el.progCont.classList.add(‘ciu-hidden’);
el.batchProg.classList.add(‘ciu-hidden’);
el.progFill.style.width = ‘0%’;
el.batchFill.style.width = ‘0%’;
updateBtn();
toast(‘Cleared’,’warning’);
};
/* ═══════════ MODAL ═══════════ */
const showPreview = idx => { el.modalImg.src = state.files[idx]._dataUrl || ”; el.modal.classList.add(‘ciu-visible’); };
const closeModal = () => { el.modal.classList.remove(‘ciu-visible’); el.modalImg.src = ”; };
/* ═══════════ EVENT WIRING ═══════════ */
// Upload
el.uploadZone.addEventListener(‘click’, () => el.fileInput.click());
el.uploadZone.addEventListener(‘dragover’, e => { e.preventDefault(); el.uploadZone.classList.add(‘ciu-drag-over’); });
el.uploadZone.addEventListener(‘dragleave’, () => el.uploadZone.classList.remove(‘ciu-drag-over’));
el.uploadZone.addEventListener(‘drop’, e => {
e.preventDefault(); el.uploadZone.classList.remove(‘ciu-drag-over’);
handleFiles(e.dataTransfer.files);
});
el.fileInput.addEventListener(‘change’, e => { handleFiles(e.target.files); e.target.value=”; });
// Mode tabs
el.modeTabs.forEach(tab => tab.addEventListener(‘click’, () => {
el.modeTabs.forEach(t => t.classList.remove(‘ciu-active’));
tab.classList.add(‘ciu-active’);
state.mode = tab.dataset.mode;
if (state.files.length) clearAll();
toast(`${state.mode===’single’?’Single’:’Bulk’} mode`);
}));
// Quality
el.qualitySlider.addEventListener(‘input’, e => {
state.quality = +e.target.value;
el.qualityValue.textContent = state.quality + ‘%’;
updateEstimates();
});
// Output format
el.outputBtns.forEach(btn => btn.addEventListener(‘click’, () => {
el.outputBtns.forEach(b => b.classList.remove(‘ciu-selected’));
btn.classList.add(‘ciu-selected’);
state.outputFormat = btn.dataset.format;
updateEstimates();
}));
// Target size presets
el.targetBtns.forEach(btn => {
if (btn.id === ‘ciuCustomSizeBtn’) return;
btn.addEventListener(‘click’, () => {
el.targetBtns.forEach(b => b.classList.remove(‘ciu-selected’));
btn.classList.add(‘ciu-selected’);
state.targetSize = parseInt(btn.dataset.size) || 0;
el.customSizeIn.value = ”;
updateEstimates();
});
});
// Custom size
el.customSizeBtn.addEventListener(‘click’, () => {
const v = parseInt(el.customSizeIn.value);
if (v > 0) {
el.targetBtns.forEach(b => b.classList.remove(‘ciu-selected’));
el.customSizeBtn.classList.add(‘ciu-selected’);
state.targetSize = v;
updateEstimates();
toast(`Target: ${v} KB`);
} else { toast(‘Enter a valid KB value’,’error’); }
});
el.customSizeIn.addEventListener(‘keydown’, e => { if (e.key===’Enter’) el.customSizeBtn.click(); });
// Main actions
el.compressBtn.addEventListener(‘click’, compressAll);
el.downloadBtn.addEventListener(‘click’, dlAll);
el.clearBtn.addEventListener(‘click’, clearAll);
// Modal
el.modalClose.addEventListener(‘click’, closeModal);
el.modal.addEventListener(‘click’, e => { if (e.target===el.modal) closeModal(); });
document.addEventListener(‘keydown’, e => { if (e.key===’Escape’) closeModal(); });
/* ═══════════ GLOBAL API (for inline onclick) ═══════════ */
window._ciu = { remove: removeFile, preview: showPreview, dlSingle };
console.log(‘Compress Image Ultimate v2.0 — Real compression engine ready’);
})();
My Name is Sourav Mukherjee. I am a Website Developer and SEO Expart. AllBestTool.com is a browser-based collection of free online utilities — everything from PDF converters and text tools to SEO helpers and calculators. The idea is pretty simple: make a bunch of useful web tools available in one place without requiring users to sign up.
Latest posts by allbesttool.com
(see all )