<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">图片裁剪工具 · 按比例裁剪</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
background: #f2f4f8;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 16px;
}
.card {
background: white;
border-radius: 24px;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15);
padding: 24px 28px 32px;
max-width: 780px;
width: 100%;
transition: all 0.2s;
}
h1 {
font-size: 1.6rem;
font-weight: 600;
letter-spacing: -0.01em;
display: flex;
align-items: center;
gap: 8px;
color: #1e293b;
margin-bottom: 8px;
}
.sub {
color: #64748b;
font-size: 0.9rem;
margin-bottom: 20px;
border-left: 4px solid #3b82f6;
padding-left: 14px;
background: #f8fafc;
border-radius: 0 8px 8px 0;
line-height: 1.4;
}
.upload-area {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
margin-bottom: 20px;
background: #f9fafb;
padding: 12px 16px;
border-radius: 60px;
border: 1px dashed #cbd5e1;
}
.upload-btn {
background: #3b82f6;
color: white;
border: none;
padding: 10px 22px;
border-radius: 40px;
font-weight: 500;
font-size: 0.95rem;
cursor: pointer;
transition: 0.2s;
display: inline-flex;
align-items: center;
gap: 8px;
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.2);
}
.upload-btn:hover {
background: #2563eb;
transform: scale(1.02);
}
.upload-btn:active {
transform: scale(0.97);
}
.file-name {
color: #334155;
font-size: 0.95rem;
font-weight: 450;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
flex: 1;
}
.ratio-control {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px 18px;
margin-bottom: 24px;
background: #f1f5f9;
padding: 12px 18px;
border-radius: 40px;
}
.ratio-group {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.ratio-group label {
font-size: 0.9rem;
font-weight: 500;
color: #1e293b;
background: white;
padding: 4px 14px;
border-radius: 30px;
border: 1px solid #e2e8f0;
cursor: pointer;
transition: all 0.15s;
box-shadow: 0 1px 3px rgba(0,0,0,0.02);
}
.ratio-group input[type="radio"] {
display: none;
}
.ratio-group input[type="radio"]:checked + label {
background: #3b82f6;
color: white;
border-color: #3b82f6;
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
}
.custom-ratio {
display: flex;
align-items: center;
gap: 6px;
background: white;
padding: 2px 8px 2px 14px;
border-radius: 40px;
border: 1px solid #e2e8f0;
}
.custom-ratio input {
width: 52px;
padding: 6px 4px;
border: none;
background: transparent;
font-weight: 500;
font-size: 0.9rem;
text-align: center;
outline: none;
color: #0f172a;
}
.custom-ratio input:focus {
background: #f0f4ff;
border-radius: 20px;
}
.custom-ratio span {
color: #64748b;
font-weight: 400;
font-size: 1.1rem;
}
.canvas-wrapper {
position: relative;
background: #0f172a;
border-radius: 20px;
overflow: hidden;
margin-bottom: 18px;
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05);
}
#imageCanvas {
display: block;
width: 100%;
height: auto;
max-height: 460px;
background: #1e293b;
object-fit: contain;
touch-action: none;
cursor: crosshair;
}
.action-bar {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-top: 6px;
}
.action-btn-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.btn {
background: white;
border: 1px solid #d1d9e6;
padding: 8px 20px;
border-radius: 40px;
font-weight: 500;
color: #1e293b;
font-size: 0.9rem;
cursor: pointer;
transition: 0.15s;
display: inline-flex;
align-items: center;
gap: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.02);
}
.btn-primary {
background: #3b82f6;
border: 1px solid #3b82f6;
color: white;
box-shadow: 0 4px 10px rgba(59, 130, 246, 0.3);
}
.btn-primary:hover {
background: #2563eb;
border-color: #2563eb;
}
.btn-primary:active {
transform: scale(0.96);
}
.btn:active {
transform: scale(0.96);
}
.btn:disabled {
opacity: 0.5;
pointer-events: none;
filter: grayscale(0.3);
}
.download-hint {
color: #475569;
font-size: 0.85rem;
background: #f1f5f9;
padding: 4px 16px;
border-radius: 30px;
white-space: nowrap;
}
.footer-note {
margin-top: 18px;
text-align: center;
font-size: 0.75rem;
color: #94a3b8;
}
@media (max-width: 500px) {
.card { padding: 16px; }
.upload-area { flex-direction: column; align-items: stretch; border-radius: 28px; }
.file-name { max-width: 100%; }
.ratio-control { flex-direction: column; align-items: stretch; gap: 8px; }
.action-bar { flex-direction: column; align-items: stretch; }
.action-btn-group { justify-content: center; }
.download-hint { text-align: center; }
}
</style>
</head>
<body>
<div class="card">
<h1>✂️ 比例裁剪</h1>
<div class="sub">拖拽鼠标 / 手指移动 · 自动保持宽高比</div>
<!-- 上传 & 比例 -->
<div class="upload-area">
<label for="imageUpload" class="upload-btn">📁 选择图片</label>
<input type="file" id="imageUpload" accept="image/png, image/jpeg, image/webp, image/bmp" style="display:none;">
<span class="file-name" id="fileName">未选择文件</span>
</div>
<div class="ratio-control">
<div class="ratio-group">
<input type="radio" name="ratio" id="ratioFree" value="free" checked>
<label for="ratioFree">🔄 自由</label>
<input type="radio" name="ratio" id="ratio1_1" value="1:1">
<label for="ratio1_1">1:1</label>
<input type="radio" name="ratio" id="ratio4_3" value="4:3">
<label for="ratio4_3">4:3</label>
<input type="radio" name="ratio" id="ratio16_9" value="16:9">
<label for="ratio16_9">16:9</label>
<input type="radio" name="ratio" id="ratio3_2" value="3:2">
<label for="ratio3_2">3:2</label>
</div>
<div class="custom-ratio">
<span>✏️</span>
<input type="number" id="ratioW" value="4" min="0.1" step="0.1" placeholder="宽">
<span>:</span>
<input type="number" id="ratioH" value="3" min="0.1" step="0.1" placeholder="高">
</div>
</div>
<!-- Canvas 容器 -->
<div class="canvas-wrapper">
<canvas id="imageCanvas" width="640" height="400"></canvas>
</div>
<!-- 操作栏 -->
<div class="action-bar">
<div class="action-btn-group">
<button class="btn" id="resetBtn">↺ 重置</button>
<button class="btn btn-primary" id="cropBtn">✂️ 裁剪并下载</button>
</div>
<span class="download-hint">⬇️ 点击裁剪自动保存</span>
</div>
<div class="footer-note">拖拽选区 · 支持触屏操作</div>
</div>
<script>
(function() {
// DOM 元素
const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d');
const fileInput = document.getElementById('imageUpload');
const fileName = document.getElementById('fileName');
const resetBtn = document.getElementById('resetBtn');
const cropBtn = document.getElementById('cropBtn');
// 比例单选
const ratioRadios = document.querySelectorAll('input[name="ratio"]');
const ratioWInput = document.getElementById('ratioW');
const ratioHInput = document.getElementById('ratioH');
// 状态
let image = null; // 原始图片 (HTMLImageElement)
let imageLoaded = false;
// 选区 (归一化 0~1 相对于画布显示区域)
let sel = { x: 0.1, y: 0.1, w: 0.8, h: 0.8 };
// 拖拽状态
let isDragging = false;
let dragType = ''; // 'move' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'edge'
let dragStart = { x: 0, y: 0 };
let dragSel = { x: 0, y: 0, w: 0, h: 0 };
// 画布尺寸 (显示尺寸)
let canvasDisplayWidth = 0;
let canvasDisplayHeight = 0;
// 初次重置选区到合理位置
function resetSelection() {
if (!imageLoaded || !image) {
// 默认选区
sel.x = 0.05; sel.y = 0.05; sel.w = 0.9; sel.h = 0.9;
drawCanvas();
return;
}
// 根据图片比例调整初始选区 (占满 85% 区域)
const imgAspect = image.naturalWidth / image.naturalHeight;
const dispAspect = canvasDisplayWidth / canvasDisplayHeight;
let w, h;
if (imgAspect > dispAspect) {
w = 0.9;
h = w / (imgAspect / dispAspect);
} else {
h = 0.9;
w = h * (imgAspect / dispAspect);
}
// 居中
sel.x = (1 - w) / 2;
sel.y = (1 - h) / 2;
sel.w = Math.min(w, 0.95);
sel.h = Math.min(h, 0.95);
clampSelection();
drawCanvas();
}
// 将选区限制在画布内
function clampSelection() {
if (sel.x < 0) sel.x = 0;
if (sel.y < 0) sel.y = 0;
if (sel.x + sel.w > 1) sel.w = 1 - sel.x;
if (sel.y + sel.h > 1) sel.h = 1 - sel.y;
if (sel.w < 0.01) sel.w = 0.01;
if (sel.h < 0.01) sel.h = 0.01;
}
// 获取当前比例约束 (返回值: [wRatio, hRatio] 或 null 表示自由)
function getCurrentRatio() {
for (const radio of ratioRadios) {
if (radio.checked) {
const val = radio.value;
if (val === 'free') return null;
if (val === '1:1') return [1, 1];
if (val === '4:3') return [4, 3];
if (val === '16:9') return [16, 9];
if (val === '3:2') return [3, 2];
}
}
// 自定义 (如果自由未选中则读取自定义)
const w = parseFloat(ratioWInput.value);
const h = parseFloat(ratioHInput.value);
if (isNaN(w) || isNaN(h) || w <= 0 || h <= 0) return null;
return [w, h];
}
// 应用比例约束 (修改 sel 原地)
function applyRatioConstraint() {
const ratio = getCurrentRatio();
if (!ratio) return; // 自由模式
const [rw, rh] = ratio;
const currentAspect = sel.w / sel.h;
const targetAspect = rw / rh;
// 以中心为锚点调整宽/高
const cx = sel.x + sel.w / 2;
const cy = sel.y + sel.h / 2;
let newW, newH;
if (currentAspect > targetAspect) {
// 过宽: 缩小宽度
newW = sel.h * targetAspect;
newH = sel.h;
} else {
// 过高: 缩小高度
newH = sel.w / targetAspect;
newW = sel.w;
}
sel.w = Math.min(newW, 1);
sel.h = Math.min(newH, 1);
sel.x = Math.max(0, Math.min(1 - sel.w, cx - sel.w / 2));
sel.y = Math.max(0, Math.min(1 - sel.h, cy - sel.h / 2));
clampSelection();
}
// ---------- 绘图 ----------
function drawCanvas() {
const rect = canvas.getBoundingClientRect();
canvasDisplayWidth = rect.width;
canvasDisplayHeight = rect.height;
// 设置画布实际分辨率 (2倍缩放提升清晰度)
const dpr = window.devicePixelRatio || 1;
const drawW = canvasDisplayWidth * dpr;
const drawH = canvasDisplayHeight * dpr;
if (canvas.width !== drawW || canvas.height !== drawH) {
canvas.width = drawW;
canvas.height = drawH;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 背景 (暗色)
ctx.fillStyle = '#1e293b';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (!imageLoaded || !image) {
// 显示提示文字
ctx.fillStyle = '#94a3b8';
ctx.font = `${18 * dpr}px system-ui, sans-serif`;
ctx.textAlign = 'center';
ctx.fillText('请选择一张图片', canvas.width/2, canvas.height/2 - 10);
return;
}
// 绘制图片 (保持宽高比适应画布)
const imgW = image.naturalWidth;
const imgH = image.naturalHeight;
const cw = canvas.width, ch = canvas.height;
const scale = Math.min(cw / imgW, ch / imgH);
const offsetX = (cw - imgW * scale) / 2;
const offsetY = (ch - imgH * scale) / 2;
ctx.drawImage(image, offsetX, offsetY, imgW * scale, imgH * scale);
// 绘制遮罩 + 选区
// 选区坐标转换为 canvas 像素
const sx = offsetX + sel.x * (imgW * scale);
const sy = offsetY + sel.y * (imgH * scale);
const sw = sel.w * (imgW * scale);
const sh = sel.h * (imgH * scale);
// 遮罩 (半透明黑)
ctx.fillStyle = 'rgba(0,0,0,0.45)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 擦出选区 (使用 'destination-out' 需要复合模式,改用 clearRect 但需保留背景? 更简单:使用globalCompositeOperation)
ctx.save();
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(0,0,0,1)';
ctx.fillRect(sx, sy, sw, sh);
ctx.restore();
// 绘制亮色边框
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2 * dpr;
ctx.setLineDash([]);
ctx.strokeRect(sx, sy, sw, sh);
// 绘制四个角柄
const handleSize = 10 * dpr;
const handles = [
[sx, sy, 'nw'], [sx+sw, sy, 'ne'],
[sx, sy+sh, 'sw'], [sx+sw, sy+sh, 'se']
];
ctx.fillStyle = '#3b82f6';
ctx.strokeStyle = 'white';
ctx.lineWidth = 1.5 * dpr;
for (let [hx, hy] of handles) {
ctx.fillRect(hx - handleSize/2, hy - handleSize/2, handleSize, handleSize);
ctx.strokeRect(hx - handleSize/2, hy - handleSize/2, handleSize, handleSize);
}
// 如果图片加载完成,更新文件名字
}
// 重绘 & 尺寸自适应
function redraw() {
if (!canvasDisplayWidth || !canvasDisplayHeight) {
const rect = canvas.getBoundingClientRect();
canvasDisplayWidth = rect.width;
canvasDisplayHeight = rect.height;
}
drawCanvas();
}
// ---------- 图片加载 ----------
function loadImage(file) {
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
image = img;
imageLoaded = true;
fileName.textContent = file.name;
// 重置选区
resetSelection();
redraw();
};
img.onerror = () => {
alert('图片加载失败,请选择有效图片文件。');
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
loadImage(e.target.files[0]);
}
});
// ---------- 鼠标/触屏 交互 ----------
function getCanvasCoords(e) {
const rect = canvas.getBoundingClientRect();
let clientX, clientY;
if (e.touches) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
e.preventDefault();
} else {
clientX = e.clientX;
clientY = e.clientY;
}
return {
x: (clientX - rect.left) / rect.width,
y: (clientY - rect.top) / rect.height
};
}
function handlePointerDown(e) {
if (!imageLoaded) return;
const pos = getCanvasCoords(e);
const px = pos.x, py = pos.y;
// 检查是否在角柄 (像素判断需要转换)
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const offX = (canvas.width - image.naturalWidth * Math.min(canvas.width/image.naturalWidth, canvas.height/image.naturalHeight)) / 2;
const offY = (canvas.height - image.naturalHeight * Math.min(canvas.width/image.naturalWidth, canvas.height/image.naturalHeight)) / 2;
const imgScale = Math.min(canvas.width / image.naturalWidth, canvas.height / image.naturalHeight);
// 计算手柄位置 (像素)
const sx = offX + sel.x * (image.naturalWidth * imgScale);
const sy = offY + sel.y * (image.naturalHeight * imgScale);
const sw = sel.w * (image.naturalWidth * imgScale);
const sh = sel.h * (image.naturalHeight * imgScale);
const handleSize = 10 * (window.devicePixelRatio || 1);
const cx = (clientXFromEvent(e) - rect.left) * (canvas.width / rect.width);
const cy = (clientYFromEvent(e) - rect.top) * (canvas.height / rect.height);
function clientXFromEvent(ev) {
return ev.touches ? ev.touches[0].clientX : ev.clientX;
}
function clientYFromEvent(ev) {
return ev.touches ? ev.touches[0].clientY : ev.clientY;
}
// 检查角手柄 (按像素)
const corners = [
{ x: sx, y: sy, type: 'topLeft' },
{ x: sx+sw, y: sy, type: 'topRight' },
{ x: sx, y: sy+sh, type: 'bottomLeft' },
{ x: sx+sw, y: sy+sh, type: 'bottomRight' }
];
for (const corner of corners) {
if (Math.abs(cx - corner.x) < handleSize && Math.abs(cy - corner.y) < handleSize) {
isDragging = true;
dragType = corner.type;
dragStart = { x: px, y: py };
dragSel = { ...sel };
return;
}
}
// 检查是否在选区内 (移动)
if (px >= sel.x && px <= sel.x + sel.w && py >= sel.y && py <= sel.y + sel.h) {
isDragging = true;
dragType = 'move';
dragStart = { x: px, y: py };
dragSel = { ...sel };
return;
}
// 点击外部: 重新选区 (可选)
// 为了简化,点击外部不重置,但可做备用
}
function handlePointerMove(e) {
if (!isDragging || !imageLoaded) return;
e.preventDefault();
const pos = getCanvasCoords(e);
const dx = pos.x - dragStart.x;
const dy = pos.y - dragStart.y;
const newSel = { ...dragSel };
if (dragType === 'move') {
newSel.x = dragSel.x + dx;
newSel.y = dragSel.y + dy;
// 边界限制
if (newSel.x < 0) newSel.x = 0;
if (newSel.y < 0) newSel.y = 0;
if (newSel.x + newSel.w > 1) newSel.x = 1 - newSel.w;
if (newSel.y + newSel.h > 1) newSel.y = 1 - newSel.h;
sel = newSel;
clampSelection();
drawCanvas();
return;
}
// 角缩放
const ratio = getCurrentRatio();
let rw = ratio ? ratio[0] : null;
let rh = ratio ? ratio[1] : null;
const processCorner = (fixedX, fixedY, freeX, freeY) => {
let rawW, rawH;
if (freeX) {
rawW = dragSel.w + dx;
} else {
rawW = dragSel.w - dx;
}
if (freeY) {
rawH = dragSel.h + dy;
} else {
rawH = dragSel.h - dy;
}
if (rawW < 0.01) rawW = 0.01;
if (rawH < 0.01) rawH = 0.01;
let newW = rawW, newH = rawH;
if (rw && rh) {
// 锁定比例: 根据拖拽方向决定以宽为准还是高为准
const targetAspect = rw / rh;
// 尝试以宽为准
let tryW = rawW;
let tryH = rawW / targetAspect;
if (tryH > 0 && tryH < 1.5) {
newW = tryW; newH = tryH;
} else {
newH = rawH;
newW = rawH * targetAspect;
}
}
// 计算固定点
let newX = fixedX ? dragSel.x : (dragSel.x + dragSel.w - newW);
let newY = fixedY ? dragSel.y : (dragSel.y + dragSel.h - newH);
// 边界修正
if (newX < 0) { newX = 0; if (rw && rh) newW = Math.min(newW, 1); }
if (newY < 0) { newY = 0; if (rw && rh) newH = Math.min(newH, 1); }
if (newX + newW > 1) { newX = 1 - newW; }
if (newY + newH > 1) { newY = 1 - newH; }
sel.x = newX; sel.y = newY; sel.w = newW; sel.h = newH;
clampSelection();
};
switch (dragType) {
case 'topLeft': processCorner(true, true, false, false); break;
case 'topRight': processCorner(false, true, true, false); break;
case 'bottomLeft': processCorner(true, false, false, true); break;
case 'bottomRight': processCorner(false, false, true, true); break;
default: break;
}
drawCanvas();
}
function handlePointerUp(e) {
if (isDragging) {
isDragging = false;
dragType = '';
// 保证比例约束 (如果是自由则跳过)
applyRatioConstraint();
drawCanvas();
}
}
// 注册事件
canvas.addEventListener('mousedown', handlePointerDown);
window.addEventListener('mousemove', handlePointerMove);
window.addEventListener('mouseup', handlePointerUp);
canvas.addEventListener('touchstart', handlePointerDown, { passive: false });
window.addEventListener('touchmove', handlePointerMove, { passive: false });
window.addEventListener('touchend', handlePointerUp);
// 比例切换重绘
for (const radio of ratioRadios) {
radio.addEventListener('change', () => {
if (imageLoaded) {
applyRatioConstraint();
drawCanvas();
}
});
}
ratioWInput.addEventListener('input', () => {
if (imageLoaded) {
// 如果自由模式,不强制;但若自定义比例,需要检查
const ratio = getCurrentRatio();
if (ratio) {
applyRatioConstraint();
drawCanvas();
}
}
});
ratioHInput.addEventListener('input', () => {
if (imageLoaded) {
const ratio = getCurrentRatio();
if (ratio) {
applyRatioConstraint();
drawCanvas();
}
}
});
// 重置
resetBtn.addEventListener('click', () => {
if (imageLoaded) {
resetSelection();
applyRatioConstraint();
drawCanvas();
} else {
sel = { x: 0.05, y: 0.05, w: 0.9, h: 0.9 };
drawCanvas();
}
});
// 裁剪下载
cropBtn.addEventListener('click', () => {
if (!imageLoaded || !image) {
alert('请先加载一张图片');
return;
}
// 计算裁剪区域 (基于原始图片)
const imgW = image.naturalWidth;
const imgH = image.naturalHeight;
// 计算画布中图片的绘制区域 (同drawCanvas)
const cw = canvas.width, ch = canvas.height;
const scale = Math.min(cw / imgW, ch / imgH);
const offsetX = (cw - imgW * scale) / 2;
const offsetY = (ch - imgH * scale) / 2;
// 选区在原始图片上的像素坐标
const cropX = Math.round((sel.x * (imgW * scale)) / scale);
const cropY = Math.round((sel.y * (imgH * scale)) / scale);
const cropW = Math.round((sel.w * (imgW * scale)) / scale);
const cropH = Math.round((sel.h * (imgH * scale)) / scale);
// 边界保护
const safeX = Math.max(0, Math.min(cropX, imgW - 1));
const safeY = Math.max(0, Math.min(cropY, imgH - 1));
const safeW = Math.min(cropW, imgW - safeX);
const safeH = Math.min(cropH, imgH - safeY);
if (safeW < 2 || safeH < 2) {
alert('选区太小,请调整裁剪区域');
return;
}
// 创建临时canvas
const tempCanvas = document.createElement('canvas');
tempCanvas.width = safeW;
tempCanvas.height = safeH;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(image, safeX, safeY, safeW, safeH, 0, 0, safeW, safeH);
// 下载
const link = document.createElement('a');
link.download = 'cropped_image.png';
link.href = tempCanvas.toDataURL('image/png');
link.click();
});
// 窗口变化自适应
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
if (imageLoaded) redraw();
else drawCanvas();
}, 100);
});
// 初始绘制
drawCanvas();
// 如果有默认图片? 无
// 设置默认自定义比例为4:3
ratioWInput.value = 4;
ratioHInput.value = 3;
})();
</script>
</body>
</html>
功能与操作方法
您可以通过简单的拖拽和选择,快速实现图片的精确裁剪,其核心操作流程如下:

- 加载图片:点击“选择图片”按钮上传您需要裁剪的图片。
- 设置比例:您可以在上方选择预设比例(如1:1、4:3)或自定义宽高比,选区将自动遵循所选比例。
- 调整选区:在图片上拖拽鼠标或手指来移动选区,或拖拽选区的四个角来调整大小,选区会始终保持您设定的宽高比。
- 完成裁剪:调整满意后,点击“裁剪并下载”按钮,系统将按照选区范围生成并保存裁剪后的图片。
标签: 比例裁剪
版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。