HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片上传裁剪</title>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<div class="container">
<div class="upload">
<input type="file" id="file" accept="image/*">
<div class="clipImage">
<img id="clipImage" src="">
<img id="clipPathImg" src="">
<div id="clip">
<div class="lt"></div>
<div class="rt"></div>
<div class="rb"></div>
<div class="lb"></div>
</div>
</div>
</div>
<div class="preview">
<div class="preview-img">
<span>浏览:</span>
<img id="clipView" src="" alt="">
<button id="uploadImage">上传</button>
</div>
</div>
</div>
<script src="./index.js"></script>
<script>
let uploadImage = document.getElementById('uploadImage');
uploadImage.onclick = async function(){
if(!curCanvas){
alert('请先上传图片');
return;
}
// 获取裁剪后的图片file对象
let uploadFile = await getClipFile(curCanvas);
console.log(uploadFile);
}
</script>
</body>
</html>
CSS
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container{
width: 1000px;
display: flex;
justify-content: center;
margin: 0 auto;
}
.upload{
width: 50%;
}
.clipImage{
width: 100%;
position: relative;
}
.clipImage img{
width: 100%;
top: 0;
left: 0;
display: block;
}
.clipImage::after{
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
}
.clipImage #clipPathImg{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
-webkit-clip-path: var(--clipPath);
clip-path: var(--clipPath);
}
/* 裁剪框 */
#clip{
position: absolute;
top: 0;
left: 0;
width: var(--clipWidth);
height: var(--clipHeight);
left: var(--clipX);
top: var(--clipY);
z-index: 3;
cursor: move;
/* border: 2px solid red; */
display: none;
}
#clip .lt{
position: absolute;
width: 10px;
height: 10px;
background: #fff;
border: 1px solid #000;
left: -5px;
top: -5px;
cursor: nw-resize;
}
#clip .rt{
position: absolute;
width: 10px;
height: 10px;
background: #fff;
border: 1px solid #000;
right: -5px;
top: -5px;
cursor: ne-resize;
}
#clip .lb{
position: absolute;
width: 10px;
height: 10px;
background: #fff;
border: 1px solid #000;
left: -5px;
bottom: -5px;
cursor: sw-resize;
}
#clip .rb{
position: absolute;
width: 10px;
height: 10px;
background: #fff;
border: 1px solid #000;
right: -5px;
bottom: -5px;
cursor: se-resize;
}
/* 浏览 */
.preview{
width: 100%;
height: 100%;
display: flex;
justify-content: center;
}
JS
/**
* 上传图片并裁剪
*/
// file选择框
let file = document.getElementById('file')
// 裁剪背景图片
let clipImage = document.getElementById('clipImage')
// 裁剪使用的图片
let clipPathImg = document.getElementById('clipPathImg')
// 裁剪框
let clip = document.getElementById('clip');
// 浏览裁剪图片
let clipView = document.getElementById('clipView');
// 裁剪框的尺寸占图片比例
let clipSize = 0.7;
// 图片当前宽高
let clipImageWidth = 0;
let clipImageHeight = 0;
// 图片原始宽高
let clipImageOriginWidth = 0;
let clipImageOriginHeight = 0;
// 缩放比例
let scale = 0;
// 当前canvas对象
let curCanvas = null;
// 监听上传状态 浏览图片
file.addEventListener('change', function () {
let file = this.files[0];
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
// 读取到的图片数据
clipImage.src = reader.result;
// 裁剪使用的图片
clipPathImg.src = reader.result;
//获取当前图片的宽高
getClipImageSize(reader.result).then((res) => {
// 初始化
init();
})
}
})
// 获取上传图片的当前尺寸和原始尺寸
function getClipImageSize(dataurl){
return new Promise((resolve) => {
clipImage.onload = function () {
resolve(clipImage);
}
}).then((data) => {
// 获取图片当前宽高
clipImageWidth = data.width;
clipImageHeight = data.height;
return new Promise((resolve) => {
// 获取图片原始宽高
let img = document.createElement('img');
img.src = dataurl;
img.onload = function () {
resolve(img)
}
})
}).then((data) => {
// 获取图片原始宽高
clipImageOriginWidth = data.width;
clipImageOriginHeight = data.height;
return Promise.resolve();
})
}
// 初始化
function init(){
// 显示裁剪框
clip.style.display = 'block';
// 缩放比例
scale = clipImageOriginWidth / clipImageWidth;
// 设置裁剪框的宽高
let clipWidth = Math.floor(clipImageWidth*clipSize);
let clipHeight = Math.floor(clipImageHeight*clipSize);
setClipSize(clipWidth, clipHeight);
// 设置裁剪框的位置
let x = Math.floor((clipImageWidth - clipWidth)/2);
let y = Math.floor((clipImageHeight - clipHeight)/2);
setClipPosition(x, y);
}
// 设置裁剪框的尺寸
function setClipSize(clipWidth, clipHeight){
clip.style.setProperty('--clipWidth', clipWidth + 'px');
clip.style.setProperty('--clipHeight', clipHeight + 'px');
}
// 设置裁剪框的位置
function setClipPosition(x, y){
clip.style.setProperty('--clipX', x + 'px');
clip.style.setProperty('--clipY', y + 'px');
// 设置裁剪阴影
setClipPath();
}
// 设置裁剪阴影
function setClipPath(){
let lt = clip.offsetLeft + 'px' + ' ' + clip.offsetTop + 'px';
let rt = (clip.offsetLeft + clip.offsetWidth) + 'px' + ' ' + clip.offsetTop + 'px';
let rb = (clip.offsetLeft + clip.offsetWidth) + 'px' + ' ' + (clip.offsetTop + clip.offsetHeight) + 'px';
let lb = clip.offsetLeft + 'px' + ' ' + (clip.offsetTop + clip.offsetHeight) + 'px' ;
clipPathImg.style.setProperty('--clipPath', `polygon(${lt}, ${rt}, ${rb}, ${lb})`);
// 裁剪
let dataUrl = clipImg(clip.offsetLeft * scale, clip.offsetTop * scale, clip.offsetWidth * scale, clip.offsetHeight * scale, clip.offsetWidth, clip.offsetHeight);
// 显示裁剪后的图片
clipView.src = dataUrl;
}
// 裁剪
function clipImg(x,y,cutWidth,cutHeight,width,height,){
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
ctx.drawImage(clipImage, x, y, cutWidth, cutHeight, 0, 0, width, height);
// 当前canvas对象
curCanvas = canvas;
// 返回裁剪后的图片
return canvas.toDataURL();
}
/**
* 裁剪框的拖拽
*/
// 鼠标按下时的位置
let x = 0;
let y = 0;
// 鼠标按下时裁剪框的位置
let offsetX = 0;
let offsetY = 0;
// 偏移量
let py = {};
// 鼠标按下
clip.onmousedown = function (e) {
// 鼠标按下时的位置
x = e.clientX;
y = e.clientY;
// 鼠标按下时裁剪框的位置
offsetX = clip.offsetLeft;
offsetY = clip.offsetTop;
// 偏移量
py = {
x: e.clientX - offsetX,
y: e.clientY - offsetY
}
// 鼠标移动
window.onmousemove = function (e) {
// 鼠标移动的距离
let moveX = e.clientX - py.x;
let moveY = e.clientY - py.y;
// 裁剪框的位置
let left = moveX;
let top = moveY;
// 裁剪框的位置限制
if (left < 0) {
left = 0;
}
if (top < 0) {
top = 0;
}
if (left > clipImageWidth - clip.offsetWidth) {
left = clipImageWidth - clip.offsetWidth;
}
if (top > clipImageHeight - clip.offsetHeight) {
top = clipImageHeight - clip.offsetHeight;
}
// 设置裁剪框的位置
setClipPosition(left, top);
}
// 鼠标松开
window.onmouseup = function () {
window.onmouseup = null;
window.onmousemove = null;
}
}
/**
* 裁剪框的缩放
*/
// 获取裁剪框的四个角
let zoomDom = {
lt: document.getElementsByClassName('lt'),
rt: document.getElementsByClassName('rt'),
rb: document.getElementsByClassName('rb'),
lb: document.getElementsByClassName('lb')
};
// 鼠标按下时的位置
let zoomInfo = {
x: 0,
y: 0,
minWidth: 100,
minHeight: 100
}
// 鼠标按下时裁剪框的位置
for (let key in zoomDom) {
zoomDom[key][0].onmousedown = function (e) {
// 禁止冒泡
e.stopPropagation();
// 鼠标按下时的位置
zoomInfo.x = e.clientX;
zoomInfo.y = e.clientY;
// 鼠标移动
window.onmousemove = function (e) {
// 鼠标移动的距离
let moveX = e.clientX - zoomInfo.x;
let moveY = e.clientY - zoomInfo.y;
// 裁剪框的位置
let left = 0;
let top = 0;
// 裁剪框的尺寸
let width = 0;
let height = 0;
// 左上角
if (key === 'lt') {
// 裁剪框的位置
left = clip.offsetLeft + moveX;
top = clip.offsetTop + moveY;
// 裁剪框的尺寸
width = clip.offsetWidth - moveX;
height = clip.offsetHeight - moveY;
// 裁剪框的位置限制
if (left < 0) {
left = 0;
// 原来宽度+原来left
width = clip.offsetWidth + clip.offsetLeft;
}
if (top < 0) {
top = 0;
// 原来高度+原来top
height = clip.offsetHeight + clip.offsetTop;
}
}
// 右上角
if (key === 'rt') {
// 裁剪框的位置
top = clip.offsetTop + moveY;
left = clip.offsetLeft;
// 裁剪框的尺寸
width = clip.offsetWidth + moveX;
height = clip.offsetHeight - moveY;
// 裁剪框的位置限制
if (top < 0) {
top = 0;
// 原来高度+原来top
height = clip.offsetHeight + clip.offsetTop;
}
if(left + width > clipImageWidth){
// 图片当前宽-原来left
width = clipImageWidth - left;
}
}
// 右下角
if (key === 'rb') {
// 裁剪框的尺寸
top = clip.offsetTop;
left = clip.offsetLeft;
width = clip.offsetWidth + moveX;
height = clip.offsetHeight + moveY;
// 裁剪框的位置限制
// 当前的left+当前的width>图片当前宽
if(clip.offsetLeft + width > clipImageWidth){
width = clipImageWidth - left;
}
// 当前的top+当前的height>图片当前宽
if(clip.offsetTop + height > clipImageHeight){
height = clipImageHeight - top;
}
}
// 左下角
if (key === 'lb') {
// 裁剪框的位置
left = clip.offsetLeft + moveX;
top = clip.offsetTop;
// 裁剪框的尺寸
width = clip.offsetWidth - moveX;
height = clip.offsetHeight + moveY;
// 裁剪框的位置限制
if (left < 0) {
left = 0;
width = clip.offsetWidth + clip.offsetLeft;
}
// 当前的top+当前的height>图片当前宽
if(clip.offsetTop + height > clipImageHeight){
height = clipImageHeight - top;
}
}
// 裁剪框的限制
({width,height,left,top} = ClipWidthPosition(width, height, left, top))
// 设置裁剪框的位置
setClipPosition(left, top);
// 设置裁剪框的尺寸
setClipSize(width, height);
// 记录上一次的位置
zoomInfo.x = e.clientX;
zoomInfo.y = e.clientY;
}
// 鼠标松开
window.onmouseup = function () {
window.onmouseup = null;
window.onmousemove = null;
}
}
}
/**
* 裁剪框的宽高限制以及位置限制
*
* @param {裁剪框宽度} w
* @param {裁剪框的高度} h
* @param {裁剪框的left} l
* @param {裁剪框的top} r
* @returns {宽度,高度,left,top}
*/
function ClipWidthPosition(w, h,l,t){
let width = w;
let height = h;
let left = l;
let top = t;
if (w < zoomInfo.minWidth) {
width = zoomInfo.minWidth;
left = clip.offsetLeft;
}
if (h < zoomInfo.minHeight) {
height = zoomInfo.minHeight;
top = clip.offsetTop;
}
return {width, height, left, top};
}
// 获取file对象
async function getClipFile(canvas){
const file = await new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(new File([blob], 'clip.png', {type: 'image/png'}));
})
})
return file;
}
效果: