本文采用原生js实现选择图片并进行裁剪,将裁剪后的图片使用canvas进行绘制。附效果图及源码。
一、 效果图
二、 源码
1.html部分
<div class="img-box">
<!-- 原图 -->
<img class="img1" alt="" />
<!-- 蒙层 -->
<div class="mask" style="display: none;"></div>
<!-- 被裁剪区域显示的图片 -->
<img class="img2" alt="" />
<!-- 裁剪区域 -->
<div class="img-section-box" style="display: none;">
<span data-pos="leftTop"></span>
<span data-pos="topCenter"></span>
<span data-pos="rightTop"></span>
<span data-pos="rightCenter"></span>
<span data-pos="rightBottom"></span>
<span data-pos="bottomCenter"></span>
<span data-pos="leftBottom"></span>
<span data-pos="leftCenter"></span>
</div>
<!-- 提示 -->
<p>请上传图片</p>
</div>
<!-- 处理按钮 -->
<div class="handle-btn">
<button class="upload">上传图片</button>
<button class="save">保存</button>
</div>
<input type="file" name="" id="file" style="display: none;">
<canvas id="canvas" width="300" height="300"></canvas>
2. css部分
body,
html {
padding: 0;
margin: 0;
}
.img-box {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.img-box img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
user-select: none;
}
.img-box .img-section-box {
position: absolute;
left: var(--l);
top: var(--t);
z-index: 10;
pointer-events: none;
}
.img-box .img-section-box span {
position: absolute;
width: 8px;
height: 8px;
background-color: #fff;
pointer-events: visible;
}
.img-section-box span:nth-child(1) {
left: -4px;
top: -4px;
cursor: nwse-resize;
}
.img-section-box span:nth-child(2) {
left: 50%;
top: -4px;
transform: translateX(-50%);
cursor: ns-resize;
}
.img-section-box span:nth-child(3) {
right: -4px;
top: -4px;
cursor: nesw-resize;
}
.img-section-box span:nth-child(4) {
display: block;
right: -4px;
top: 50%;
transform: translateY(-50%);
cursor: ew-resize;
}
.img-section-box span:nth-child(5) {
right: -4px;
bottom: -4px;
cursor: nwse-resize;
}
.img-section-box span:nth-child(6) {
left: 50%;
bottom: -4px;
transform: translateX(-50%);
cursor: ns-resize;
}
.img-section-box span:nth-child(7) {
left: -4px;
bottom: -4px;
cursor: nesw-resize;
}
.img-section-box span:nth-child(8) {
left: -4px;
top: 50%;
transform: translateY(-50%);
cursor: ew-resize;
}
.img-box .mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.img-box .img2 {
clip-path: inset(var(--t) var(--r) var(--b) var(--l));
cursor: move;
z-index: 9;
}
3. js部分
// 裁剪区域外层容器
const imgBox = document.querySelector(".img-box");
// 获取原图及被裁剪区域图片
const imgs = imgBox.querySelectorAll("img");
// canvas元素
const canvas = document.querySelector("#canvas");
// 保存按钮
const saveBtn = document.querySelector(".save");
// 上传按钮
const uploadBtn = document.querySelector(".upload");
// 上传input
const uploadInput = document.querySelector("#file");
// 被裁剪区域外层容器
const imgSectionBox = document.querySelector(".img-section-box");
// 获取伸缩点元素
const stretchEl = [...imgSectionBox.children];
// 获取蒙层元素
const mask = document.querySelector(".mask");
// 获取canvas上下文
const context = canvas.getContext('2d');
// 是否按下被裁剪区域元素标识
let isDown = false;
// 是否伸缩标识
let isStrech = false;
// 伸缩点处理标识
let dataPos = "";
// 按下被裁剪区域时当前的x轴位置
let downX = 0;
// 按下被裁剪区域时当前的y轴位置
let downY = 0;
// 被裁剪区域距离原图顶部距离
let t = 0;
// 被裁剪区域距离原图左侧距离
let l = 0;
// 被裁剪区域距离原图右侧距离(默认400px,位于最左侧)
let r = 200;
// 被裁剪区域距离原图底部距离(默认400px,位于最上面)
let b = 200;
// 被裁剪区域宽度(默认100px)
let beClipW = 100;
// 被裁剪区域高度(默认100px)
let beClipH = 100;
// 裁剪区域宽度(默认500px)
const clipW = 300;
// 裁剪区域高度(默认500px)
const clipH = 300;
// 上传图片的原始宽度
let imgOriginalW = 0;
// 上传图片的原始高度
let imgOriginalH = 0;
// 裁剪图片地址
let clipImgUrl = "";
// 设置被裁剪区域元素大小
const setBeClipEl = () => {
imgSectionBox.style.width = beClipW + 'px';
imgSectionBox.style.height = beClipH + "px";
}
// 设置裁剪区域元素大小
const setClipEl = () => {
imgBox.style.setProperty('width', clipW + 'px');
imgBox.style.setProperty('height', clipH + 'px');
}
// 设置被裁剪区域元素位置
const setBeClipElPosition = () => {
imgBox.style.setProperty('--t', t + 'px');
imgBox.style.setProperty('--l', l + 'px');
imgBox.style.setProperty('--b', b + 'px');
imgBox.style.setProperty('--r', r + 'px');
}
// 初始化
const init = () => {
// 设置被裁剪区域元素大小
setBeClipEl();
// 设置裁剪区域元素大小
setClipEl();
// 设置被裁剪区域元素位置
setBeClipElPosition();
}
init();
// 被裁剪区域图片添加鼠标按下事件
imgs[1].addEventListener("mousedown", (e) => {
isDown = true;
downX = e.offsetX - l;
downY = e.offsetY - t;
});
document.body.addEventListener("mouseup", () => {
// 复原裁剪区域按下标识
isDown = false;
// 复原伸缩点元素按下标识
isStrech = false;
});
// 裁剪区域外层容器添加移动事件
imgBox.addEventListener("mousemove", (e) => {
if (!isDown && !isStrech) return;
e.preventDefault();
if (isDown) {
// 被裁剪区域图片移动
handleBeClipImgMove(e);
} else if (isStrech) {
// 伸缩点移动
handleStrechPointMove(e);
}
});
// 处理被裁剪区域图片按下移动
const handleBeClipImgMove = (e) => {
t = e.offsetY - downY;
l = e.offsetX - downX;
b = imgBox.offsetHeight + downY - (e.offsetY + beClipH);
r = imgBox.offsetWidth + downX - (e.offsetX + beClipW);
if (b > imgBox.offsetHeight - beClipH) {
b = imgBox.offsetHeight - beClipH;
t = 0;
}
if (r > imgBox.offsetWidth - beClipW) {
r = imgBox.offsetWidth - beClipW;
l = 0;
}
if (t > imgBox.offsetHeight - beClipH) {
t = imgBox.offsetHeight - beClipH;
}
if (l > imgBox.offsetWidth - beClipW) {
l = imgBox.offsetWidth - beClipW;
}
imgBox.style.setProperty('--t', t + 'px');
imgBox.style.setProperty('--l', l + 'px');
imgBox.style.setProperty('--b', b + 'px');
imgBox.style.setProperty('--r', r + 'px');
}
// 处理伸缩点按下移动
const handleStrechPointMove = (e) => {
// 左上
const handleLeftTop = () => {
handleTopCenter();
handleLeftCenter();
}
// 上中
const handleTopCenter = () => {
beClipH = imgBox.offsetHeight - e.clientY - b > imgBox.offsetHeight - b ? imgBox.offsetHeight - b : imgBox.offsetHeight - e.clientY - b;
t = e.clientY < 0 ? 0 : e.clientY;
imgBox.style.setProperty('--t', t + 'px');
}
// 右上
const handleRightTop = () => {
handleTopCenter();
handleRightCenter();
}
// 下中
const handleBottomCenter = () => {
beClipH = e.clientY - t > imgBox.offsetHeight - t ? imgBox.offsetHeight - t : e.clientY - t;
b = imgBox.offsetHeight - e.clientY < 0 ? 0 : imgBox.offsetHeight - e.clientY;
imgBox.style.setProperty('--b', b + 'px');
}
// 右中
const handleRightCenter = () => {
beClipW = e.clientX - l > imgBox.offsetWidth - l ? imgBox.offsetWidth - l : e.clientX - l;
r = imgBox.offsetWidth - e.clientX < 0 ? 0 : imgBox.offsetWidth - e.clientX;
imgBox.style.setProperty('--r', r + 'px');
}
// 右下
const handleRightBottom = () => {
handleBottomCenter();
handleRightCenter();
}
// 左下
const handleLeftBottom = () => {
handleBottomCenter();
handleLeftCenter();
}
// 左中
const handleLeftCenter = () => {
beClipW = imgBox.offsetWidth - e.clientX - r > imgBox.offsetWidth - r ? imgBox.offsetWidth - r : imgBox.offsetWidth - e.clientX - r;
l = e.clientX < 0 ? 0 : e.clientX;
imgBox.style.setProperty('--l', l + 'px');
}
switch (dataPos) {
case "leftTop":
handleLeftTop();
break;
case "topCenter":
handleTopCenter();
break;
case "rightTop":
handleRightTop();
break;
case "rightCenter":
handleRightCenter();
break;
case "rightBottom":
handleRightBottom();
break;
case "bottomCenter":
handleBottomCenter();
break;
case "leftBottom":
handleLeftBottom();
break;
case "leftCenter":
handleLeftCenter();
break;
}
// 更新被裁剪区域元素宽高
setBeClipEl();
}
// 给伸缩点元素添加按下事件
stretchEl.forEach(item => {
item.addEventListener("mousedown", (e) => {
if (isStrech) return;
isStrech = true;
dataPos = e.target.dataset.pos;
});
});
// 保存回调
const saveCallback = () => {
if (clipImgUrl === "") return alert("请上传图片后再试");
// 根据原始图片与裁剪区域图片大小计算图片比例
const left = imgOriginalW / clipW * l;
const top = imgOriginalH / clipH * t;
const sw = imgOriginalW / clipW * beClipW;
const sh = imgOriginalH / clipH * beClipH;
// 绘制被裁剪图片
context.drawImage(imgs[0], left, top, sw, sh, 0, 0, 100, 100);
}
// 保存
saveBtn.addEventListener("click", saveCallback);
// 上传图片
uploadBtn.addEventListener("click", () => {
uploadInput.click();
});
// 图片选择触发回调
uploadInput.addEventListener("change", (e) => {
const file = e.target.files[0];
const url = window.URL || window.webkitURL;
clipImgUrl = url.createObjectURL(file);
imgs[0].src = clipImgUrl;
imgs[1].src = clipImgUrl;
// 显示蒙层
mask.style.display = "block";
// 显示被裁剪区域外层容器
imgSectionBox.style.display = "block";
//创建Image对象
const img = new Image();
//创建Image的对象的url
img.src = clipImgUrl;
img.onload = function() {
// 获取图片原始高度
imgOriginalH = this.height;
// 获取图片原始宽度
imgOriginalW = this.width;
}
})