微信小程序 canvas 图片裁剪 旋转 插件we-cropper
比较推荐插件: https://github.com/wx-plugin/image-cropper
另一个插件
git:https://github.com/we-plugin/we-cropper
文档: https://we-plugin.github.io/we-cropper/#/
此插件只提供裁剪等功能,所以我在此插件上修改了一部分代码,添加了图片旋转功能,和图片放大功能
下载解压:
文件目录
如果在所需要裁剪的页面通过弹出窗口来使用这个功能,ios真机上面遇到一个问题:
第一次进入页面,页面无法上拉,原因查不出来,所以只好将裁剪的功能写在一个新页面中(而且本小程序需要裁剪的页面很多,所以将裁剪写成一个新的页面比较好)
pages里面新建目录cutFace
在index.wxml中代码如下:
// 引入裁剪组件
<import src="/commpents/we-cropper/we-cropper.wxml"/>
<view class='cut-img-page'>
<view class='cropper-wrapper-bg'>
<view style='height: 1rpx;'></view>
<view class="cropper-wrapper" style='margin-top: {{marTop}}px'>
<template is="we-cropper" data="{{...cropperOpt}}"/>
<view class="getCropperImage">
<view bindtap='cancleCropper'>取消</view><view bindtap='rotateImg'><image src='/images/rotate.png' class='rotate'></image></view><view bindtap="getCropperImage">确定</view>
</view>
</view>
</view>
</view>
在index.wxss中代码如下:
page {
min-height: auto;
height: auto;
}
.cropper-wrapper {
background: #fff;
}
.getCropperImage {
text-align: center;
font-size: 32rpx;
display: flex;
justify-content: space-around;
position: absolute;
bottom: 0;
width: 750rpx;
padding: 40 0rpx;
background: #000;
z-index: 9999;
color: #fff;
align-items: center;
}
.getCropperImage > view {
padding: 0 40rpx;
height: 60px;
line-height: 60px;
}
.cropper-wrapper-bg {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background: rgb(0, 0, 0);
z-index: 22;
}
.rotate {
width: 44rpx;
height: 44rpx;
}
在index.js中代码如下:
// pages/cutFace/index.js
// pages/cutFace/index.js
const WeCropper = require('../../we-cropper/we-cropper.js')
const device = wx.getSystemInfoSync() // 获取设备信息
const width = device.windowWidth // 示例为一个与屏幕等宽的正方形裁剪框
const system = device.system;
let height = device.windowHeight - 100
Page({
/**
* 页面的初始数据
*/
data: {
rotateI: 0, //旋转默认角度
cropperOpt: {
id: 'cropper',
rotateI: 0,//旋转默认角度
tranlateX: width / 2, //定义canvas 的原点
tranlateY: height / 2, //定义canvas 的原点
width, // 画布宽度
height, // 画布高度
scale: 2.5, // 最大缩放倍数
zoom: 8, // 缩放系数,
cut: {
x: -width / 2, // 裁剪框的坐标
y: -(height - (width / 1.4)) / 2, // 裁剪框的坐标
width: width, //裁剪框的大小
height: width / 1.4
}
},
chooseImg: false,
imgSrc: '',
marTop: 40
},
onLoad: function (options) {
const self = this;
//兼容可不写
const system = device.system;
if (system.indexOf('iOS') != -1) {
this.setData({
ios: true
})
};
if (system.indexOf('iOS') != -1) {
this.setData({
marTop: 45
})
} else {
this.setData({
marTop: 45
})
};
if (device.model.indexOf("iPhone X") != -1) {
this.setData({
height: wx.getStorageSync('height') * 2 + 50,
marTop: 80
})
};
// 判断来自哪个图片的裁剪 身份证、荣誉证书、营业证书等
this.setData({
cuttype: options.cuttype
})
//裁剪 插件配置
const { cropperOpt } = this.data;
new WeCropper(cropperOpt)
.on('ready', (ctx) => {
self.wecropper.updateCanvas(this.data.rotateI)
})
.on('beforeImageLoad', (ctx) => {
wx.showToast({
title: '上传中',
icon: 'loading',
duration: 20000
})
})
.on('imageLoad', (ctx) => {
wx.hideToast()
})
this.chooseImg()
},
chooseImg() {
const self = this;
wx.chooseImage({
count: 1, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success(res) {
const src = res.tempFilePaths[0]
if (src) {
// 将图片参数传递给插件
self.wecropper.pushOrign(src)
self.setData({
chooseImg: true,
imgSrc: src,
rotateI: 0
})
};
wx.hideToast()
},
fail(res) {
wx.hideToast();
wx.navigateBack()
}
})
},
touchStart(e) {
this.wecropper.touchStart(e)
},
touchMove(e) {
this.wecropper.touchMove(e)
},
touchEnd(e) {
this.wecropper.touchEnd(e)
},
// 获取裁剪后的图片
getCropperImage() {
let that = this;
if (this.data.chooseImg) {
this.wecropper.getCropperImage((src) => {
//获取上个页面的参数
let pages = getCurrentPages();
//prevPage 相当于上个页面的this,可以通过setData修改上个页面参数执行上个页面的方法等
let prevPage = pages[pages.length - 2]
if (src) {
// 身份证
if (this.data.cuttype == 1) {
prevPage.setData({
IDCard: src,
cuttype: this.data.cuttype
})
//荣誉证书
} else if (this.data.cuttype == 2) {
prevPage.setData({
hornerCard: src,
cuttype: this.data.cuttype
})
} else if (this.data.cuttype == 3) {
prevPage.setData({
licenceCard: src,
cuttype: this.data.cuttype
})
} else if (this.data.cuttype == 4) {
prevPage.setData({
certificationCard: src,
cuttype: this.data.cuttype
})
};
wx.navigateBack()
} else {
wx.hideToast()
wx.showToast({
title: '获取图片地址失败,请稍后再试!',
})
}
})
} else {
wx.showToast({
title: '您还没选择图片!',
icon: 'none'
})
}
},
cancleCropper() {
wx.hideToast()
wx.navigateBack()
},
// 图片旋转
rotateImg() {
const self = this;
let rotateI = this.data.rotateI + 1;
this.setData({
rotateI: rotateI
})
// 将旋转的角度传递给插件
self.wecropper.updateCanvas(rotateI)
}
})
上个页面(进入裁剪的那个页面)处理
在上个页面的数据处理
在data里面声明:
data: {
IDCard: '', //身份证
cuttype: null, // 图片类型
licenceCard: '',// 机构证书
certificationCard: ''//资质证书
}
点击跳转到裁剪页面时所传递的参数
//证书
getLicence() {
wx.navigateTo({
url: '/pages/cutFace/index?cuttype=3',
})
},
//证书
getCertification() {
wx.navigateTo({
url: '/pages/cutFace/index?cuttype=4',
})
},
// 身份证
IDCardFont() {
wx.navigateTo({
url: '/pages/cutFace/index?cuttype=1',
})
},
从裁剪页面退回时的判断
onShow() {
if (this.data.cuttype == 1) {
this.upLoadImg(this.data.IDCard)
};
if (this.data.cuttype == 3) {
this.upLoadImg(this.data.licenceCard)
};
if (this.data.cuttype == 4) {
this.upLoadImg(this.data.certificationCard)
};
},
//图片上传
upLoadImg(path) {
let that = this;
wx.uploadFile({
url: wx.$.host + 'api/uploadImage',
filePath: path,
name: 'files',
success: function (res) {
if (res.data) {
const lecturerInfo = that.data.lecturerInfo
let data = JSON.parse(res.data);
if (that.data.cuttype == 1) {
lecturerInfo.hand_card_one = data.path
};
if (that.data.cuttype == 3) {
lecturerInfo.licence_pic = data.path
};
if (that.data.cuttype == 4) {
lecturerInfo.certification_pic = data.path
};
app.data.lecturerInfo.data.lecturer = lecturerInfo
that.setData({
lecturerInfo: lecturerInfo,
cuttype: null//上传成功后设置为空
})
}
}
})
},
下面是插件源码的修改(能力有限,修改之后勉强能用吧,反正自己是不怎么满意)
在插件的method函数中修改
function methods() {
var self = this;
var id = self.id;
var deviceRadio = self.deviceRadio;
var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度
var ref = self.cut;
var x = ref.x; if (x === void 0) x = 0;
var y = ref.y; if (y === void 0) y = 0;
var width = ref.width; if (width === void 0) width = boundWidth;
var height = ref.height; if (height === void 0) height = boundHeight;
self.updateCanvas = function (rotateI) {
self.rotateI = rotateI ? rotateI : self.rotateI
if (self.croperTarget) {
// 画布绘制图片
self.ctx.save()
self.ctx.translate(self.tranlateX, self.tranlateY)
self.ctx.rotate(self.rotateI * 90 * Math.PI / 180)
self.ctx.drawImage(self.croperTarget, self.imgLeft, self.imgTop, self.scaleWidth, self.scaleHeight);
}
isFunction(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self);
self.ctx.restore()
self.setBoundStyle(); // 设置边界样式
self.ctx.draw();
return self
};
self.pushOrign = function (src) {
self.rotateI = 0
self.src = src;
isFunction(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self);
wx.getImageInfo({
src: src,
success: function success(res) {
var innerAspectRadio = res.width / res.height;
self.croperTarget = res.path;
if (innerAspectRadio < width / height) {
self.rectX = x;
self.baseWidth = width;
self.baseHeight = width / innerAspectRadio;
self.rectY = y - Math.abs((height - self.baseHeight) / 2);
} else {
self.rectY = y;
self.baseWidth = height * innerAspectRadio;
self.baseHeight = height;
self.rectX = x - Math.abs((width - self.baseWidth) / 2);
}
self.imgLeft = self.rectX;
self.imgTop = self.rectY;
self.scaleWidth = self.baseWidth;
self.scaleHeight = self.baseHeight;
self.updateCanvas();
isFunction(self.onImageLoad) && self.onImageLoad(self.ctx, self);
}
});
self.update();
return self
};
self.getCropperImage = function () {
var args = [], len = arguments.length;
while (len--) args[len] = arguments[len];
var ARG_TYPE = toString.call(args[0]);
var fn = args[args.length - 1];
switch (ARG_TYPE) {
case '[object Object]':
var ref = args[0];
var quality = ref.quality; if (quality === void 0) quality = 10;
if (typeof (quality) !== 'number') {
console.error(("quality:" + quality + " is invalid"));
} else if (quality < 0 || quality > 10) {
console.error("quality should be ranged in 0 ~ 10");
}
wx.canvasToTempFilePath({
canvasId: id,
x: x,
y: -y,
width: width,
height: height,
destWidth: width * quality / (deviceRadio * 10),
destHeight: height * quality / (deviceRadio * 10),
success: function success(res) {
isFunction(fn) && fn.call(self, res.tempFilePath);
},
fail: function fail(res) {
isFunction(fn) && fn.call(self, null);
}
}); break
case '[object Function]':
wx.canvasToTempFilePath({
canvasId: id,
x: x,
y: -y,
width: width,
height: height,
destWidth: width / deviceRadio,
destHeight: height / deviceRadio,
success: function success(res) {
isFunction(fn) && fn.call(self, res.tempFilePath);
},
fail: function fail(res) {
isFunction(fn) && fn.call(self, null);
}
}); break
}
return self
};
}
对照图
update 函数
function update() {
var self = this;
if (!self.src) { return }
self.__oneTouchStart = function (touch) {
self.touchX0 = Math.round(touch.x);
self.touchY0 = Math.round(touch.y);
};
self.__oneTouchMove = function (touch) {
var xMove, yMove;
// 计算单指移动的距离
if (self.touchended) {
return self.updateCanvas()
}
xMove = Math.round(touch.x - self.touchX0);
yMove = Math.round(touch.y - self.touchY0);
if (self.rotateI % 4 == 0) {
var imgLeft = Math.round(self.rectX + xMove);
var imgTop = Math.round(self.rectY + yMove);
} else if (self.rotateI % 4 == 1) {
var imgLeft = Math.round(self.rectX + yMove);
var imgTop = Math.round(self.rectY - xMove);
} else if (self.rotateI % 4 == 2) {
var imgLeft = Math.round(self.rectX - xMove);
var imgTop = Math.round(self.rectY - yMove);
} else if (self.rotateI % 4 == 3) {
var imgLeft = Math.round(self.rectX - yMove);
var imgTop = Math.round(self.rectY + xMove);
}
self.outsideBound(imgLeft, imgTop);
self.updateCanvas();
};
对照图
outsideBound函数
self.outsideBound = function (imgLeft, imgTop) {
self.imgLeft = imgLeft
self.imgTop = imgTop
// self.imgLeft = imgLeft >= x
// ? x
// : self.scaleWidth + imgLeft - x <= width
// ? x + width - self.scaleWidth
// : imgLeft;
// self.imgTop = imgTop >= y
// ? y
// : self.scaleHeight + imgTop - y <= height
// ? y + height - self.scaleHeight
// : imgTop;
// console.log(imgLeft)
};
对照图
__twoTouchMove函数,原插件只提供了图片放大,没有图片缩小功能,这里小改了一下
self.__twoTouchMove = function (touch0, touch1) {
var oldScale = self.oldScale;
var oldDistance = self.oldDistance;
var scale = self.scale;
var zoom = self.zoom;
self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1);
// 设定缩放范围
self.newScale <= 0.2 && (self.newScale = oldScale - 0.001 * zoom * (newDistance - oldDistance));
self.newScale >= scale && (self.newScale = scale);
self.scaleWidth = Math.round(self.newScale * self.baseWidth);
self.scaleHeight = Math.round(self.newScale * self.baseHeight);
var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2);
var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2);
self.outsideBound(imgLeft, imgTop);
self.updateCanvas();
};
对照图
选择图片不居中时需要微调,微调地方
最后附上demo的git地址: https://github.com/UprightCedar/we-cropper-demo-cut-img