1.新建公共组件components / cropping / cropping.vue 将代码复制进去
复制代码放入cropping.vue
<template name="yq-avatar">
<view class="croppage">
<canvas canvas-id="avatar-canvas" id="avatar-canvas" class="my-canvas" :style="{top: sT, height: csH}"
:disable-scroll="false"></canvas>
<canvas canvas-id="oper-canvas" id="oper-canvas" class="oper-canvas" :style="{top: sT1, height: csH1}"
:disable-scroll="false" @touchstart="fStart" @touchmove="fMove" @touchend="fEnd">
</canvas>
<canvas canvas-id="prv-canvas" id="prv-canvas" class="prv-canvas" :disable-scroll="false" @touchstart="fHideImg"
:style="{ height: csH, top: pT }">
</canvas>
<view class="oper-wrapper" :style="{top:tp}">
<view class="oper">
<view class="btn-wrapper">
<view @click="rechoose" hover-class="hover" :style="{width: bW}"><text>重选</text></view>
<view @click="fUpload" hover-class="hover" :style="{width: bW}"><text>确定</text></view>
</view>
</view>
</view>
</view>
</template>
<script>
const tH = 50;
export default {
data() {
return {
sT1num: 0,
csH1: '0px',
sT1: 0,
csH: '0px',
sD: 'none',
sT: '-10000px',
pT: '-10000px',
iS: {},
sS: {},
bW: '19%',
bD: 'flex',
tp: 0,
};
},
props: {
avatarSrc: '',
avatarStyle: '',
selWidth: '',
selHeight: '',
expWidth: '',
expHeight: '',
minScale: '',
maxScale: '',
canScale: '',
canRotate: '',
lockWidth: '',
lockHeight: '',
stretch: '',
lock: '',
fileType: '',
noTab: '',
inner: '',
quality: '',
index: '',
bgImage: '',
},
created() {
this.cc = uni.createCanvasContext('avatar-canvas', this);
this.cco = uni.createCanvasContext('oper-canvas', this);
this.ccp = uni.createCanvasContext('prv-canvas', this);
this.qlty = parseFloat(this.quality) || 1; //画质
this.letRotate = (this.canRotate === false || this.inner === true || this.inner === 'true' || this
.canRotate === 'false') ? 0 : 1; //是否旋转
this.letScale = (this.canScale === false || this.canScale === 'false') ? 0 : 1; //是否放大
this.isin = (this.inner === true || this.inner === 'true') ? 1 : 0;
this.indx = this.index || undefined; //禁止旋转并在图片范围内移动
this.mnScale = parseFloat(this.minScale) || 0.3; //缩小比例
this.mxScale = parseFloat(this.maxScale) || 4; //放大比列
this.noBar = (this.noTab === true || this.noTab === 'true') ? 1 : 0; //是否存在nobar
this.stc = this.stretch; //自动铺满
this.lck = this.lock; //锁定图方向 x y long short longSel shortSel
this.fType = this.fileType === 'jpg' ? 'jpg' : 'png';
if (this.isin || !this.letRotate) {
this.bW = '24%';
this.bD = 'none';
} else {
this.bW = '19%';
this.bD = 'flex';
}
if (this.noBar) {
this.fWindowResize();
} else {
uni.showTabBar({
fail: () => {
this.noBar = 1;
},
success: () => {
this.noBar = 0;
},
complete: (res) => {
this.fWindowResize();
}
});
}
},
methods: {
//重选
rechoose() {
const that = this
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ["album"],
success: (res) => {
that.sT1 = that.sT
that.csH1 = that.csH
that.fSelect(res.tempFilePaths[0])
}
});
},
//初始化各种数据
fWindowResize() {
let sysInfo = uni.getSystemInfoSync();
this.platform = sysInfo.platform;
this.wW = sysInfo.windowWidth;
// #ifndef H5
let phone = uni.getSystemInfoSync().platform
if (phone == 'ios') {
let h = uni.upx2px(60); //将rpx单位值转换成px 裁剪框自己
let b = uni.upx2px(100); //底部按钮
this.sT1 = (sysInfo.windowHeight - h - b) / 2 + 'px' //裁剪框距离顶部+px
this.sT1num = (sysInfo.windowHeight - h - b) / 2 //裁剪框距离顶部不+px
} else {
this.drawTop = 0;
}
// #endif
// #ifndef MP-ALIPAY
this.wH = sysInfo.windowHeight; //设备高
if (!this.noBar) this.wH += tH; //th=50
this.csH = this.wH - tH + 'px'; //高=设备高-50(导航)
if (phone == 'ios') { //适配ios
this.csH1 = (this.wH - 50) / 2 + 'px'
} else {
this.csH1 = this.wH - tH + 'px';
}
// #endif
this.tp = this.csH;
this.pxRatio = this.wW / 750; //设备宽/750 比列
this.expWidth && (this.eW = this.expWidth.toString().indexOf('upx') >= 0 ? parseInt(this.expWidth) * this
.pxRatio : parseInt(this.expWidth));
this.expHeight && (this.eH = this.expHeight.toString().indexOf('upx') >= 0 ? parseInt(this.expHeight) *
this.pxRatio : parseInt(this.expHeight));
this.fHideImg();
},
fSelect(r) {
if (this.fSelecting) return;
this.fSelecting = true;
setTimeout(() => {
this.fSelecting = false;
}, 500); //防抖
let path = this.imgPath = r; //需要剪裁的图片路径
// 获取图片信息
uni.getImageInfo({
src: path,
success: r => {
this.imgWidth = r.width;
this.imgHeight = r.height;
this.path = path;
if (!this.hasSel) {
let style = this.sS || {};
if (this.selWidth && this.selHeight) { //设置的剪裁区域宽高
let sW = this.selWidth.toString().indexOf('upx') >= 0 ?
parseInt(this.selWidth) * this.pxRatio : parseInt(
this.selWidth),
sH = this.selHeight.toString().indexOf('upx') >= 0 ?
parseInt(this.selHeight) * this.pxRatio : parseInt(
this.selHeight);
style.width = sW + 'px';
style.height = sH + 'px';
style.top = ((this.wH - sH - tH) | 0) / 2 + 'px';
style.left = ((this.wW - sW) | 0) / 2 + 'px';
// }
} else {
uni.showModal({
title: '裁剪框的宽或高没有设置',
showCancel: false
})
return;
}
this.sS = style;
}
if (this.noBar) {
setTimeout(() => {
this.fDrawInit(true);
}, 200)
} else {
uni.hideTabBar({
complete: () => {
setTimeout(() => {
this.fDrawInit(true);
}, 200)
}
});
}
},
fail: () => {
uni.showToast({
title: "请选择正确图片",
duration: 2000,
})
},
complete() {
uni.hideLoading();
}
});
},
//剪裁确定
fUpload() {
uni.showLoading({
title: '图片上传中...',
mask: true
});
if (this.fUploading) return;
this.fUploading = true;
setTimeout(() => {
this.fUploading = false;
}, 1000)
let style = this.sS,
x = parseInt(style.left),
y = parseInt(style.top),
width = parseInt(style.width),
height = parseInt(style.height),
expWidth = this.eW || (width * this.pixelRatio),
expHeight = this.eH || (height * this.pixelRatio);
this.fHideImg();
// #ifndef MP-ALIPAY
let phone = uni.getSystemInfoSync().platform
if (phone == 'ios') {
y = this.sT1num
}
uni.canvasToTempFilePath({
x: x,
y: y,
width: width,
height: height,
destWidth: expWidth,
destHeight: expHeight,
canvasId: 'avatar-canvas',
fileType: this.fType,
quality: this.qlty,
success: (r) => {
r = r.tempFilePath;
// #ifndef H5
this.$emit("upload", {
avatar: this.imgSrc,
path: r,
index: this.indx,
data: this.rtn,
base64: this.base64 || null
});
// #endif
},
fail: (res) => {
uni.showToast({
title: "error1",
duration: 2000,
})
},
complete: () => {
uni.hideLoading();
this.noBar || uni.showTabBar();
this.$emit("end");
}
}, this);
// #endif
},
fDrawInit(ini = false) {
let allWidth = this.wW, //设备宽
allHeight = this.wH, //设备高
imgWidth = this.imgWidth, //图宽
imgHeight = this.imgHeight, //图高
imgRadio = imgWidth / imgHeight, //图比
useWidth = allWidth - 40, //设备宽-40
useHeight = allHeight - tH - 80, //设备高-80
useRadio = useWidth / useHeight,
sW = parseInt(this.sS.width), //图片信息
sH = parseInt(this.sS.height);
this.fixWidth = 0;
this.fixHeight = 0;
this.lckWidth = 0;
this.lckHeight = 0;
switch (this.stc) { //以下是自动铺满的算法
case 'x':
this.fixWidth = 1;
break;
case 'y':
this.fixHeight = 1;
break;
case 'long':
if (imgRadio > 1) this.fixWidth = 1;
else this.fixHeight = 1;
break;
case 'short':
if (imgRadio > 1) this.fixHeight = 1;
else this.fixWidth = 1;
break;
case 'longSel':
if (sW > sH) this.fixWidth = 1;
else this.fixHeight = 1;
break;
case 'shortSel':
if (sW > sH) this.fixHeight = 1;
else this.fixWidth = 1;
break;
}
switch (this.lck) { //以下锁定屏幕的移动方向
case 'x':
this.lckWidth = 1;
break;
case 'y':
this.lckHeight = 1;
break;
case 'long':
if (imgRadio > 1) this.lckWidth = 1;
else this.lckHeight = 1;
break;
case 'short':
if (imgRadio > 1) this.lckHeight = 1;
else this.lckWidth = 1;
break;
case 'longSel':
if (sW > sH) this.lckWidth = 1;
else this.lckHeight = 1;
break;
case 'shortSel':
if (sW > sH) this.lckHeight = 1;
else this.lckWidth = 1;
break;
}
if (this.fixWidth) {
useWidth = sW;
useHeight = useWidth / imgRadio;
} else if (this.fixHeight) {
useHeight = sH;
useWidth = useHeight * imgRadio;
} else if (imgRadio < useRadio) {
if (imgHeight < useHeight) {
useWidth = imgWidth;
useHeight = imgHeight;
} else {
useWidth = useHeight * imgRadio;
}
} else {
if (imgWidth < useWidth) {
useWidth = imgWidth;
useHeight = imgHeight;
} else {
useHeight = useWidth / imgRadio;
}
}
if (this.isin) {
if (useWidth < sW) {
useWidth = sW;
useHeight = useWidth / imgRadio;
this.lckHeight = 0;
}
if (useHeight < sH) {
useHeight = sH;
useWidth = useHeight * imgRadio;
this.lckWidth = 0;
}
}
this.scaleSize = 1;
this.rotateDeg = 0;
this.posWidth = (allWidth - useWidth) / 2 | 0;
this.posHeight = (allHeight - useHeight - tH) / 2 | 0;
this.useWidth = useWidth | 0;
this.useHeight = useHeight | 0;
this.centerX = this.posWidth + useWidth / 2;
this.centerY = this.posHeight + useHeight / 2;
this.focusX = 0;
this.focusY = 0;
let style = this.sS,
left = parseInt(style.left),
top = parseInt(style.top),
width = parseInt(style.width),
height = parseInt(style.height),
canvas = this.canvas,
canvasOper = this.canvasOper,
cc = this.cc, //avatar-canvas
cco = this.cco; //oper-canvas 裁剪
cco.beginPath(); //开始创建一个路径
cco.setLineWidth(3); //设置线条的宽度 px
cco.setGlobalAlpha(1); //设置全局画笔透明度。
cco.setStrokeStyle('white'); //设置边框颜色。如果没有设置 fillStyle,默认颜色为 black。
cco.strokeRect(left, top, width, height); //画一个矩形(非填充)。用 setFillStroke() 设置边框颜色,如果没设置默认是黑色。
cco.setFillStyle('black');
cco.setGlobalAlpha(0.5);
cco.fillRect(0, 0, this.wW, top); //填充一个矩形
cco.fillRect(0, top, left, height);
cco.fillRect(0, top + height, this.wW, this.wH - height - top - tH);
cco.fillRect(left + width, top, this.wW - width - left, height);
cco.setGlobalAlpha(1);
cco.setStrokeStyle('red');
cco.moveTo(left + 15, top); //把路径移动到画布中的指定点,不创建线条。用 stroke() 方法来画线条。
cco.lineTo(left, top); //增加一个新点,然后创建一条从上次指定点到目标点的线。
cco.lineTo(left, top + 15);
cco.moveTo(left + width - 15, top);
cco.lineTo(left + width, top);
cco.lineTo(left + width, top + 15);
cco.moveTo(left + 15, top + height);
cco.lineTo(left, top + height);
cco.lineTo(left, top + height - 15);
cco.moveTo(left + width - 15, top + height);
cco.lineTo(left + width, top + height);
cco.lineTo(left + width, top + height - 15);
cco.stroke(); //画线条
cco.draw(false, () => { //将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中。
if (ini) {
this.sT = this.drawTop + 'px';
this.fDrawImage(true); //绘制背景色
}
});
this.$emit("init");
},
//绘制背景色
fDrawImage(ini = false) {
let tm_now = Date.now(); //当前时间
if (tm_now - this.drawTm < 20) return;
this.drawTm = tm_now;
let cc = this.cc,
imgWidth = this.useWidth * this.scaleSize,
imgHeight = this.useHeight * this.scaleSize;
if (this.bgImage) { //如果背景图
// #ifndef MP-ALIPAY
cc.drawImage(this.bgImage, 0, 0, this.wW, this.wH - tH); //绘制图像到画布。
// #endif
} else {
cc.fillRect(0, 0, this.wW, this.wH - tH); //填充一个矩形
}
if (this.isin) { //禁止旋转并在图片范围内移动
let cx = this.focusX * (this.scaleSize - 1),
cy = this.focusY * (this.scaleSize - 1);
cc.translate(this.centerX, this.centerY);
cc.rotate(this.rotateDeg * Math.PI / 180);
cc.drawImage(this.imgPath, this.posWidth - this.centerX - cx, this.posHeight - this.centerY - cy,
imgWidth, imgHeight);
} else {
cc.translate(this.posWidth + imgWidth / 2, this.posHeight + imgHeight / 2);
cc.rotate(this.rotateDeg * Math.PI / 180);
cc.drawImage(this.imgPath, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
}
cc.draw(false);
},
//旋转
fRotate() {
this.rotateDeg += 90 - this.rotateDeg % 90;
this.fDrawImage();
},
//手指触摸开始
fStart(e) {
let touches = e.touches,
touch0 = touches[0],
touch1 = touches[1];
this.touch0 = touch0;
this.touch1 = touch1;
if (touch1) {
let x = touch1.x - touch0.x,
y = touch1.y - touch0.y;
this.fgDistance = Math.sqrt(x * x + y * y);
}
},
//手指触摸后移动
fMove(e) {
let touches = e.touches,
touch0 = touches[0],
touch1 = touches[1];
if (touch1) {
let x = touch1.x - touch0.x,
y = touch1.y - touch0.y,
fgDistance = Math.sqrt(x * x + y * y),
scaleSize = 0.005 * (fgDistance - this.fgDistance),
beScaleSize = this.scaleSize + scaleSize;
do {
if (!this.letScale) break;
if (beScaleSize < this.mnScale) break;
if (beScaleSize > this.mxScale) break;
let growX = this.useWidth * scaleSize / 2,
growY = this.useHeight * scaleSize / 2;
if (this.isin) {
let imgWidth = this.useWidth * beScaleSize,
imgHeight = this.useHeight * beScaleSize,
l = this.posWidth - growX,
t = this.posHeight - growY,
r = l + imgWidth,
b = t + imgHeight,
left = parseInt(this.sS.left),
top = parseInt(this.sS.top),
width = parseInt(this.sS.width),
height = parseInt(this.sS.height),
right = left + width,
bottom = top + height,
cx, cy;
if (imgWidth <= width || imgHeight <= height) break;
this.cx = cx = this.focusX * beScaleSize - this.focusX,
this.cy = cy = this.focusY * beScaleSize - this.focusY;
this.posWidth -= growX;
this.posHeight -= growY;
if (this.posWidth - cx > left) {
this.posWidth = left + cx;
}
if (this.posWidth + imgWidth - cx < right) {
this.posWidth = right - imgWidth + cx;
}
if (this.posHeight - cy > top) {
this.posHeight = top + cy;
}
if (this.posHeight + imgHeight - cy < bottom) {
this.posHeight = bottom - imgHeight + cy;
}
} else {
this.posWidth -= growX;
this.posHeight -= growY;
}
this.scaleSize = beScaleSize;
} while (0);
this.fgDistance = fgDistance;
if (touch1.x !== touch0.x && this.letRotate) {
x = (this.touch1.y - this.touch0.y) / (this.touch1.x - this.touch0.x);
y = (touch1.y - touch0.y) / (touch1.x - touch0.x);
this.rotateDeg += Math.atan((y - x) / (1 + x * y)) * 180 / Math.PI;
this.touch0 = touch0;
this.touch1 = touch1;
}
this.fDrawImage();
} else if (this.touch0) {
let x = touch0.x - this.touch0.x,
y = touch0.y - this.touch0.y,
beX = this.posWidth + x,
beY = this.posHeight + y;
if (this.isin) {
let imgWidth = this.useWidth * this.scaleSize,
imgHeight = this.useHeight * this.scaleSize,
l = beX,
t = beY,
r = l + imgWidth,
b = t + imgHeight,
left = parseInt(this.sS.left),
top = parseInt(this.sS.top),
right = left + parseInt(this.sS.width),
bottom = top + parseInt(this.sS.height),
cx, cy;
this.cx = cx = this.focusX * this.scaleSize - this.focusX;
this.cy = cy = this.focusY * this.scaleSize - this.focusY;
if (!this.lckWidth && Math.abs(x) < 100) {
if (left < l - cx) {
this.posWidth = left + cx;
} else if (right > r - cx) {
this.posWidth = right - imgWidth + cx;
} else {
this.posWidth = beX;
this.focusX -= x;
}
}
if (!this.lckHeight && Math.abs(y) < 100) {
if (top < t - cy) {
this.focusY -= (top + cy - this.posHeight);
this.posHeight = top + cy;
} else if (bottom > b - cy) {
this.focusY -= (bottom + cy - (this.posHeight + imgHeight));
this.posHeight = bottom - imgHeight + cy;
} else {
this.posHeight = beY;
this.focusY -= y;
}
}
} else {
if (Math.abs(x) < 100 && !this.lckWidth) this.posWidth = beX;
if (Math.abs(y) < 100 && !this.lckHeight) this.posHeight = beY;
this.focusX -= x;
this.focusY -= y;
}
this.touch0 = touch0;
this.fDrawImage();
}
},
//手指触摸动作结束
fEnd(e) {
let touches = e.touches,
touch0 = touches && touches[0],
touch1 = touches && touches[1];
if (touch0) {
this.touch0 = touch0;
} else {
this.touch0 = null;
this.touch1 = null;
}
},
fHideImg() {
this.prvImg = '';
this.pT = '-10000px';
this.prvImgData = null;
this.target = null;
},
}
}
</script>
<style>
.croppage {
width: 100%;
height: 100%;
box-sizing: border-box;
background-color: #000000;
position: relative;
}
.my-canvas {
/* display: flex; */
position: absolute !important;
left: 0;
z-index: 100000;
width: 100%;
}
.my-avatar {
width: 150upx;
height: 150upx;
border-radius: 100%;
}
.oper-canvas {
/* display: flex; */
position: absolute !important;
left: 0;
z-index: 100000;
width: 100%;
}
.prv-canvas {
display: flex;
position: fixed !important;
background: #000000;
left: 0;
z-index: 200000;
width: 100%;
}
.oper-wrapper {
height: 100rpx;
position: fixed !important;
box-sizing: border-box;
border: 1px solid #F1F1F1;
background: #ffffff;
width: 100%;
left: 0;
bottom: 0;
z-index: 99999999;
flex-direction: row;
}
.oper {
display: flex;
flex-direction: column;
justify-content: center;
padding: 10upx 20upx;
width: 100%;
height: 100%;
box-sizing: border-box;
align-self: center;
}
.btn-wrapper {
display: flex;
flex-direction: row;
flex-grow: 1;
height: 50px;
justify-content: space-between;
}
.btn-wrapper view {
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #3b7ffb;
border-radius: 6%;
}
.hover {
background: #f1f1f1;
border-radius: 6%;
}
.clr-wrapper {
display: flex;
flex-direction: row;
flex-grow: 1;
}
.clr-wrapper view {
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #333;
border: 1px solid #f1f1f1;
border-radius: 6%;
}
.my-slider {
flex-grow: 1;
}
</style>
2.在需要使用的页面引入组件
<template>
<view>
<Cropping @upload="doUpload" ref="cropping" :selWidth="screenWidth" selHeight="60rpx" />
</view>
</template>
<script>
import Cropping from "@/components/cropping/cropping.vue";
export default {
components: {
Cropping
},
data() {
return {
result:'',
screenWidth: ''
}
},
onLoad(option) {
let getWindowInfo = uni.getWindowInfo()
this.screenWidth = getWindowInfo.screenWidth
//screenWidth定义需要裁剪的宽度 selHeight为高度 自行定义传入
this.$nextTick(()=>{
//调用组件的fSelect方法传入需要裁剪的图片路径
//option.img为需要裁剪的图片路径 自行定义传入
this.$refs.cropping.fSelect(option.img)
})
},
methods: {
//剪裁确认
doUpload(rsp) {
console.log(rsp)
//rsp.path为截取的图片路径
},
}
}
</script>