在瀏覽器里裁剪圖片:純?cè)⒘阋蕾嚨?JavaScript 小妙招
作者:前端小智
?今天帶你用瀏覽器原生能力實(shí)現(xiàn)圖片裁剪:不裝庫、不走服務(wù)端、即時(shí)預(yù)覽、即可下載。

先用一個(gè)極簡 demo 走一遍流程。
你將做出什么
- 從用戶設(shè)備本地上傳圖片
- 在
<canvas>中實(shí)時(shí)展示與裁剪 - 用拖拽框調(diào)整裁剪區(qū)域
- 即時(shí)預(yù)覽裁剪結(jié)果
- 一鍵生成并下載裁剪后的圖片,無需服務(wù)器
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;
// 初始尺寸設(shè)為 0(數(shù)值),避免拉伸
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 = () => {
// 固定畫布演示尺寸(也可按比例自適應(yīng))
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);
// 限制在畫布范圍內(nèi)
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;
// 將畫布坐標(biāo)映射回原圖坐標(biāo)
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>原理一眼看懂
- 用一個(gè)
<canvas>承擔(dān)圖片渲染,輕量且原生。 - 用戶選擇本地圖片,加載到固定尺寸的畫布里;拖動(dòng)裁剪框或其縮放手柄,實(shí)時(shí)改變裁剪區(qū)域。
- 綠框與手柄提供直觀的視覺反饋;布局簡單,移動(dòng)端/桌面端都能順利操作。
- 點(diǎn)擊按鈕后,按比例把畫布坐標(biāo)映射到原圖像素,把裁剪結(jié)果繪到預(yù)覽畫布,并生成 Data URL 觸發(fā)下載——全程不觸服務(wù)器。
重點(diǎn)回顧
- 純 HTML/CSS/JS 即可完成:上傳 → 裁剪 → 預(yù)覽 → 下載
canvas.drawImage()+ 比例換算是關(guān)鍵- 交互只需拖動(dòng)移動(dòng)與單點(diǎn)縮放兩種模式,邏輯清晰、易擴(kuò)展
把它塞進(jìn)你的下一個(gè)小項(xiàng)目里試試吧。
責(zé)任編輯:姜華
來源:
大遷世界























