图片裁剪是现代 Web 应用中常见的功能需求,无论是社交媒体平台、电商网站还是个人博客,用户经常需要对上传的图片进行裁剪以获得最佳显示效果。本文将介绍如何使用纯前端技术实现一个功能完整的图片裁剪工具,无需后端支持即可在浏览器中完成图片裁剪操作。
实现原理概述
我们的图片裁剪工具基于 HTML5、CSS3 和原生 JavaScript 实现,主要利用以下技术特性:
- 使用 HTML5 的 File API 处理图片上传
- 通过 CSS 的 clip-path 属性实现图片裁剪效果预览
- 使用 Canvas API 生成最终裁剪结果
- 结合鼠标事件实现裁剪框的拖拽和缩放交互
实现效果展示
完整功能代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片裁剪demo</title>
<style type="text/css">
body {
background: #555;
}
#box {
position: relative;
}
#img1 {
opacity: 0.6;
position: absolute;
top: 0;
left: 0;
user-select: none;
}
#img2 {
opacity: 1;
position: absolute;
top: 0;
left: 0;
user-select: none;
/* clip: rect(0px,200px,200px,0px); */
clip-path: polygon(0px 0px,
/* 左上角 */
200px 0px,
/* 右上角 */
200px 200px,
/* 右下角 */
0px 200px
/* 左下角 */
);
}
#main {
position: absolute;
width: 200px;
height: 200px;
cursor: move;
display: none;
}
.minDiv {
position: absolute;
width: 8px;
height: 8px;
background: #fff;
}
.left-up {
top: -4px;
left: -4px;
cursor: nw-resize;
}
.up {
left: 50%;
margin-left: -4px;
margin-top: -4px;
cursor: n-resize;
}
.right-up {
right: -4px;
top: -4px;
cursor: ne-resize;
}
.right {
right: -4px;
top: 50%;
margin-top: -4px;
cursor: e-resize;
}
.right-down {
right: -4px;
bottom: -4px;
cursor: se-resize;
}
.down {
bottom: -4px;
right: 50%;
margin-left: -4px;
cursor: s-resize;
}
.left-down {
left: -4px;
bottom: -4px;
cursor: sw-resize;
}
.left {
left: -4px;
top: 50%;
margin-top: -4px;
cursor: w-resize;
}
</style>
</head>
<body>
<div class="contain">
<input id="imgFile" type="file" name="imgFile">
<button id="preview">预览裁剪结果</button>
<div id="box">
<img src="" id="img1">
<img src="" id="img2">
<div id="main">
<div class="minDiv left-up"></div>
<div class="minDiv up"></div>
<div class="minDiv right-up"></div>
<div class="minDiv right"></div>
<div class="minDiv right-down"></div>
<div class="minDiv down"></div>
<div class="minDiv left-down"></div>
<div class="minDiv left"></div>
</div>
</div>
</div>
</body>
<script>
const doms = {
imgFile: document.getElementById('imgFile'),
preview: document.getElementById('preview'),
box: document.getElementById('box'),
img1: document.getElementById('img1'),
img2: document.getElementById('img2'),
main: document.getElementById('main'),
leftUp: document.querySelector('.left-up'),
up: document.querySelector('.up'),
rightUp: document.querySelector('.right-up'),
right: document.querySelector('.right'),
rightDown: document.querySelector('.right-down'),
down: document.querySelector('.down'),
leftDown: document.querySelector('.left-down'),
left: document.querySelector('.left')
}
const dirDoms = [
doms.leftUp,
doms.up,
doms.rightUp,
doms.right,
doms.rightDown,
doms.down,
doms.leftDown,
doms.left
]
// 封装:更新裁剪框位置和大小(带边界检查)
function updateCropBoxPosition(left, top, width, height) {
// console.log('left, top, width, height', left, top, width, height);
// 边界检查
const imgWidth = doms.img1.offsetWidth;
const imgHeight = doms.img1.offsetHeight;
// 设置最小尺寸限制
const minWidth = 60;
const minHeight = 60;
// 确保宽度和高度不小于最小值
if (width < minWidth) width = minWidth;
if (height < minHeight) height = minHeight;
// 防止左侧溢出
if (left < 0) {
left = 0;
}
// 防止右侧溢出
if (left + width > imgWidth) {
// 尝试调整位置保持宽度
if (width <= imgWidth) {
left = imgWidth - width;
} else {
// 如果宽度大于图片宽度,限制宽度并居中显示
width = imgWidth;
left = 0;
}
}
// 防止顶部溢出
if (top < 0) {
top = 0;
}
// 防止底部溢出
if (top + height > imgHeight) {
// 尝试调整位置保持高度
if (height <= imgHeight) {
top = imgHeight - height;
} else {
// 如果高度大于图片高度,限制高度并居中显示
height = imgHeight;
top = 0;
}
}
// 设置裁剪框样式
doms.main.style.left = `${left}px`;
doms.main.style.top = `${top}px`;
doms.main.style.width = `${width}px`;
doms.main.style.height = `${height}px`;
// 更新裁剪路径
doms.img2.style.clipPath = `polygon(
${left}px ${top}px,
${left + width}px ${top}px,
${left + width}px ${top + height}px,
${left}px ${top + height}px
)`;
}
// 实现图片的上传
doms.imgFile.onchange = function (e) {
// 获取上传的图片
const file = e.target.files[0];
// 创建一个文件读取器
const reader = new FileReader();
// 读取文件
reader.readAsDataURL(file);
// 读取文件成功时,将图片显示在img1和img2中
reader.onload = function (e) {
const imgUrl = e.target.result;
// 设置图片 src 后,监听图片的 load 事件
doms.img1.src = imgUrl;
doms.img2.src = imgUrl;
// 监听 img1 的 load 事件(确保两张图片共享同一 URL,避免重复加载)
const img = new Image();
img.src = imgUrl;
img.onload = function () {
// 此时图片已完全渲染,可获取正确尺寸
const imgWidth = doms.img1.offsetWidth;
const imgHeight = doms.img1.offsetHeight;
// 初始化裁剪框
const width = imgWidth / 2;
const height = imgHeight / 2;
const left = imgWidth / 4;
const top = imgHeight / 4;
updateCropBoxPosition(left, top, width, height);
doms.main.style.display = 'block';
};
// const url = URL.createObjectURL(file);
// doms.img1.src = url;
// doms.img2.src = url;
};
};
// 预览裁剪结果
doms.preview.onclick = function () {
// 获取裁剪框的位置和大小
const left = doms.main.offsetLeft;
const top = doms.main.offsetTop;
const width = doms.main.offsetWidth;
const height = doms.main.offsetHeight;
// console.log(left, top, width, height);
// 使用canvas将裁剪框中的图片裁剪出来
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(doms.img1, left, top, width, height, 0, 0, width, height);
// 将裁剪后的图片显示在页面上 不要被裁剪框遮挡
canvas.style.position = 'absolute';
canvas.style.left = doms.img1.offsetWidth + 50 + 'px';
// 如果canvas已经存在,就删除
if (document.querySelector('canvas')) {
document.querySelector('canvas').remove();
}
document.body.appendChild(canvas);
canvas.toBlob(function (blob) {
// console.log(blob);
const file = new File([blob], 'cut.png', { type: 'image/png' });
console.log('file', file);
})
}
// 实现裁剪框的拖拽效果
doms.main.onmousedown = function (e) {
// 鼠标按下时,获取鼠标的位置
const startX = e.clientX;
const startY = e.clientY;
// console.log(startX, startY);
// 裁剪框初始位置
const startLeft = doms.main.offsetLeft;
const startTop = doms.main.offsetTop;
// 鼠标移动时,获取鼠标的位置
document.onmousemove = function (e) {
const moveX = e.clientX;
const moveY = e.clientY;
// 计算鼠标移动的距离
const disX = moveX - startX;
const disY = moveY - startY;
// console.log(disX, disY);
// 计算裁剪框的位置
const left = startLeft + disX;
const top = startTop + disY;
updateCropBoxPosition(left, top, doms.main.offsetWidth, doms.main.offsetHeight);
}
// 鼠标抬起时,停止移动
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
}
}
// 实现裁剪框的缩放效果
dirDoms.forEach(dirDom => {
dirDom.onmousedown = function (e) {
// 阻止父元素的事件
e.stopPropagation();
// 鼠标按下时,获取鼠标的位置
const startX = e.clientX;
const startY = e.clientY;
// 裁剪框初始位置
const startLeft = doms.main.offsetLeft;
const startTop = doms.main.offsetTop;
// 裁剪框初始大小
const startWidth = doms.main.offsetWidth;
const startHeight = doms.main.offsetHeight;
// 鼠标移动时,获取鼠标的位置
document.onmousemove = function (e) {
const moveX = e.clientX;
const moveY = e.clientY;
// 计算鼠标移动的距离
const disX = moveX - startX;
const disY = moveY - startY;
// 根据不同点计算裁剪框的位置和大小
let left;
let top;
let width;
let height;
if (dirDom === doms.leftUp) {
left = startLeft + disX;
top = startTop + disY;
width = startWidth - disX;
height = startHeight - disY;
}
else if (dirDom === doms.up) {
left = startLeft;
top = startTop + disY;
width = startWidth;
height = startHeight - disY;
}
else if (dirDom === doms.rightUp) {
left = startLeft;
top = startTop + disY;
width = startWidth + disX;
height = startHeight - disY;
}
else if (dirDom === doms.right) {
left = startLeft;
top = startTop;
width = startWidth + disX;
height = startHeight;
}
else if (dirDom === doms.rightDown) {
left = startLeft;
top = startTop;
width = startWidth + disX;
height = startHeight + disY;
}
else if (dirDom === doms.down) {
left = startLeft;
top = startTop;
width = startWidth;
height = startHeight + disY;
}
else if (dirDom === doms.leftDown) {
left = startLeft + disX;
top = startTop;
width = startWidth - disX;
height = startHeight + disY;
}
else if (dirDom === doms.left) {
left = startLeft + disX;
top = startTop;
width = startWidth - disX;
height = startHeight;
}
else {
left = startLeft;
top = startTop;
width = startWidth;
height = startHeight;
}
updateCropBoxPosition(left, top, width, height);
}
// 鼠标抬起时,停止移动
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
}
}
})
</script>
</html>
核心功能解析
1.双图片叠加预览技术
我们使用两张相同的图片叠加显示,底层图片 (img1) 设置为半透明作为背景参考,上层图片 (img2) 使用 clip-path 属性进行裁剪,从而实现裁剪效果的实时预览:
#img1 {
opacity: 0.6;
position: absolute;
top: 0;
left: 0;
}
#img2 {
opacity: 1;
position: absolute;
top: 0;
left: 0;
clip-path: polygon(0px 0px, 200px 0px, 200px 200px, 0px 200px);
}
2.图片上传与预览
通过 HTML5 的 File API 实现图片上传功能,使用 FileReader 将图片转换为 DataURL 格式并显示在页面上:
doms.imgFile.onchange = function (e) {
// 获取上传的图片
const file = e.target.files[0];
// 创建一个文件读取器
const reader = new FileReader();
// 读取文件
reader.readAsDataURL(file);
// 读取文件成功时,将图片显示在img1和img2中
reader.onload = function (e) {
const imgUrl = e.target.result;
// 设置图片 src 后,监听图片的 load 事件
doms.img1.src = imgUrl;
doms.img2.src = imgUrl;
// 监听 img1 的 load 事件(确保两张图片共享同一 URL,避免重复加载)
const img = new Image();
img.src = imgUrl;
img.onload = function () {
// 此时图片已完全渲染,可获取正确尺寸
const imgWidth = doms.img1.offsetWidth;
const imgHeight = doms.img1.offsetHeight;
// 初始化裁剪框
const width = imgWidth / 2;
const height = imgHeight / 2;
const left = imgWidth / 4;
const top = imgHeight / 4;
updateCropBoxPosition(left, top, width, height);
doms.main.style.display = 'block';
};
// const url = URL.createObjectURL(file);
// doms.img1.src = url;
// doms.img2.src = url;
};
};
3.裁剪框的拖拽与缩放交互
核心就是确定裁剪框最终的left,top,width以及height的值,通过监听鼠标事件实现裁剪框的拖拽和缩放功能:
// 实现裁剪框的拖拽效果
doms.main.onmousedown = function (e) {
// 鼠标按下时,获取鼠标的位置
const startX = e.clientX;
const startY = e.clientY;
// console.log(startX, startY);
// 裁剪框初始位置
const startLeft = doms.main.offsetLeft;
const startTop = doms.main.offsetTop;
// 鼠标移动时,获取鼠标的位置
document.onmousemove = function (e) {
const moveX = e.clientX;
const moveY = e.clientY;
// 计算鼠标移动的距离
const disX = moveX - startX;
const disY = moveY - startY;
// console.log(disX, disY);
// 计算裁剪框的位置
const left = startLeft + disX;
const top = startTop + disY;
updateCropBoxPosition(left, top, doms.main.offsetWidth, doms.main.offsetHeight);
}
// 鼠标抬起时,停止移动
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
}
}
// 实现裁剪框的缩放效果
dirDoms.forEach(dirDom => {
dirDom.onmousedown = function (e) {
// 阻止父元素的事件
e.stopPropagation();
// 鼠标按下时,获取鼠标的位置
const startX = e.clientX;
const startY = e.clientY;
// 裁剪框初始位置
const startLeft = doms.main.offsetLeft;
const startTop = doms.main.offsetTop;
// 裁剪框初始大小
const startWidth = doms.main.offsetWidth;
const startHeight = doms.main.offsetHeight;
// 鼠标移动时,获取鼠标的位置
document.onmousemove = function (e) {
const moveX = e.clientX;
const moveY = e.clientY;
// 计算鼠标移动的距离
const disX = moveX - startX;
const disY = moveY - startY;
// 根据不同点计算裁剪框的位置和大小
let left;
let top;
let width;
let height;
if (dirDom === doms.leftUp) {
left = startLeft + disX;
top = startTop + disY;
width = startWidth - disX;
height = startHeight - disY;
}
else if (dirDom === doms.up) {
left = startLeft;
top = startTop + disY;
width = startWidth;
height = startHeight - disY;
}
else if (dirDom === doms.rightUp) {
left = startLeft;
top = startTop + disY;
width = startWidth + disX;
height = startHeight - disY;
}
else if (dirDom === doms.right) {
left = startLeft;
top = startTop;
width = startWidth + disX;
height = startHeight;
}
else if (dirDom === doms.rightDown) {
left = startLeft;
top = startTop;
width = startWidth + disX;
height = startHeight + disY;
}
else if (dirDom === doms.down) {
left = startLeft;
top = startTop;
width = startWidth;
height = startHeight + disY;
}
else if (dirDom === doms.leftDown) {
left = startLeft + disX;
top = startTop;
width = startWidth - disX;
height = startHeight + disY;
}
else if (dirDom === doms.left) {
left = startLeft + disX;
top = startTop;
width = startWidth - disX;
height = startHeight;
}
else {
left = startLeft;
top = startTop;
width = startWidth;
height = startHeight;
}
updateCropBoxPosition(left, top, width, height);
}
// 鼠标抬起时,停止移动
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
}
}
})
4.边界检查与尺寸限制
为了确保裁剪框不会超出图片范围,我们实现了边界检查功能:
function updateCropBoxPosition(left, top, width, height) {
// console.log('left, top, width, height', left, top, width, height);
// 边界检查
const imgWidth = doms.img1.offsetWidth;
const imgHeight = doms.img1.offsetHeight;
// 设置最小尺寸限制
const minWidth = 60;
const minHeight = 60;
// 确保宽度和高度不小于最小值
if (width < minWidth) width = minWidth;
if (height < minHeight) height = minHeight;
// 防止左侧溢出
if (left < 0) {
left = 0;
}
// 防止右侧溢出
if (left + width > imgWidth) {
// 尝试调整位置保持宽度
if (width <= imgWidth) {
left = imgWidth - width;
} else {
// 如果宽度大于图片宽度,限制宽度并居中显示
width = imgWidth;
left = 0;
}
}
// 防止顶部溢出
if (top < 0) {
top = 0;
}
// 防止底部溢出
if (top + height > imgHeight) {
// 尝试调整位置保持高度
if (height <= imgHeight) {
top = imgHeight - height;
} else {
// 如果高度大于图片高度,限制高度并居中显示
height = imgHeight;
top = 0;
}
}
// 设置裁剪框样式
doms.main.style.left = `${left}px`;
doms.main.style.top = `${top}px`;
doms.main.style.width = `${width}px`;
doms.main.style.height = `${height}px`;
// 更新裁剪路径
doms.img2.style.clipPath = `polygon(
${left}px ${top}px,
${left + width}px ${top}px,
${left + width}px ${top + height}px,
${left}px ${top + height}px
)`;
}
5.最终裁剪结果生成
使用 Canvas API 将裁剪区域导出为新的图片(需要注意使用drawImage时如果img区域与原图大小如果不一致的话,需要额外计算缩放比例来确定最终的裁剪区域):
doms.preview.onclick = function () {
// 获取裁剪框的位置和大小
const left = doms.main.offsetLeft;
const top = doms.main.offsetTop;
const width = doms.main.offsetWidth;
const height = doms.main.offsetHeight;
// console.log(left, top, width, height);
// 使用canvas将裁剪框中的图片裁剪出来
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(doms.img1, left, top, width, height, 0, 0, width, height);
// 将裁剪后的图片显示在页面上 不要被裁剪框遮挡
canvas.style.position = 'absolute';
canvas.style.left = doms.img1.offsetWidth + 50 + 'px';
// 如果canvas已经存在,就删除
if (document.querySelector('canvas')) {
document.querySelector('canvas').remove();
}
document.body.appendChild(canvas);
canvas.toBlob(function (blob) {
// console.log(blob);
const file = new File([blob], 'cut.png', { type: 'image/png' });
console.log('file', file);
})
}
总结
通过结合 HTML5 的 File API、CSS 的 clip-path 属性和 Canvas API,我们实现了一个功能完整的纯前端图片裁剪工具。这个实现不需要后端支持,所有操作都在浏览器中完成,具有良好的用户体验和性能表现。
可以根据自己的需求进一步扩展这个工具,例如添加预设裁剪比例、旋转功能、滤镜效果等,使其成为一个更强大的图片编辑工具。








文章有(10)条网友点评
using tadalafil daily
using tadalafil daily
kamagra 100mg sydney
kamagra 100mg sydney
levitra versus viagra
levitra versus viagra
avanafil mexico precio
avanafil mexico precio
finasteride lower libido
finasteride lower libido
semaglutide bijwerkingen
semaglutide bijwerkingen
proscar drug group
proscar drug group
cialis bph treatment
cialis bph treatment
minoxidil men nearby
minoxidil men nearby
xenical tablets reviews
xenical tablets reviews