网上有很多前端剪裁图片的demo,但是很多都需要各种插件,我的想法很简单,什么插件都不用,就原生的JS剪裁,然后传回Python后台行不行?找遍网络无觅处,看来还得靠自己。
本文得到了https://blog.csdn.net/qq_33466661/article/details/104899474的启发,在此向这位博主表示感谢!本文在上文的基础上实现了上面这位博主没有实现的功能,例如按需要调整剪裁框的大小,用Python后台接收前端的图片,传图片的同时也传其他参数等。
废话不多说,直接上代码。下面这段代码直接保存成html双击即可运行,只需这一个html文件就搞定。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
.pic-cropper{
border:1px solid #099;
width: 600px;
height: 1040px;
margin:auto;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
}
#submit {
margin-top: 10px;
margin-bottom: 10px;
}
#export {
margin-top: 10px;
margin-bottom: 10px;
}
#result-canvas {
border:1px solid #CCCCCC
}
#image-canvas {
border:1px solid #CCCCCC
}
</style>
<title>图片上传</title>
</head>
<body>
<div style="text-align:center;padding:10px"><input id="img-input" type="file"/></div>
<div class="pic-cropper">
<input id="slider" type="range"/>
<canvas id="image-canvas" width="500" height="500"></canvas>
<button id="submit">预览</button>
<canvas id="result-canvas" width="400" height="400"></canvas>
<button id="export">确定</button>
</div>
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
<script>
var imgInput = document.getElementById("img-input");
var submitBtn = document.getElementById("submit");
var exportBtn = document.getElementById("export");
var mySlider = document.getElementById("slider");
var resultCanvas = document.getElementById("result-canvas");
var resultCxt = resultCanvas.getContext("2d");
var imgCanvas = document.getElementById("image-canvas");
var imgCxt = imgCanvas.getContext("2d");
var currentFactor = 1; //当前的放缩倍数
var minFactor = 1; //最小放缩倍数
var maxFactor = 1; //最大放缩倍数
var ori = document.getElementById("image-canvas").height;
var cut = document.getElementById("result-canvas").height;
console.log(ori);
console.log(cut);
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
// const imgUrl = "https://udi-official-website.oss-cn-shenzhen.aliyuncs.com/0/rc-upload-1584349106386-2";
imgInput.onchange = function() {
console.log("======",imgInput.files[0]);
var imgUrl = URL.createObjectURL(imgInput.files[0]);
imgCanvas.setAttribute('height', ori);
resultCanvas.setAttribute('height',cut);
drawMask();
getImage(imgUrl);
// var fileReader = new FileReader();
// fileReader.readAsBinaryString(imgInput.files[0]);
}
//获取图像并画出来
getImage = function(imgUrl) {
// img.src = './changtuxiang.jpg'
img.src=imgUrl;
img.onload = function() {
calculateFactor();
var originPosition = canvasOriginPosition();
imgCxt.globalCompositeOperation = "destination-over";
imgCxt.drawImage(img,originPosition.positionX,originPosition.positionY,img.width*currentFactor,img.height*currentFactor);
}
}
//画蒙版
drawMask = function() {
imgCxt.fillStyle = "rgba(0,0,0,0.5)";
imgCxt.fillRect(0,0,ori,ori);
imgCxt.fillStyle = "rgba(0,0,0,1)";
imgCxt.globalCompositeOperation = "destination-out";
imgCxt.fillRect(50,50,cut,cut);
}
//控制canvas拖动
var moving = false;
var lastmouseX = null;
var lastmouseY = null;
//上次移动结束,canvas画图的位置
var lastImgX = 0;
var lastImgY = 0;
imgCanvas.onmousedown=function(e) {
// console.log("onmousedown",lastImgX,lastImgY);
moving = true;
lastmouseX = e.clientX;
lastmouseY = e.clientY;
}
window.onmouseup = function(e) {
if(moving === true) {
moving = false;
var newPosition = checkPosition(e.clientX - lastmouseX,e.clientY - lastmouseY)
//每次移动完成都要改变一下上次图片绘制位置
lastImgX = newPosition.X;
lastImgY = newPosition.Y;
// console.log("onmouseup",lastImgX,lastImgY);
}
}
redrawImg = function(pX,pY) {
//每次绘图前检查position是否没有超出区域
var newPosition = checkPosition(pX,pY)
imgCanvas.setAttribute('height', ori);
drawMask();
imgCxt.globalCompositeOperation = "destination-over";
imgCxt.drawImage(img,newPosition.X,newPosition.Y,img.width*currentFactor,img.height*currentFactor);
}
checkPosition = function(pX,pY) {
if(pX+lastImgX<=50&&pX+lastImgX>=cut+50-img.width*currentFactor&&pY+lastImgY<=50&&pY+lastImgY>=cut+50-img.height*currentFactor) {
return ({"X":pX+lastImgX,"Y":pY+lastImgY});
} else {
var x_ = pX+lastImgX;
var y_ = pY+lastImgY;
if(pX+lastImgX>50) {
x_ = 50;
}
if(pX+lastImgX<cut+50-img.width*currentFactor) {
x_ = cut+50-img.width*currentFactor;
}
if(pY+lastImgY>50) {
y_ = 50;
}
if(pY+lastImgY<cut+50-img.height*currentFactor) {
y_ = cut+50-img.height*currentFactor;
}
return({"X":x_,"Y":y_});
}
}
// 计算初始Factor
calculateFactor = function() {
if(img.width<img.height) {
minFactor = cut/img.width;//初始状态
currentFactor = cut/img.width;
maxFactor = cut*2/img.width;
} else {
minFactor = cut/img.height;
currentFactor = cut/img.height;
maxFactor = cut*2/img.height;
}
// console.log(currentFactor,minFactor,maxFactor);
initSlider();
}
//计算初始画布位置
canvasOriginPosition = function() {
calculateFactor();
var positionX;
var positionY;
if(img.width<img.height) {
positionX = 50;
positionY = 50 - (img.height*currentFactor - cut) /2;
} else {
positionX = 50 - (img.width*currentFactor - cut) /2;
positionY = 50;
}
lastImgX = positionX;
lastImgY = positionY;
return({"positionX":positionX,"positionY":positionY});
}
window.onmousemove = function(e) {
if(moving) {
pX=e.clientX - lastmouseX;
pY=e.clientY - lastmouseY;
// console.log(checkPosition(pX,pY));
redrawImg(pX,pY);
}
}
//初始滑动条
initSlider = function() {
mySlider.min = minFactor;
mySlider.max = maxFactor;
// mySlider.value = currentFactor;
mySlider.step = 0.01;
}
mySlider.oninput = function() {
currentFactor = mySlider.value;
//每次缩放完也都要修改图片的绘制位置
// console.log("oninput",mySlider.value,lastImgX,lastImgY);
redrawImg(0,0);
}
submitBtn.onclick = function() {
// console.log("onclick!!",lastImgX,lastImgY);
resultCxt.drawImage(img,lastImgX-50,lastImgY-50,img.width*currentFactor,img.height*currentFactor)
}
//下载图片
exportBtn.onclick = function() {
var url = resultCanvas.toDataURL('image/png', 0.92);
var resultLink = document.createElement("a");
resultLink.download = "裁剪结果";
resultLink.href = url;
document.body.appendChild(resultLink);
resultLink.click();
resultLink.remove();
}
</script>
</body>
</html>
上面这段代码可以实现点击确定后即可在浏览器中下载剪裁好的照片。但是有人说我不想下载,我想传回Python后台怎么办?没问题,你只需要把下面这段下载图片的代码换成传回后台的代码即可。
//下载图片
exportBtn.onclick = function() {
var url = resultCanvas.toDataURL('image/png', 0.92);
var resultLink = document.createElement("a");
resultLink.download = "裁剪结果";
resultLink.href = url;
document.body.appendChild(resultLink);
resultLink.click();
resultLink.remove();
}
换成:
//上传到服务器
exportBtn.onclick = function() {
var url = resultCanvas.toDataURL('image/jpg', 0.92);
var arr = url.split(',');
var mime = arr[0].match(/:(.*?);/)[1];
var bstr = atob(arr[1]);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
var obj = new Blob([u8arr], {type:mime});
var fd = new FormData();
fd.append("upfile", obj,"image.jpg");
console.log(typeof(fd));
$.ajax({
url: "/huichuan/",
type: "POST",
processData: false,
contentType: false,
data: fd,
success: function (response) {
console.log(response);
}
})
}
Python后台用flask实现的,代码是:
@app.route('/huichuan/',methods=['POST','GET'])
def huichuan():
imgData = request.files.get('upfile')
imgData.save('./static/images/剪裁.jpg')
return "成功回传了!"
另外,如果你想在传图片的同时也传各种文本参数怎么办呢?没问题,上面那段再换成这段即可。
//上传到服务器并带参数
exportBtn.onclick = function() {
var xuexiao = "清华大学"
var zhuanye = "计算机科学"
var url = resultCanvas.toDataURL('image/jpg', 0.92);
var arr = url.split(',');
var mime = arr[0].match(/:(.*?);/)[1];
var bstr = atob(arr[1]);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
var obj = new Blob([u8arr], {type:mime});
console.log(obj.size);
var fd = new FormData();
fd.append("upfile", obj,"image.jpg");
fd.append("xuexiao", xuexiao);
fd.append("zhuanye", zhuanye);
$.ajax({
url: "/huichuan/",
type: "POST",
processData: false,
contentType: false,
data: fd,
success: function (response) {
console.log(response);
}
})
}
相应地,加上其他文本参数后,Python后台的代码为:
@app.route('/huichuan/',methods=['POST','GET'])
def guanguan6():
imgData = request.files.get('upfile')
imgData.save('./static/images/剪裁.jpg')
item={}
item["xuexiao"] = request.form.get("xuexiao")
item["zhuanye"] = request.form.get("zhuanye")
print(item)
return "成功"
简洁有效的代码讨人喜欢!