最近在写自己的ui组件(没错,我已经不满足做一个只会cv的程序猿!!!),目前想写一个上传图片的组件,之前笔记里也记载了如何上传图片。但是我想做一个可以实现裁剪的图片上传,那么图片上传已经实现了,剩下的就是裁剪图片了。这里主要记录原生js实现一个图片裁剪。代码主要体现实现思路,所以会有代码冗余繁琐等问题。
首先是文件上传预览,因为要实现裁剪上传,所以就不能传统input type=file 选择文件后就交互后端,应该使用FileReader的readAsDataURL直接把图片读取出来加载,然后交给裁剪容器去裁剪。
那么裁剪容器真的能做到裁剪吗?当然是不行了,它只是获取选中的左上和右下边界点的坐标,裁剪这个事情还是要交给canvas去做的。
本次笔记主要记录如何呈现一个裁剪容器。
首先你得有一个遮罩层和一个选中区,这里一开始真的是难倒我了,一开始就去死磕blend-mode,但是在选中区背景色为透明的时候,这个方法不能满足我的要求。所以人不能喜新厌旧,不能学了点新东西就老想用上,最后实现遮罩层和选中区的方法很简单就是用border,让border足够的粗大,颜色设置为半透明的黑色不就是遮罩层吗?那么只需要控制选中区的大小就可以实现裁剪选中了。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>截图选中title>
<link rel="stylesheet" type="text/css" href="./css/jietu.css">
link>
head>
<body>
<div class="container">
<img src="./img/皮卡丘01.jpg" alt="上传图片" />
<div class="select-area s-move-content-outer">
<div class="s-move-content-direction n">div>
<div class="s-move-content-direction ne">div>
<div class="s-move-content-direction e">div>
<div class="s-move-content-direction se">div>
<div class="s-move-content-direction s">div>
<div class="s-move-content-direction sw">div>
<div class="s-move-content-direction w">div>
<div class="s-move-content-direction nw">div>
div>
div>
<script>
// 获取展示的元素
const selectArea = document.querySelector(".select-area");
// 获取各个拖动点
const directionN = document.querySelector(".n");
const directionE = document.querySelector(".e");
const directionS = document.querySelector(".s");
const directionW = document.querySelector(".w");
const directionNE = document.querySelector(".ne");
const directionSE = document.querySelector(".se");
const directionSW = document.querySelector(".sw");
const directionNW = document.querySelector(".nw");
// 记录每次点击拖动点的坐标位置原始移动位置
let initialPointCoordinates = [0, 0];
// 记录拖动前展示区的对应border的宽度,当拖动斜角上的点时需要两个边框一起改变
let currentBorderWidth = [0, 0];
// 记录拖动前展示区的宽高
let sizeBeforeChange = [0, 0];
// 绑定向上拖动的函数
directionN.addEventListener("mousedown", (event) => {
// 记录初始点的坐标
initialPointCoordinates = [event.x, event.y];
sizeBeforeChange = [selectArea.clientWidth, selectArea.clientHeight];
currentBorderWidth[0] = parseFloat(getComputedStyle(selectArea)["border-top-width"]);
selectArea.addEventListener("mousemove", moveUp);
});
// 绑定向下拖动的函数
directionS.addEventListener("mousedown", (event) => {
initialPointCoordinates = [event.x, event.y];
sizeBeforeChange = [selectArea.clientWidth, selectArea.clientHeight];
currentBorderWidth[0] = parseFloat(getComputedStyle(selectArea)["border-bottom-width"]);
selectArea.addEventListener("mousemove", moveDown);
});
// 绑定向左拖动的函数
directionW.addEventListener("mousedown", (event) => {
initialPointCoordinates = [event.x, event.y];
sizeBeforeChange = [selectArea.clientWidth, selectArea.clientHeight];
currentBorderWidth[0] = parseFloat(getComputedStyle(selectArea)["border-left-width"]);
selectArea.addEventListener("mousemove", moveLeft);
});
// 绑定向右拖动的函数
directionE.addEventListener("mousedown", (event) => {
initialPointCoordinates = [event.x, event.y];
sizeBeforeChange = [selectArea.clientWidth, selectArea.clientHeight];
currentBorderWidth[0] = parseFloat(getComputedStyle(selectArea)["border-right-width"]);
selectArea.addEventListener("mousemove", moveRight);
});
// 绑定向右上拖动的函数
directionNE.addEventListener("mousedown", (event) => {
initialPointCoordinates = [event.x, event.y];
sizeBeforeChange = [selectArea.clientWidth, selectArea.clientHeight];
currentBorderWidth[0] = parseFloat(getComputedStyle(selectArea)["border-top-width"]);
currentBorderWidth[1] = parseFloat(getComputedStyle(selectArea)["border-right-width"]);
selectArea.addEventListener("mousemove", moveUpRight);
});
// 绑定向右下拖动的函数
directionSE.addEventListener("mousedown", (event) => {
initialPointCoordinates = [event.x, event.y];
sizeBeforeChange = [selectArea.clientWidth, selectArea.clientHeight];
currentBorderWidth[0] = parseFloat(getComputedStyle(selectArea)["border-bottom-width"]);
currentBorderWidth[1] = parseFloat(getComputedStyle(selectArea)["border-right-width"]);
selectArea.addEventListener("mousemove", moveDownRight);
});
// 绑定向左上拖动的函数
directionNW.addEventListener("mousedown", (event) => {
initialPointCoordinates = [event.x, event.y];
sizeBeforeChange = [selectArea.clientWidth, selectArea.clientHeight];
currentBorderWidth[0] = parseFloat(getComputedStyle(selectArea)["border-top-width"]);
currentBorderWidth[1] = parseFloat(getComputedStyle(selectArea)["border-left-width"]);
selectArea.addEventListener("mousemove", moveUpLeft);
});
directionSW.addEventListener("mousedown", (event) => {
initialPointCoordinates = [event.x, event.y];
sizeBeforeChange = [selectArea.clientWidth, selectArea.clientHeight];
currentBorderWidth[0] = parseFloat(getComputedStyle(selectArea)["border-bottom-width"]);
currentBorderWidth[1] = parseFloat(getComputedStyle(selectArea)["border-left-width"]);
selectArea.addEventListener("mousemove", moveDownLeft);
});
/**
* 向上移动函数
*/
const moveUp = (event) => {
// 用当前位置减去原始位置算出移动距离
const movingDistance = event.y - initialPointCoordinates[1];
if (movingDistance > sizeBeforeChange[1]) return null;
// 把计算得出距离加到选中元素高度上面去,注意向下拖动时缩减高度,所以用减号
selectArea.style.height = sizeBeforeChange[1] - movingDistance + "px"
// 同时根据移动距离减去对应的boder的宽度
selectArea.style.borderTopWidth = currentBorderWidth[0] + movingDistance + "px";
document.addEventListener("mouseup", () => {
selectArea.removeEventListener("mousemove", moveUp);
})
}
/**
* 向下移动的函数
*/
const moveDown = (event) => {
// 用当前位置减去原始位置算出移动距离
const movingDistance = event.y - initialPointCoordinates[1];
// 设置向上拖动的极限,也就是上边框的底部
if (movingDistance < -sizeBeforeChange[1]) return null;
// 把计算得出距离加到选中元素高度上面去
selectArea.style.height = sizeBeforeChange[1] + movingDistance + "px";
// 同时根据移动距离减去对应的boder的宽度, 注意向下拖动时缩减border宽度,所以要做减法
selectArea.style.borderBottomWidth = currentBorderWidth[0] - movingDistance + "px";
document.addEventListener("mouseup", () => {
selectArea.removeEventListener("mousemove", moveDown);
})
}
/**
* 向左移动的函数
*/
const moveLeft = (event) => {
// 用当前位置减去原始位置算出移动距离,这里减去的是宽度
const movingDistance = event.x - initialPointCoordinates[0];
if (movingDistance > sizeBeforeChange[0]) return null;
// 把计算得出距离加到选中元素宽度上面去
selectArea.style.width = sizeBeforeChange[0] - movingDistance + "px"
// 同时根据移动距离减去对应的boder的宽度
selectArea.style.borderLeftWidth = currentBorderWidth[0] + movingDistance + "px";
document.addEventListener("mouseup", () => {
selectArea.removeEventListener("mousemove", moveLeft);
})
}
/**
* 向右移动的函数
*/
const moveRight = (event) => {
// 用当前位置减去原始位置算出移动距离,这里减去的是宽度
const movingDistance = event.x - initialPointCoordinates[0];
if (movingDistance < -sizeBeforeChange[0]) return null;
// 把计算得出距离加到选中元素宽度上面去
selectArea.style.width = sizeBeforeChange[0] + movingDistance + "px"
// 同时根据移动距离减去对应的boder的宽度
selectArea.style.borderRightWidth = currentBorderWidth[0] - movingDistance + "px";
document.addEventListener("mouseup", () => {
selectArea.removeEventListener("mousemove", moveRight);
})
}
/**
* 向右上移动的函数
*/
const moveUpRight = (event) => {
const verticalDistance = event.y - initialPointCoordinates[1];
const horizontalDistance = event.x - initialPointCoordinates[0];
// 当垂直角度拖拽已经超过极限,则高度始终为0
selectArea.style.height = ((verticalDistance > sizeBeforeChange[1]) ? 0 : (sizeBeforeChange[1] - verticalDistance)) + "px";
// 同时对应方向的border宽度侵占了所有的高度
selectArea.style.borderTopWidth = ((verticalDistance > sizeBeforeChange[1]) ? (currentBorderWidth[0] + sizeBeforeChange[1]) : (currentBorderWidth[0] + verticalDistance)) + "px";
// 同理水平角度拖拽超过极限,则宽度始终为0
selectArea.style.width = ((horizontalDistance < -sizeBeforeChange[0]) ? 0 : (sizeBeforeChange[0] + horizontalDistance)) + "px";
// 相对应的border侵占了所有的宽度
selectArea.style.borderRightWidth = (horizontalDistance < -sizeBeforeChange[0]) ? (currentBorderWidth[1] - sizeBeforeChange[0]) : (currentBorderWidth[1] - horizontalDistance) + "px";
document.addEventListener("mouseup", () => {
selectArea.removeEventListener("mousemove", moveUpRight);
})
}
/**
* 向右下移动的函数
*/
const moveDownRight = (event) => {
const verticalDistance = event.y - initialPointCoordinates[1];
const horizontalDistance = event.x - initialPointCoordinates[0];
selectArea.style.height = ((verticalDistance < -sizeBeforeChange[1]) ? 0 : (sizeBeforeChange[1] + verticalDistance)) + "px";
selectArea.style.borderBottomWidth = ((verticalDistance < -sizeBeforeChange[1]) ? (currentBorderWidth[0] + sizeBeforeChange[1]) : (currentBorderWidth[0] - verticalDistance)) + "px";
selectArea.style.width = ((horizontalDistance < -sizeBeforeChange[0]) ? 0 : (sizeBeforeChange[0] + horizontalDistance)) + "px";
selectArea.style.borderRightWidth = (horizontalDistance < -sizeBeforeChange[0]) ? (currentBorderWidth[1] - sizeBeforeChange[0]) : (currentBorderWidth[1] - horizontalDistance) + "px";
document.addEventListener("mouseup", () => {
selectArea.removeEventListener("mousemove", moveDownRight);
})
}
/**
* 向左上移动的函数
*/
const moveUpLeft = (event) => {
const verticalDistance = event.y - initialPointCoordinates[1];
const horizontalDistance = event.x - initialPointCoordinates[0];
selectArea.style.height = ((verticalDistance > sizeBeforeChange[1]) ? 0 : (sizeBeforeChange[1] - verticalDistance)) + "px";
selectArea.style.borderTopWidth = ((verticalDistance > sizeBeforeChange[1]) ? (currentBorderWidth[0] + sizeBeforeChange[1]) : (currentBorderWidth[0] + verticalDistance)) + "px";
selectArea.style.width = ((horizontalDistance > sizeBeforeChange[0]) ? 0 : (sizeBeforeChange[0] - horizontalDistance)) + "px";
selectArea.style.borderLeftWidth = (horizontalDistance > sizeBeforeChange[0]) ? (currentBorderWidth[1] + sizeBeforeChange[0]) : (currentBorderWidth[1] + horizontalDistance) + "px";
document.addEventListener("mouseup", () => {
selectArea.removeEventListener("mousemove", moveUpLeft);
})
}
/**
* 向左下移动的函数
*/
const moveDownLeft = (event) => {
const verticalDistance = event.y - initialPointCoordinates[1];
const horizontalDistance = event.x - initialPointCoordinates[0];
selectArea.style.height = ((verticalDistance < -sizeBeforeChange[1]) ? 0 : (sizeBeforeChange[1] + verticalDistance)) + "px";
selectArea.style.borderBottomWidth = ((verticalDistance < -sizeBeforeChange[1]) ? (currentBorderWidth[0] + sizeBeforeChange[1]) : (currentBorderWidth[0] - verticalDistance)) + "px";
selectArea.style.width = ((horizontalDistance > sizeBeforeChange[0]) ? 0 : (sizeBeforeChange[0] - horizontalDistance)) + "px";
selectArea.style.borderLeftWidth = (horizontalDistance > sizeBeforeChange[0]) ? (currentBorderWidth[1] + sizeBeforeChange[0]) : (currentBorderWidth[1] + horizontalDistance) + "px";
document.addEventListener("mouseup", () => {
selectArea.removeEventListener("mousemove", moveDownLeft);
})
}
script>
body>
html>
上面引用的css如下:
body {
padding: 0;
margin: 0;
border: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 50vh;
height: 50vh;
position: relative;
}
.container>img {
width: 100%;
height: 100%;
user-select: none;
}
.select-area {
width: 40%;
height: 40%;
border: 15vh solid rgba(0, 0, 0, 0.3);
position: absolute;
top: 0;
left: 0;
}
.s-move-content-direction {
width: 7px;
height: 7px;
position: absolute;
background-color: #070707;
}
.n {
cursor: n-resize;
left: 50%;
top: 0;
transform: translateY(-90%) translateX(-50%);
}
.ne {
cursor: ne-resize;
top: 0;
right: 0;
transform: translateY(-90%) translateX(90%);
}
.e {
cursor: e-resize;
top: 50%;
right: 0;
transform: translateY(-50%) translateX(90%);
}
.se {
cursor: se-resize;
bottom: 0;
right: 0;
transform: translateY(90%) translateX(90%);
}
.s {
cursor: s-resize;
bottom: 0;
left: 50%;
transform: translateY(90%) translateX(-50%);
}
.sw {
cursor: sw-resize;
bottom: 0;
left: 0;
transform: translateY(90%) translateX(-90%);
}
.w {
cursor: w-resize;
top: 50%;
left: 0;
transform: translateY(-50%) translateX(-90%);
}
.nw {
cursor: nw-resize;
top: 0;
left: 0;
transform: translateY(-90%) translateX(-90%);
}
这样截图选中效果就实现了,那么接下来就是交给canvas坐标实现上传了。