Перетащите файлы или нажмите для выбора
<div class="upload-area" id="dropArea">
<input type="file" id="imageUpload" accept="image/*" style="display: none;">
<div style="font-size: 24px; margin-bottom: 10px;">📁</div>
<h3 style="margin: 10px 0; color: #667eea;">Загрузите изображение</h3>
<p style="color: #666; margin-bottom: 20px;">Перетащите файлы или нажмите для выбора</p>
<button onclick="document.getElementById('imageUpload').click()" style="padding: 12px 24px; background: #667eea; color: white; border: none; border-radius: 8px; cursor: pointer;">
Выбрать файл
</button>
</div>
<div id="resizeControls" class="hidden">
<div class="controls-grid">
<!-- Основные размеры -->
<div class="control-group">
<h3 style="margin-top: 0; color: #667eea;">📐 Размеры</h3>
<div class="dimension-inputs">
<div>
<label for="widthInput">Ширина (px):</label>
<input type="number" id="widthInput" min="1" max="5000" value="300">
</div>
<div>
<label for="heightInput">Высота (px):</label>
<input type="number" id="heightInput" min="1" max="5000" value="200">
</div>
</div>
<label for="widthSlider">Ширина: <span id="widthValue">300</span> px</label>
<input type="range" id="widthSlider" min="50" max="2000" value="300">
<label for="heightSlider">Высота: <span id="heightValue">200</span> px</label>
<input type="range" id="heightSlider" min="50" max="2000" value="200">
<div class="checkbox-group">
<input type="checkbox" id="lockAspect" checked>
<label for="lockAspect" style="display: inline; margin: 0;">Сохранять пропорции</label>
</div>
</div>
<!-- Дополнительные настройки -->
<div class="control-group">
<h3 style="margin-top: 0; color: #667eea;">⚙️ Дополнительно</h3>
<label>Предустановленные размеры:</label>
<div class="preset-buttons">
<button class="preset-btn" onclick="setPreset(800, 600)">800×600</button>
<button class="preset-btn" onclick="setPreset(1024, 768)">1024×768</button>
<button class="preset-btn" onclick="setPreset(1920, 1080)">Full HD</button>
<button class="preset-btn" onclick="setPreset(2560, 1440)">2K</button>
<button class="preset-btn" onclick="setPreset(1080, 1080)">Instagram</button>
</div>
<label for="scalePercent">Масштаб (%):</label>
<input type="range" id="scalePercent" min="10" max="200" value="100">
<div style="text-align: center; margin-top: 5px;">
<span id="scaleValue">100%</span>
</div>
<label for="formatSelect">Формат файла:</label>
<select id="formatSelect">
<option value="png">PNG (лучшее качество)</option>
<option value="jpeg" selected>JPEG (малый размер)</option>
<option value="webp">WebP (современный)</option>
</select>
<div id="qualityControl">
<label for="qualitySlider">Качество: <span id="qualityValue">85%</span></label>
<input type="range" id="qualitySlider" min="10" max="100" value="85">
</div>
</div>
</div>
<!-- Имя файла -->
<div class="filename-input">
<label for="filenameInput">Имя файла (без расширения):</label>
<input type="text" id="filenameInput" value="измененное-изображение">
</div>
<!-- Кнопки -->
<div class="button-group">
<button id="downloadBtn">💾 Скачать изображение</button>
<button id="resetBtn">🔄 Сбросить к оригиналу</button>
<button id="copyBtn" onclick="copyToClipboard()">📋 Копировать в буфер</button>
</div>
<!-- Статус -->
<div id="statusBar" class="status-bar hidden">
Готово!
</div>
</div>
<!-- Превью -->
<div class="preview-area">
<img id="resizableImage" src="" alt="Ваше изображение" class="hidden">
<div class="image-info">
<div>Текущий размер: <span id="currentSize">-</span></div>
<div>Оригинальный размер: <span id="originalSize">-</span></div>
<div>Изменение: <span id="scaleDiff">-</span></div>
</div>
</div>
// Элементы DOM
const elements = {
fileInput: document.getElementById('imageUpload'),
dropArea: document.getElementById('dropArea'),
img: document.getElementById('resizableImage'),
controls: document.getElementById('resizeControls'),
widthSlider: document.getElementById('widthSlider'),
heightSlider: document.getElementById('heightSlider'),
widthInput: document.getElementById('widthInput'),
heightInput: document.getElementById('heightInput'),
widthValue: document.getElementById('widthValue'),
heightValue: document.getElementById('heightValue'),
downloadBtn: document.getElementById('downloadBtn'),
resetBtn: document.getElementById('resetBtn'),
lockAspect: document.getElementById('lockAspect'),
scalePercent: document.getElementById('scalePercent'),
scaleValue: document.getElementById('scaleValue'),
formatSelect: document.getElementById('formatSelect'),
qualitySlider: document.getElementById('qualitySlider'),
qualityValue: document.getElementById('qualityValue'),
qualityControl: document.getElementById('qualityControl'),
filenameInput: document.getElementById('filenameInput'),
statusBar: document.getElementById('statusBar'),
currentSize: document.getElementById('currentSize'),
originalSize: document.getElementById('originalSize'),
scaleDiff: document.getElementById('scaleDiff')
};
// Переменные состояния
let state = {
originalWidth: 0,
originalHeight: 0,
aspectRatio: 1,
isLocked: true,
currentScale: 100,
isImageLoaded: false
};
// Drag & Drop
elements.dropArea.addEventListener('dragover', (e) => {
e.preventDefault();
elements.dropArea.classList.add('highlight');
});
elements.dropArea.addEventListener('dragleave', () => {
elements.dropArea.classList.remove('highlight');
});
elements.dropArea.addEventListener('drop', (e) => {
e.preventDefault();
elements.dropArea.classList.remove('highlight');
if (e.dataTransfer.files.length) {
elements.fileInput.files = e.dataTransfer.files;
handleFileSelect(e.dataTransfer.files[0]);
}
});
elements.dropArea.addEventListener('click', () => {
elements.fileInput.click();
});
elements.fileInput.addEventListener('change', (e) => {
if (e.target.files[0]) {
handleFileSelect(e.target.files[0]);
}
});
// Обработка выбора файла - ИСПРАВЛЕННАЯ ВЕРСИЯ
function handleFileSelect(file) {
if (!file.type.startsWith('image/')) {
showStatus('Пожалуйста, выберите изображение!', 'error');
return;
}
const reader = new FileReader();
// Сбрасываем состояние
state.isImageLoaded = false;
reader.onload = (event) => {
elements.img.src = event.target.result;
elements.img.classList.remove('hidden');
elements.controls.classList.remove('hidden');
// Обработчик onload устанавливаем ТОЛЬКО если изображение еще не загружено
if (!state.isImageLoaded) {
elements.img.onload = () => {
state.isImageLoaded = true;
// Сохраняем оригинальные размеры
state.originalWidth = elements.img.naturalWidth;
state.originalHeight = elements.img.naturalHeight;
state.aspectRatio = state.originalWidth / state.originalHeight;
// Обновляем информацию
updateSizeInfo();
// Устанавливаем начальные значения
updateSlidersFromImage();
// Обновляем имя файла
const name = file.name.substring(0, file.name.lastIndexOf('.'));
elements.filenameInput.value = name + '-измененное';
showStatus('Изображение загружено!', 'success');
};
}
};
reader.onerror = () => {
showStatus('Ошибка при чтении файла', 'error');
};
reader.readAsDataURL(file);
}
// Обновление слайдеров по изображению
function updateSlidersFromImage() {
const width = elements.img.naturalWidth;
const height = elements.img.naturalHeight;
// Устанавливаем максимальные значения слайдеров
const maxSize = Math.max(width, height) * 3;
elements.widthSlider.max = maxSize;
elements.heightSlider.max = maxSize;
elements.widthInput.max = maxSize;
elements.heightInput.max = maxSize;
// Устанавливаем текущие значения
elements.widthSlider.value = width;
elements.heightSlider.value = height;
elements.widthInput.value = width;
elements.heightInput.value = height;
elements.widthValue.textContent = width;
elements.heightValue.textContent = height;
// Устанавливаем масштаб
state.currentScale = 100;
elements.scalePercent.value = 100;
elements.scaleValue.textContent = '100%';
// Применяем размеры
updateImageSize(width, height);
}
// Обновление размера изображения
function updateImageSize(width, height) {
elements.img.style.width = width + 'px';
elements.img.style.height = height + 'px';
// Обновляем значения в интерфейсе
elements.widthValue.textContent = width;
elements.heightValue.textContent = height;
elements.widthInput.value = width;
elements.heightInput.value = height;
// Обновляем информацию о размере
updateSizeInfo();
}
// Обновление информации о размере
function updateSizeInfo() {
const currentWidth = parseInt(elements.img.style.width) || state.originalWidth;
const currentHeight = parseInt(elements.img.style.height) || state.originalHeight;
elements.currentSize.textContent = `${currentWidth} × ${currentHeight}`;
elements.originalSize.textContent = `${state.originalWidth} × ${state.originalHeight}`;
const scale = Math.round((currentWidth / state.originalWidth) * 100);
elements.scaleDiff.textContent = `${scale > 100 ? '+' : ''}${scale - 100}%`;
state.currentScale = scale;
elements.scalePercent.value = scale;
elements.scaleValue.textContent = `${scale}%`;
}
// События слайдеров
elements.widthSlider.addEventListener('input', () => {
let width = parseInt(elements.widthSlider.value);
let height = parseInt(elements.heightSlider.value);
if (elements.lockAspect.checked) {
height = Math.round(width / state.aspectRatio);
elements.heightSlider.value = height;
}
updateImageSize(width, height);
});
elements.heightSlider.addEventListener('input', () => {
let width = parseInt(elements.widthSlider.value);
let height = parseInt(elements.heightSlider.value);
if (elements.lockAspect.checked) {
width = Math.round(height * state.aspectRatio);
elements.widthSlider.value = width;
}
updateImageSize(width, height);
});
// События числовых полей
elements.widthInput.addEventListener('change', () => {
let width = parseInt(elements.widthInput.value);
let height = parseInt(elements.heightInput.value);
if (elements.lockAspect.checked) {
height = Math.round(width / state.aspectRatio);
elements.heightInput.value = height;
elements.heightSlider.value = height;
}
elements.widthSlider.value = width;
updateImageSize(width, height);
});
elements.heightInput.addEventListener('change', () => {
let width = parseInt(elements.widthInput.value);
let height = parseInt(elements.heightInput.value);
if (elements.lockAspect.checked) {
width = Math.round(height * state.aspectRatio);
elements.widthInput.value = width;
elements.widthSlider.value = width;
}
elements.heightSlider.value = height;
updateImageSize(width, height);
});
// Событие масштаба
elements.scalePercent.addEventListener('input', () => {
const scale = parseInt(elements.scalePercent.value);
elements.scaleValue.textContent = `${scale}%`;
const width = Math.round(state.originalWidth * (scale / 100));
const height = Math.round(state.originalHeight * (scale / 100));
updateImageSize(width, height);
});
// Формат файла
elements.formatSelect.addEventListener('change', () => {
const format = elements.formatSelect.value;
elements.qualityControl.style.display = (format === 'jpeg' || format === 'webp') ? 'block' : 'none';
});
// Качество
elements.qualitySlider.addEventListener('input', () => {
elements.qualityValue.textContent = `${elements.qualitySlider.value}%`;
});
// Сброс к оригиналу
elements.resetBtn.addEventListener('click', () => {
updateImageSize(state.originalWidth, state.originalHeight);
showStatus('Размер сброшен к оригиналу', 'info');
});
// Предустановки
function setPreset(width, height) {
updateImageSize(width, height);
showStatus(`Установлен размер ${width}×${height}`, 'info');
}
// Скачивание
elements.downloadBtn.addEventListener('click', () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const displayWidth = parseInt(elements.img.style.width);
const displayHeight = parseInt(elements.img.style.height);
canvas.width = displayWidth;
canvas.height = displayHeight;
// Фон для JPEG
if (elements.formatSelect.value === 'jpeg') {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.drawImage(elements.img, 0, 0, displayWidth, displayHeight);
// Параметры экспорта
const format = elements.formatSelect.value;
const quality = parseInt(elements.qualitySlider.value) / 100;
let mimeType, extension;
switch(format) {
case 'jpeg':
mimeType = 'image/jpeg';
extension = 'jpg';
break;
case 'webp':
mimeType = 'image/webp';
extension = 'webp';
break;
case 'png':
default:
mimeType = 'image/png';
extension = 'png';
break;
}
// Создаем имя файла
let filename = elements.filenameInput.value.trim();
if (!filename) filename = 'измененное-изображение';
if (!filename.endsWith('.' + extension)) {
filename += '.' + extension;
}
// Скачивание
const link = document.createElement('a');
link.download = filename;
link.href = canvas.toDataURL(mimeType, quality);
link.click();
showStatus(`Изображение скачано как ${filename}`, 'success');
});
// Копирование в буфер
function copyToClipboard() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const displayWidth = parseInt(elements.img.style.width);
const displayHeight = parseInt(elements.img.style.height);
canvas.width = displayWidth;
canvas.height = displayHeight;
ctx.drawImage(elements.img, 0, 0, displayWidth, displayHeight);
canvas.toBlob(blob => {
navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]).then(() => {
showStatus('Изображение скопировано в буфер обмена!', 'success');
}).catch(err => {
showStatus('Не удалось скопировать в буфер', 'error');
console.error(err);
});
});
}
// Показать статус
function showStatus(message, type = 'info') {
elements.statusBar.textContent = message;
elements.statusBar.className = 'status-bar ' + type;
elements.statusBar.classList.remove('hidden');
setTimeout(() => {
elements.statusBar.classList.add('hidden');
}, 3000);
}
// Инициализация
document.addEventListener('DOMContentLoaded', () => {
showStatus('Загрузите изображение для начала работы', 'info');
elements.qualityControl.style.display = 'block';
});
.upload-area {
margin-bottom: 30px;
text-align: center;
padding: 40px;
border: 3px dashed #7f8c8d;
border-radius: 15px;
background: rgba(127, 140, 141, 0.1);
transition: all 0.3s;
cursor: pointer;
}
.upload-area:hover {
background: rgba(127, 140, 141, 0.2);
border-color: #95a5a6;
}
.upload-area.highlight {
background: rgba(46, 204, 113, 0.1);
border-color: #27ae60;
}
.upload-area h3 {
color: #bdc3c7;
margin: 10px 0;
}
.upload-area p {
color: #95a5a6;
margin-bottom: 20px;
}
.upload-area button {
padding: 12px 24px;
background: #7f8c8d;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s;
}
.upload-area button:hover {
background: #95a5a6;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.controls-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 25px;
margin: 30px 0;
}
.control-group {
background: rgba(52, 73, 94, 0.7);
padding: 20px;
border-radius: 12px;
border-left: 4px solid #7f8c8d;
border: 1px solid #3d566e;
}
.control-group h3 {
margin-top: 0;
color: #ecf0f1;
font-size: 1.3em;
}
label {
display: block;
margin: 12px 0 6px;
font-weight: 600;
color: #bdc3c7;
}
input[type="range"] {
width: 100%;
height: 8px;
background: #3d566e;
border-radius: 4px;
outline: none;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 22px;
height: 22px;
background: #95a5a6;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s;
border: 2px solid #2c3e50;
}
input[type="range"]::-webkit-slider-thumb:hover {
background: #bdc3c7;
}
input[type="number"],
select,
input[type="text"] {
width: 100%;
padding: 10px 14px;
border: 2px solid #3d566e;
border-radius: 8px;
font-size: 16px;
transition: all 0.3s;
background: #34495e;
color: #ecf0f1;
box-sizing: border-box;
}
input[type="number"]:focus,
select:focus,
input[type="text"]:focus {
outline: none;
border-color: #95a5a6;
}
.dimension-inputs {
display: flex;
gap: 15px;
margin-top: 15px;
}
.dimension-inputs > div {
flex: 1;
}
.checkbox-group {
display: flex;
align-items: center;
margin: 15px 0;
}
.checkbox-group input {
margin-right: 10px;
width: auto;
accent-color: #95a5a6;
}
.checkbox-group label {
display: inline;
margin: 0;
color: #bdc3c7;
}
.button-group {
display: flex;
gap: 15px;
margin-top: 30px;
flex-wrap: wrap;
}
#downloadBtn {
background: #7f8c8d;
color: white;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
#downloadBtn:hover {
background: #95a5a6;
transform: translateY(-3px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
}
#resetBtn {
background: #5d6d7e;
color: #ecf0f1;
border: 1px solid #5d6d7e;
}
#resetBtn:hover {
background: #6c7b8a;
transform: translateY(-2px);
}
#copyBtn {
background: #6c7b8a;
color: white;
}
#copyBtn:hover {
background: #7d8c9b;
transform: translateY(-3px);
}
.preview-area {
margin-top: 40px;
text-align: center;
}
#resizableImage {
max-width: 100%;
height: auto;
border: 3px solid #3d566e;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
margin: 0 auto;
display: block;
background: rgba(0, 0, 0, 0.1);
}
.image-info {
display: flex;
justify-content: center;
gap: 30px;
margin-top: 20px;
color: #95a5a6;
font-size: 14px;
background: rgba(52, 73, 94, 0.7);
padding: 15px;
border-radius: 10px;
border: 1px solid #3d566e;
}
.image-info div {
padding: 0 10px;
}
.hidden {
display: none;
}
.preset-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 15px;
}
.preset-btn {
padding: 8px 16px;
background: rgba(127, 140, 141, 0.2);
border: 1px solid #7f8c8d;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
color: #bdc3c7;
font-size: 14px;
}
.preset-btn:hover {
background: rgba(127, 140, 141, 0.4);
transform: translateY(-2px);
}
.filename-input {
margin-top: 20px;
background: rgba(52, 73, 94, 0.7);
padding: 15px;
border-radius: 10px;
border: 1px solid #3d566e;
}
.status-bar {
padding: 15px;
border-radius: 8px;
margin-top: 20px;
text-align: center;
font-weight: 600;
transition: all 0.3s;
}
.status-bar.success {
background: rgba(39, 174, 96, 0.2);
color: #2ecc71;
border: 1px solid rgba(46, 204, 113, 0.3);
}
.status-bar.error {
background: rgba(231, 76, 60, 0.2);
color: #e74c3c;
border: 1px solid rgba(231, 76, 60, 0.3);
}
.status-bar.info {
background: rgba(127, 140, 141, 0.2);
color: #bdc3c7;
border: 1px solid rgba(127, 140, 141, 0.3);
}
select option {
background: #34495e;
color: #ecf0f1;
}
/* Адаптивность */
@media (max-width: 768px) {
.controls-grid {
grid-template-columns: 1fr;
}
button {
min-width: 100%;
}
.image-info {
flex-direction: column;
gap: 10px;
text-align: center;
}
.dimension-inputs {
flex-direction: column;
gap: 10px;
}
}
Комментарии
Пока нет комментариев. Будьте первым!