<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">图片等比例缩放 · 纯前端脚本</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: linear-gradient(145deg, #f6f9fc 0%, #e6f0f5 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
padding: 20px;
}
.card {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border-radius: 48px;
padding: 40px 36px;
max-width: 860px;
width: 100%;
box-shadow: 0 25px 45px -12px rgba(0, 20, 30, 0.35), inset 0 1px 2px rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.5);
transition: all 0.2s ease;
}
h1 {
font-weight: 600;
font-size: 2rem;
letter-spacing: -0.01em;
color: #0b2b3b;
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
h1 small {
font-size: 1rem;
font-weight: 400;
color: #3b5f6f;
background: #dce8ef;
padding: 4px 14px;
border-radius: 40px;
letter-spacing: 0.3px;
}
.sub {
color: #2b5a6b;
margin-bottom: 28px;
border-left: 4px solid #4b8a9f;
padding-left: 18px;
background: rgba(75, 138, 159, 0.06);
border-radius: 0 12px 12px 0;
font-weight: 450;
}
.upload-zone {
background: #f0f7fb;
border: 2px dashed #8fb6c9;
border-radius: 28px;
padding: 32px 24px;
text-align: center;
cursor: pointer;
transition: 0.2s;
margin-bottom: 28px;
position: relative;
}
.upload-zone:hover {
background: #e4f0f7;
border-color: #3a7a8f;
transform: scale(1.01);
}
.upload-zone.dragover {
background: #d8eaf3;
border-color: #1f6579;
box-shadow: 0 0 0 4px rgba(31, 101, 121, 0.15);
}
.upload-zone svg {
width: 48px;
height: 48px;
color: #3f7b8c;
margin-bottom: 8px;
opacity: 0.7;
}
.upload-zone p {
font-size: 1.2rem;
font-weight: 500;
color: #124a5c;
}
.upload-zone small {
color: #3f6f7a;
display: block;
margin-top: 6px;
}
#imageInput {
display: none;
}
.preview-container {
display: flex;
flex-wrap: wrap;
gap: 30px;
justify-content: center;
margin-bottom: 28px;
}
.preview-box {
background: #ffffffd6;
border-radius: 24px;
padding: 18px 18px 22px 18px;
box-shadow: 0 8px 20px -8px rgba(0,0,0,0.1);
flex: 1 1 200px;
min-width: 160px;
backdrop-filter: blur(2px);
border: 1px solid #ffffff;
}
.preview-box h3 {
font-weight: 500;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #1b4b5b;
margin-bottom: 12px;
border-bottom: 1px solid #c5dae3;
padding-bottom: 6px;
}
.preview-box .image-wrapper {
background: #f2f9ff;
border-radius: 18px;
padding: 8px;
display: flex;
justify-content: center;
align-items: center;
min-height: 140px;
}
.preview-box img {
max-width: 100%;
max-height: 200px;
border-radius: 12px;
display: block;
box-shadow: 0 4px 8px rgba(0,0,0,0.04);
}
.size-info {
font-size: 0.8rem;
margin-top: 10px;
background: #e9f2f7;
padding: 6px 12px;
border-radius: 40px;
display: inline-block;
color: #1e4d5a;
}
.controls {
background: #eaf3f8;
border-radius: 32px;
padding: 20px 24px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 20px;
backdrop-filter: blur(2px);
border: 1px solid #cfe1ea;
}
.slider-group {
display: flex;
align-items: center;
gap: 16px;
flex: 2 1 240px;
}
.slider-group label {
font-weight: 550;
color: #0b3b4b;
white-space: nowrap;
}
.slider-group input[type="range"] {
flex: 1;
height: 6px;
-webkit-appearance: none;
background: #acc7d6;
border-radius: 12px;
outline: none;
}
.slider-group input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: #1a6b7f;
border-radius: 20px;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0,40,50,0.3);
transition: 0.1s;
border: 2px solid white;
}
.slider-group input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.12);
background: #0f5368;
}
.dimension-badge {
background: #d0e2ec;
border-radius: 40px;
padding: 6px 18px;
font-weight: 500;
color: #0a3b4b;
min-width: 110px;
text-align: center;
font-size: 0.9rem;
}
.action-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
background: white;
border: none;
padding: 12px 22px;
border-radius: 60px;
font-weight: 550;
color: #0a3340;
box-shadow: 0 4px 8px rgba(0, 20, 30, 0.06);
cursor: pointer;
transition: 0.15s;
display: inline-flex;
align-items: center;
gap: 8px;
border: 1px solid #d0e2eb;
background: #f8fcff;
font-size: 0.9rem;
}
.btn-primary {
background: #1a6b7f;
border: 1px solid #1a6b7f;
color: white;
box-shadow: 0 8px 14px -8px #0a4a5e;
}
.btn-primary:hover {
background: #115a6b;
transform: scale(0.97);
border-color: #0f4d5e;
}
.btn:hover {
background: #e6f2f9;
transform: translateY(-2px);
}
.btn-primary:hover {
background: #115a6b;
}
.btn:active {
transform: translateY(1px);
box-shadow: none;
}
.footer-note {
margin-top: 20px;
font-size: 0.8rem;
color: #3b697a;
text-align: center;
opacity: 0.7;
}
.empty-hint {
color: #2c5b6b;
font-weight: 400;
}
</style>
</head>
<body>
<div class="card">
<h1>
🖼️ 等比缩放
<small>保持比例</small>
</h1>
<div class="sub">拖拽图片或点击上传 · 实时预览缩放结果</div>
<!-- 上传区域 -->
<div class="upload-zone" id="dropZone">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5" />
</svg>
<p>上传图片 / 拖拽到此处</p>
<small>支持 JPG / PNG / WebP / BMP</small>
<input type="file" id="imageInput" accept="image/*">
</div>
<!-- 预览区域 -->
<div class="preview-container" id="previewContainer">
<div class="preview-box">
<h3>📷 原图</h3>
<div class="image-wrapper" id="originalWrapper">
<img id="originalImage" src="" alt="原图预览" style="display: none;">
<span class="empty-hint" id="emptyOriginalHint">等待上传</span>
</div>
<div class="size-info" id="originalSize">—</div>
</div>
<div class="preview-box">
<h3>✨ 缩放后</h3>
<div class="image-wrapper" id="scaledWrapper">
<img id="scaledImage" src="" alt="缩放预览" style="display: none;">
<span class="empty-hint" id="emptyScaledHint">等待上传</span>
</div>
<div class="size-info" id="scaledSize">—</div>
</div>
</div>
<!-- 控制栏 -->
<div class="controls">
<div class="slider-group">
<label for="scaleSlider">缩放</label>
<input type="range" id="scaleSlider" min="1" max="100" value="50" step="1">
<span class="dimension-badge" id="scalePercent">50%</span>
</div>
<div class="dimension-badge" id="dimensionDisplay">等待图片</div>
<div class="action-buttons">
<button class="btn" id="resetBtn">↺ 重置</button>
<button class="btn btn-primary" id="downloadBtn">⬇ 下载缩放图</button>
</div>
</div>
<div class="footer-note">
拖动滑块调整缩放比例 · 始终保持原始宽高比
</div>
</div>
<script>
(function() {
// DOM 元素
const dropZone = document.getElementById('dropZone');
const imageInput = document.getElementById('imageInput');
const originalImage = document.getElementById('originalImage');
const scaledImage = document.getElementById('scaledImage');
const originalSize = document.getElementById('originalSize');
const scaledSize = document.getElementById('scaledSize');
const scaleSlider = document.getElementById('scaleSlider');
const scalePercent = document.getElementById('scalePercent');
const dimensionDisplay = document.getElementById('dimensionDisplay');
const resetBtn = document.getElementById('resetBtn');
const downloadBtn = document.getElementById('downloadBtn');
const emptyOriginalHint = document.getElementById('emptyOriginalHint');
const emptyScaledHint = document.getElementById('emptyScaledHint');
const originalWrapper = document.getElementById('originalWrapper');
const scaledWrapper = document.getElementById('scaledWrapper');
// 状态
let originalImg = null; // 原始图片对象 (HTMLImageElement)
let originalWidth = 0;
let originalHeight = 0;
let currentScale = 0.5; // 0.01 ~ 1.0,默认50%
let scaledDataUrl = ''; // 缩放后的 dataURL (用于下载)
// ---- 工具函数 ----
// 显示图片或占位提示
function setImageDisplay(imgElement, hintElement, src, showImg) {
if (showImg && src) {
imgElement.src = src;
imgElement.style.display = 'block';
hintElement.style.display = 'none';
} else {
imgElement.style.display = 'none';
hintElement.style.display = 'inline';
if (!src) imgElement.src = '';
}
}
// 更新原始图片预览和尺寸信息
function updateOriginalView(image) {
if (!image) {
setImageDisplay(originalImage, emptyOriginalHint, '', false);
originalSize.textContent = '—';
return;
}
setImageDisplay(originalImage, emptyOriginalHint, image.src, true);
originalSize.textContent = `${image.naturalWidth} × ${image.naturalHeight}`;
}
// 核心:缩放并更新预览 (根据 currentScale)
function scaleAndUpdate() {
if (!originalImg) {
// 没有图片,清空缩放预览
setImageDisplay(scaledImage, emptyScaledHint, '', false);
scaledSize.textContent = '—';
dimensionDisplay.textContent = '等待图片';
scaledDataUrl = '';
return;
}
const scale = currentScale;
const w = Math.round(originalWidth * scale);
const h = Math.round(originalHeight * scale);
// 保证最小为1px (原图非空时)
const finalW = Math.max(1, w);
const finalH = Math.max(1, h);
// 使用 Canvas 进行等比缩放
const canvas = document.createElement('canvas');
canvas.width = finalW;
canvas.height = finalH;
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(originalImg, 0, 0, finalW, finalH);
// 输出 dataURL ( PNG 无损 )
const dataUrl = canvas.toDataURL('image/png');
scaledDataUrl = dataUrl;
// 更新缩放预览
setImageDisplay(scaledImage, emptyScaledHint, dataUrl, true);
scaledSize.textContent = `${finalW} × ${finalH}`;
dimensionDisplay.textContent = `${finalW} × ${finalH}`;
// 更新百分比显示
scalePercent.textContent = Math.round(scale * 100) + '%';
}
// 从文件加载图片
function loadImageFile(file) {
if (!file || !file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
// 保存原始图片信息
originalImg = img;
originalWidth = img.naturalWidth;
originalHeight = img.naturalHeight;
// 更新原始图预览
updateOriginalView(img);
// 根据当前滑块缩放
currentScale = parseFloat(scaleSlider.value) / 100;
// 保证范围
currentScale = Math.min(1, Math.max(0.01, currentScale));
scaleSlider.value = Math.round(currentScale * 100);
scaleAndUpdate();
};
img.onerror = function() {
alert('图片加载失败,请尝试其他文件。');
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
// ---- 事件绑定 ----
// 点击上传区域触发文件选择
dropZone.addEventListener('click', function() {
imageInput.click();
});
imageInput.addEventListener('change', function(e) {
if (e.target.files && e.target.files[0]) {
loadImageFile(e.target.files[0]);
}
// 可重复上传同一文件
imageInput.value = '';
});
// 拖拽上传
dropZone.addEventListener('dragover', function(e) {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', function(e) {
e.preventDefault();
dropZone.classList.remove('dragover');
});
dropZone.addEventListener('drop', function(e) {
e.preventDefault();
dropZone.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files && files.length > 0) {
loadImageFile(files[0]);
}
});
// 滑块调整
scaleSlider.addEventListener('input', function() {
const val = parseInt(this.value, 10);
currentScale = val / 100;
// 限制范围
if (currentScale < 0.01) currentScale = 0.01;
if (currentScale > 1) currentScale = 1;
scalePercent.textContent = Math.round(currentScale * 100) + '%';
scaleAndUpdate();
});
// 重置:回到50%,并且预览重置 (但不清除图片)
resetBtn.addEventListener('click', function() {
if (!originalImg) {
// 没有图片重置无效,但可以给个反馈
return;
}
scaleSlider.value = 50;
currentScale = 0.5;
scalePercent.textContent = '50%';
scaleAndUpdate();
});
// 下载缩放后图片
downloadBtn.addEventListener('click', function() {
if (!originalImg || !scaledDataUrl) {
alert('请先上传一张图片,才能下载缩放图 😊');
return;
}
const link = document.createElement('a');
link.download = `等比缩放_${Math.round(currentScale*100)}%.png`;
link.href = scaledDataUrl;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
// ---- 初始化 (空状态) ----
updateOriginalView(null);
setImageDisplay(scaledImage, emptyScaledHint, '', false);
scaledSize.textContent = '—';
dimensionDisplay.textContent = '等待图片';
scalePercent.textContent = '50%';
currentScale = 0.5;
scaleSlider.value = 50;
scaledDataUrl = '';
// 额外:若用户点击清空? 不提供清空功能,但可以上传替换。
// 让上传区域更友好: 点击upload-zone也能触发input
// 键盘可用性: 略
// 小细节:当没有图片时,滑块依然可以拖动,但不会产生缩放图 (scaleAndUpdate 已经处理)
// 重置按钮在无图片时不报错
console.log('图片等比例缩放脚本已启动 ✓');
})();
</script>
</body>
</html>
缩放操作与核心逻辑
您可以通过上传图片并拖动滑块,直观地控制缩放比例,整个过程会保持图片的原始宽高比,并实时预览结果。

- 图片上传:您可以通过点击上传区域或直接拖拽图片文件到指定区域来加载图片,支持常见的图片格式。
- 缩放控制:拖动“缩放”滑块可以调整缩放比例(1% 至 100%),右侧会同步显示当前比例和缩放后的精确尺寸。
- 结果处理:预览区会并排显示原图和缩放后的效果,您可以使用“下载缩放图”按钮保存处理后的图片,或使用“重置”按钮快速将比例恢复到 50%。
优化建议: 您可以根据需要调整 scaleSlider 滑块的 min、max 和 step 属性值,以改变可选的缩放范围与精度。