在浏览器里裁剪图片:纯原生、零依赖的 JavaScript 小妙招
先用一个极简 demo 走一遍流程。
你将做出什么从用户设备本地上传图片在 <canvas> 中实时展示与裁剪用拖拽框调整裁剪区域即时预览裁剪结果一键生成并下载裁剪后的图片,无需服务器Demo(原生 HTML/CSS/JS)复制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
.container {
max-width: 600px;
margin: 20px auto;
text-align: center;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}
.image-wrapper {
position: relative;
display: inline-block;
}
.crop-frame {
display: none;
position: absolute;
border: 3px dashed #28a745; /* 视觉反馈更明显 */
box-sizing: border-box;
cursor: move;
}
.resize-handle {
position: absolute;
width: 12px;
height: 12px;
background: #28a745;
cursor: nwse-resize;
}
.preview-area {
margin-top: 20px;
}
button {
padding: 10px20px;
background: #28a745;
color: #fff;
border: none;
cursor: pointer;
border-radius: 6px;
}
button:disabled {
opacity: .6;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<input type="file" id="imageInput" accept="image/*" />
<div class="image-wrapper">
<canvas id="mainCanvas"></canvas>
<div class="crop-frame" id="cropFrame"></div>
</div>
<button id="cropButton" disabled>Crop & Download</button>
<div class="preview-area">
<canvas id="previewCanvas"></canvas>
</div>
</div>
<script>
const imageInput = document.getElementById("imageInput");
const mainCanvas = document.getElementById("mainCanvas");
const ctx = mainCanvas.getContext("2d");
const cropFrame = document.getElementById("cropFrame");
const previewCanvas = document.getElementById("previewCanvas");
const previewCtx = previewCanvas.getContext("2d");
const cropButton = document.getElementById("cropButton");
let image = new Image();
let cropData = { x: 100, y: 100, width: 150, height: 150 };
let isDragging = false;
// 初始尺寸设为 0(数值),避免拉伸
mainCanvas.width = 0;
mainCanvas.height = 0;
imageInput.addEventListener("change", (e) => {
const file = e.target.files && e.target.files[0];
if (!file) return;
image.src = URL.createObjectURL(file);
image.onload = () => {
// 固定画布演示尺寸(也可按比例自适应)
mainCanvas.width = 600;
mainCanvas.height = 450;
// 绘制整图到画布
ctx.clearRect(0, 0, mainCanvas.width, mainCanvas.height);
ctx.drawImage(image, 0, 0, mainCanvas.width, mainCanvas.height);
// 启用裁剪
updateCropFrame();
cropButton.disabled = false;
};
});
function updateCropFrame() {
cropFrame.style.display = "block";
cropFrame.style.left = `${cropData.x}px`;
cropFrame.style.top = `${cropData.y}px`;
cropFrame.style.width = `${cropData.width}px`;
cropFrame.style.height = `${cropData.height}px`;
cropFrame.innerHTML =
<div class="resize-handle" style="bottom: -6px; right: -6px;"></div>;
}
cropFrame.addEventListener("mousedown", (e) => {
isDragging = true;
if (e.target.classList.contains("resize-handle")) {
cropFrame.dataset.mode = "resize";
} else {
cropFrame.dataset.mode = "move";
}
});
document.addEventListener("mousemove", (e) => {
if (!isDragging) return;
const rect = mainCanvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
if (cropFrame.dataset.mode === "move") {
cropData.x = Math.max(
0,
Math.min(
mouseX - cropData.width / 2,
mainCanvas.width - cropData.width
)
);
cropData.y = Math.max(
0,
Math.min(
mouseY - cropData.height / 2,
mainCanvas.height - cropData.height
)
);
} elseif (cropFrame.dataset.mode === "resize") {
cropData.width = Math.max(50, mouseX - cropData.x);
cropData.height = Math.max(50, mouseY - cropData.y);
// 限制在画布范围内
cropData.width = Math.min(cropData.width, mainCanvas.width - cropData.x);
cropData.height = Math.min(cropData.height, mainCanvas.height - cropData.y);
}
updateCropFrame();
});
document.addEventListener("mouseup", () => {
isDragging = false;
cropFrame.dataset.mode = "";
});
cropButton.addEventListener("click", () => {
if (!image.width) return;
previewCanvas.width = cropData.width;
previewCanvas.height = cropData.height;
// 将画布坐标映射回原图坐标
const scaleX = image.width / mainCanvas.width;
const scaleY = image.height / mainCanvas.height;
previewCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
previewCtx.drawImage(
image,
cropData.x * scaleX,
cropData.y * scaleY,
cropData.width * scaleX,
cropData.height * scaleY,
0,
0,
cropData.width,
cropData.height
);
const link = document.createElement("a");
link.download = "cropped-image.png";
link.href = previewCanvas.toDataURL("image/png");
link.click();
});
</script>
</body>
</html>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.97.98.99.100.101.102.103.104.105.106.107.108.109.110.111.112.113.114.115.116.117.118.119.120.121.122.123.124.125.126.127.128.129.130.131.132.133.134.135.136.137.138.139.140.141.142.143.144.145.146.147.148.149.150.151.152.153.154.155.156.157.158.159.160.161.162.163.164.165.166.167.168.169.170.171.172.173.174.175.176.177.178.179.180.181.182.183.184.
把它塞进你的下一个小项目里试试吧。
THE END