* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #138808 0%, #0a5505 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 900px;
width: 100%;
padding: 40px;
}
h1 {
color: #138808;
font-size: 28px;
margin-bottom: 30px;
text-align: center;
font-weight: 600;
}
.upload-area {
border: 2px dashed #138808;
border-radius: 8px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 20px;
}
.upload-area:hover {
background: rgba(19, 136, 8, 0.05);
border-color: #0a5505;
}
.upload-area.hidden {
display: none;
}
.upload-icon {
font-size: 48px;
color: #138808;
margin-bottom: 10px;
}
.upload-text {
color: #666;
font-size: 16px;
}
input[type="file"] {
display: none;
}
.crop-container {
display: none;
margin-bottom: 30px;
}
.crop-container.active {
display: block;
}
.canvas-wrapper {
position: relative;
max-width: 100%;
margin: 0 auto 20px;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
background: #f5f5f5;
}
canvas {
display: block;
max-width: 100%;
height: auto;
}
.crop-overlay {
position: absolute;
border: 2px solid #138808;
cursor: move;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
}
.resize-handle {
position: absolute;
width: 10px;
height: 10px;
background: white;
border: 2px solid #138808;
border-radius: 50%;
}
.resize-handle.nw { top: -5px; left: -5px; cursor: nw-resize; }
.resize-handle.ne { top: -5px; right: -5px; cursor: ne-resize; }
.resize-handle.sw { bottom: -5px; left: -5px; cursor: sw-resize; }
.resize-handle.se { bottom: -5px; right: -5px; cursor: se-resize; }
.controls {
display: flex;
gap: 15px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.control-group {
flex: 1;
min-width: 200px;
}
label {
display: block;
color: #333;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
}
input[type="number"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s;
}
input[type="number"]:focus {
outline: none;
border-color: #138808;
}
.button-group {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
button {
padding: 12px 30px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-primary {
background: linear-gradient(135deg, #138808 0%, #0a5505 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(19, 136, 8, 0.3);
}
.btn-secondary {
background: #f5f5f5;
color: #666;
}
.btn-secondary:hover {
background: #e0e0e0;
}
.preview {
display: none;
text-align: center;
}
.preview.active {
display: block;
}
.preview h3 {
color: #333;
margin-bottom: 15px;
font-size: 18px;
}
.preview-image {
max-width: 100%;
border-radius: 8px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.info {
background: #f5f5f5;
padding: 15px;
border-radius: 6px;
margin-top: 20px;
font-size: 14px;
color: #666;
}
const fileInput = document.getElementById('fileInput');
const uploadArea = document.getElementById('uploadArea');
const cropContainer = document.getElementById('cropContainer');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const canvasWrapper = document.getElementById('canvasWrapper');
const preview = document.getElementById('preview');
const previewImage = document.getElementById('previewImage');
const widthInput = document.getElementById('widthInput');
const heightInput = document.getElementById('heightInput');
const qualityInput = document.getElementById('qualityInput');
const processBtn = document.getElementById('processBtn');
const resetBtn = document.getElementById('resetBtn');
const downloadBtn = document.getElementById('downloadBtn');
const newImageBtn = document.getElementById('newImageBtn');
const imageInfo = document.getElementById('imageInfo');
let img = new Image();
let cropArea = { x: 0, y: 0, width: 0, height: 0 };
let isDragging = false;
let isResizing = false;
let resizeHandle = null;
let startX = 0;
let startY = 0;
let processedBlob = null;
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.style.background = 'rgba(19, 136, 8, 0.1)';
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.style.background = '';
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.style.background = '';
const file = e.dataTransfer.files[0];
if (file && file.type.startsWith('image/')) {
loadImage(file);
}
});
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) loadImage(file);
});
function loadImage(file) {
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result;
img.onload = () => {
uploadArea.classList.add('hidden');
cropContainer.classList.add('active');
initCanvas();
};
};
reader.readAsDataURL(file);
}
function initCanvas() {
const maxWidth = canvasWrapper.clientWidth;
const scale = Math.min(maxWidth / img.width, 1);
canvas.width = img.width * scale;
canvas.height = img.height * scale;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
cropArea = {
x: canvas.width * 0.1,
y: canvas.height * 0.1,
width: canvas.width * 0.8,
height: canvas.height * 0.8
};
widthInput.value = Math.round(cropArea.width / scale);
heightInput.value = Math.round(cropArea.height / scale);
drawCropOverlay();
}
function drawCropOverlay() {
let overlay = document.querySelector('.crop-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.className = 'crop-overlay';
canvasWrapper.appendChild(overlay);
['nw', 'ne', 'sw', 'se'].forEach(pos => {
const handle = document.createElement('div');
handle.className = `resize-handle ${pos}`;
handle.dataset.position = pos;
overlay.appendChild(handle);
});
addEventListeners(overlay);
}
overlay.style.left = cropArea.x + 'px';
overlay.style.top = cropArea.y + 'px';
overlay.style.width = cropArea.width + 'px';
overlay.style.height = cropArea.height + 'px';
}
function addEventListeners(overlay) {
overlay.addEventListener('mousedown', startDrag);
overlay.querySelectorAll('.resize-handle').forEach(handle => {
handle.addEventListener('mousedown', startResize);
});
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
}
function startDrag(e) {
if (e.target.classList.contains('resize-handle')) return;
isDragging = true;
startX = e.clientX - cropArea.x;
startY = e.clientY - cropArea.y;
}
function startResize(e) {
e.stopPropagation();
isResizing = true;
resizeHandle = e.target.dataset.position;
startX = e.clientX;
startY = e.clientY;
}
function drag(e) {
if (isDragging) {
cropArea.x = Math.max(0, Math.min(e.clientX - startX, canvas.width - cropArea.width));
cropArea.y = Math.max(0, Math.min(e.clientY - startY, canvas.height - cropArea.height));
drawCropOverlay();
} else if (isResizing) {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newArea = { ...cropArea };
if (resizeHandle.includes('e')) {
newArea.width = Math.max(50, Math.min(cropArea.width + dx, canvas.width - cropArea.x));
}
if (resizeHandle.includes('w')) {
const newWidth = Math.max(50, cropArea.width - dx);
const newX = Math.max(0, cropArea.x + (cropArea.width - newWidth));
newArea.width = newWidth;
newArea.x = newX;
}
if (resizeHandle.includes('s')) {
newArea.height = Math.max(50, Math.min(cropArea.height + dy, canvas.height - cropArea.y));
}
if (resizeHandle.includes('n')) {
const newHeight = Math.max(50, cropArea.height - dy);
const newY = Math.max(0, cropArea.y + (cropArea.height - newHeight));
newArea.height = newHeight;
newArea.y = newY;
}
cropArea = newArea;
startX = e.clientX;
startY = e.clientY;
drawCropOverlay();
}
}
function stopDrag() {
isDragging = false;
isResizing = false;
resizeHandle = null;
}
processBtn.addEventListener('click', processImage);
function processImage() {
const scale = img.width / canvas.width;
const targetWidth = parseInt(widthInput.value);
const targetHeight = parseInt(heightInput.value);
const quality = parseInt(qualityInput.value) / 100;
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = cropArea.width * scale;
tempCanvas.height = cropArea.height * scale;
tempCtx.drawImage(
img,
cropArea.x * scale,
cropArea.y * scale,
tempCanvas.width,
tempCanvas.height,
0,
0,
tempCanvas.width,
tempCanvas.height
);
const finalCanvas = document.createElement('canvas');
const finalCtx = finalCanvas.getContext('2d');
finalCanvas.width = targetWidth;
finalCanvas.height = targetHeight;
finalCtx.drawImage(tempCanvas, 0, 0, targetWidth, targetHeight);
finalCanvas.toBlob((blob) => {
processedBlob = blob;
const url = URL.createObjectURL(blob);
previewImage.src = url;
const sizeKB = (blob.size / 1024).toFixed(2);
imageInfo.innerHTML = `
<strong>Dimensions:</strong> ${targetWidth} × ${targetHeight}px<br>
<strong>File Size:</strong> ${sizeKB} KB<br>
<strong>Quality:</strong> ${Math.round(quality * 100)}%
`;
cropContainer.classList.remove('active');
preview.classList.add('active');
}, 'image/jpeg', quality);
}
downloadBtn.addEventListener('click', () => {
if (processedBlob) {
const url = URL.createObjectURL(processedBlob);
const a = document.createElement('a');
a.href = url;
a.download = `processed-image-${Date.now()}.jpg`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 100);
}
});
resetBtn.addEventListener('click', reset);
newImageBtn.addEventListener('click', reset);
function reset() {
uploadArea.classList.remove('hidden');
cropContainer.classList.remove('active');
preview.classList.remove('active');
fileInput.value = '';
const overlay = document.querySelector('.crop-overlay');
if (overlay) overlay.remove();
}
No comments yet. Be the first!