PixelPerfect Pro – Ultimate Image Resizer
/* — CSS SEPARATION START — */
@import url(‘https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap’);:root {
–primary: #DB2777; /* Pink-600 */
–bg-dark: #111827;
}body {
font-family: ‘Inter’, sans-serif;
transition: background-color 0.3s, color 0.3s;
}/* Toggle Switch */
.toggle-checkbox:checked {
right: 0;
border-color: #DB2777;
}
.toggle-checkbox:checked + .toggle-label {
background-color: #DB2777;
}/* Tab Styles */
.tab-btn {
position: relative;
}
.tab-btn.active {
color: #DB2777;
background-color: rgba(219, 39, 119, 0.05);
border-color: #DB2777;
}/* Loading Spinner */
.loader {
border: 3px solid #f3f3f3;
border-radius: 50%;
border-top: 3px solid #DB2777;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}/* Dark Mode */
.dark body {
background-color: var(–bg-dark);
color: #F9FAFB;
}
.dark .card {
background-color: #1F2937;
border-color: #374151;
}
.dark input, .dark select {
background-color: #374151;
color: white;
border-color: #4B5563;
}
/* — CSS SEPARATION END — */
tailwind.config = {
darkMode: ‘class’,
theme: {
extend: {
colors: {
primary: ‘#DB2777’,
}
}
}
}
Change dimensions precisely or compress to a specific file size (KB). All processing happens securely in your browser.
By Dimensions
By File Size (KB)
Resize Options
Reset
Lock Aspect Ratio
Or Resize by Percentage
25%
50%
75%
We will adjust quality and dimensions to try and meet your target size.
Save As Format
Keep Original Format
JPG (Best for compression)
PNG (Best for transparency)
WEBP (Modern web format)
Resize Images
Drop images here Supports JPG, PNG, WEBP, GIF
Browse Files
Processed Images 0
Clear All
// — JAVASCRIPT SEPARATION START —// State
let currentMode = ‘dimensions’; // ‘dimensions’ or ‘filesize’
let selectedFiles = [];
let isDarkMode = false;
let activeRatio = 0; // Current aspect ratio of the FIRST image (used for Dimension locking)// DOM Elements
const fileInput = document.getElementById(‘fileInput’);
const dropzone = document.getElementById(‘dropzone’);
const inpWidth = document.getElementById(‘inp-width’);
const inpHeight = document.getElementById(‘inp-height’);
const checkRatio = document.getElementById(‘check-ratio’);
const inpKb = document.getElementById(‘inp-kb’);
const outFormat = document.getElementById(‘out-format’);
const btnProcess = document.getElementById(‘btn-process’);
const btnText = document.getElementById(‘btn-text’);
const btnLoader = document.getElementById(‘btn-loader’);
const resultsArea = document.getElementById(‘results-area’);
const resultsList = document.getElementById(‘results-list’);
const countBadge = document.getElementById(‘count-badge’);
const canvas = document.getElementById(‘process-canvas’);
const ctx = canvas.getContext(‘2d’);// — Event Listeners —// Drag & Drop
dropzone.addEventListener(‘click’, () => fileInput.click());
dropzone.addEventListener(‘dragover’, (e) => {
e.preventDefault();
dropzone.classList.add(‘border-pink-500’, ‘bg-pink-50’, ‘dark:bg-pink-900/10’);
});
dropzone.addEventListener(‘dragleave’, () => {
dropzone.classList.remove(‘border-pink-500’, ‘bg-pink-50’, ‘dark:bg-pink-900/10’);
});
dropzone.addEventListener(‘drop’, (e) => {
e.preventDefault();
dropzone.classList.remove(‘border-pink-500’, ‘bg-pink-50’, ‘dark:bg-pink-900/10’);
if(e.dataTransfer.files.length) handleFiles(e.dataTransfer.files);
});
fileInput.addEventListener(‘change’, (e) => {
if(e.target.files.length) handleFiles(e.target.files);
});// Dimension Inputs Logic (Aspect Ratio Lock)
inpWidth.addEventListener(‘input’, () => {
if (checkRatio.checked && activeRatio && inpWidth.value) {
inpHeight.value = Math.round(inpWidth.value / activeRatio);
}
});
inpHeight.addEventListener(‘input’, () => {
if (checkRatio.checked && activeRatio && inpHeight.value) {
inpWidth.value = Math.round(inpHeight.value * activeRatio);
}
});// — Functions —function switchMode(mode) {
currentMode = mode;
// Update Tabs
document.querySelectorAll(‘.tab-btn’).forEach(btn => {
btn.classList.remove(‘active’, ‘border-b-2’);
btn.classList.add(‘border-transparent’);
});
const activeBtn = document.getElementById(`tab-${mode}`);
activeBtn.classList.add(‘active’, ‘border-b-2’);
activeBtn.classList.remove(‘border-transparent’);// Update Settings Panel
document.getElementById(‘settings-dimensions’).classList.add(‘hidden’);
document.getElementById(‘settings-filesize’).classList.add(‘hidden’);
document.getElementById(`settings-${mode}`).classList.remove(‘hidden’);// Update Button Text
btnText.innerText = mode === ‘dimensions’ ? ‘Resize Images’ : ‘Compress to Size’;
}function handleFiles(files) {
// Add new files to array
const newFiles = Array.from(files).filter(f => f.type.startsWith(‘image/’));
selectedFiles = […selectedFiles, …newFiles];
if (selectedFiles.length > 0) {
// If this is the first batch, load the first image to determine Aspect Ratio for the UI inputs
const img = new Image();
img.onload = () => {
activeRatio = img.width / img.height;
// Auto-fill width/height inputs if empty
if (!inpWidth.value && !inpHeight.value) {
inpWidth.value = img.width;
inpHeight.value = img.height;
}
};
img.src = URL.createObjectURL(selectedFiles[0]);
// Show results area (initially list pending files or just count?)
// For simplicity, we process on click. But we could list them.
btnProcess.disabled = false;
btnText.innerText = `Process ${selectedFiles.length} Images`;
}
}async function processImages() {
if (selectedFiles.length === 0) return;btnProcess.disabled = true;
btnLoader.classList.remove(‘hidden’);
resultsArea.classList.remove(‘hidden’);
resultsList.innerHTML = ”; // Clear previous resultslet processedCount = 0;for (const file of selectedFiles) {
try {
const result = await processSingleImage(file);
addResultToUI(result);
processedCount++;
countBadge.innerText = processedCount;
} catch (err) {
console.error(“Error processing file”, file.name, err);
}
}// Reset
btnProcess.disabled = false;
btnLoader.classList.add(‘hidden’);
btnText.innerText = `Process ${selectedFiles.length} Images`;
}function processSingleImage(file) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
// 1. Determine new Dimensions
let w = img.width;
let h = img.height;if (currentMode === ‘dimensions’) {
const targetW = parseInt(inpWidth.value);
const targetH = parseInt(inpHeight.value);
// Simple logic: if specific W/H provided, use them.
// If one is missing and ratio locked, calc it.
// If both missing, keep original.
if (targetW && targetH) {
w = targetW;
h = targetH;
} else if (targetW) {
w = targetW;
if (checkRatio.checked) h = w / (img.width / img.height);
} else if (targetH) {
h = targetH;
if (checkRatio.checked) w = h * (img.width / img.height);
}
}// 2. Draw to Canvas
canvas.width = w;
canvas.height = h;
ctx.clearRect(0, 0, w, h);
// Handle PNG transparency background (make white if converting to JPG)
const format = outFormat.value === ‘original’ ? file.type : outFormat.value;
if (format === ‘image/jpeg’) {
ctx.fillStyle = ‘#FFFFFF’;
ctx.fillRect(0, 0, w, h);
}
ctx.drawImage(img, 0, 0, w, h);// 3. Export Logic (Dimensions vs File Size)
if (currentMode === ‘filesize’) {
compressToTargetSize(canvas, format, parseInt(inpKb.value) * 1024)
.then(blob => resolve({ original: file, blob: blob, w, h }));
} else {
// Standard Resize
canvas.toBlob(blob => {
resolve({ original: file, blob: blob, w, h });
}, format, 0.9);
}
};
img.onerror = reject;
img.src = URL.createObjectURL(file);
});
}// Recursive binary-ish search for quality to hit target KB
// Note: Canvas doesn’t support target size natively, so we iterate quality.
async function compressToTargetSize(canvas, format, targetBytes) {
let minQ = 0;
let maxQ = 1;
let quality = 0.9;
let blob = null;
let iterations = 0;// Formats like PNG don’t strictly support quality in toBlob the same way JPG/WEBP do,
// but we try anyway. If format is PNG, we might have to fallback or accept we can’t compress much.
if (format === ‘image/png’) {
// PNG compression in browser is limited. Return standard blob.
return new Promise(r => canvas.toBlob(r, format));
}while (iterations canvas.toBlob(r, format, quality));
if (Math.abs(blob.size – targetBytes) targetBytes) {
maxQ = quality;
} else {
minQ = quality;
}
quality = (minQ + maxQ) / 2;
iterations++;
}
return blob;
}function addResultToUI(data) {
const url = URL.createObjectURL(data.blob);
const sizeDiff = data.original.size – data.blob.size;
const isReduced = sizeDiff > 0;
const sizePercent = ((sizeDiff / data.original.size) * 100).toFixed(0);
// Generate filename
let ext = ‘jpg’;
if (data.blob.type === ‘image/png’) ext = ‘png’;
if (data.blob.type === ‘image/webp’) ext = ‘webp’;
const name = `resized_${data.original.name.split(‘.’)[0]}.${ext}`;const item = document.createElement(‘div’);
item.className = ‘card bg-white dark:bg-gray-800 p-4 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 flex flex-col sm:flex-row gap-4 items-center’;
item.innerHTML = `
${name}
${data.w} x ${data.h}px
•
${formatBytes(data.blob.size)}
${isReduced ? `(-${sizePercent}%)` : ”}
Save
`;
resultsList.prepend(item);
}function setPercent(p) {
if (!selectedFiles.length) {
alert(‘Please upload an image first.’);
return;
}
// Load first image dims just to calc percent
const img = new Image();
img.onload = () => {
inpWidth.value = Math.round(img.width * (p/100));
inpHeight.value = Math.round(img.height * (p/100));
};
img.src = URL.createObjectURL(selectedFiles[0]);
}function formatBytes(bytes) {
if (bytes === 0) return ‘0 Bytes’;
const k = 1024;
const sizes = [‘Bytes’, ‘KB’, ‘MB’];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ‘ ‘ + sizes[i];
}function clearAll() {
selectedFiles = [];
resultsList.innerHTML = ”;
resultsArea.classList.add(‘hidden’);
fileInput.value = ”;
btnText.innerText = ‘Resize Images’;
inpWidth.value = ”;
inpHeight.value = ”;
countBadge.innerText = ‘0’;
}function resetSettings() {
inpWidth.value = ”;
inpHeight.value = ”;
checkRatio.checked = true;
}// — Dark Mode —
function toggleDarkMode() {
isDarkMode = !isDarkMode;
document.documentElement.classList.toggle(‘dark’);
const icon = document.getElementById(‘theme-icon’);
if(isDarkMode) {
icon.classList.remove(‘fa-moon’);
icon.classList.add(‘fa-sun’);
} else {
icon.classList.remove(‘fa-sun’);
icon.classList.add(‘fa-moon’);
}
}
// — JAVASCRIPT SEPARATION END —
Using Image Resizer The Image Resizer utility is designed for image and graphics tasks. Check the on-page controls for available formats, limits and options. Always review the output for accuracy before sharing or publishing it.
Practical tips Keep your original files or source information as a backup. Choose suitable options using the controls provided by this tool. Check the generated result and retry with different settings if needed. Explore related tools Image Compressor are available for related tasks.