1108 lines
32 KiB
Plaintext
1108 lines
32 KiB
Plaintext
<template>
|
||
<view class="t-cropper" v-show="imageUrl" disable-scroll>
|
||
<!-- #ifdef MP-WEIXIN || MP-ALIPAY-->
|
||
<canvas
|
||
type="2d"
|
||
canvas-id="canvas-cropper"
|
||
id="canvas-cropper"
|
||
class="canvas"
|
||
></canvas>
|
||
<!-- #endif -->
|
||
|
||
<!-- #ifdef APP-PLUS || H5 -->
|
||
<canvas
|
||
:canvas-id="canvasId"
|
||
:style="{
|
||
width: origin.canvasWidth + 'px',
|
||
height: origin.canvasHeight + 'px',
|
||
}"
|
||
></canvas>
|
||
<!-- #endif -->
|
||
<!-- 裁剪区域 -->
|
||
<view class="t-preview-container" :class="{ showPage: inInit }">
|
||
<view
|
||
class="preview-body"
|
||
@touchstart="(e) => touchStart(e, 'body')"
|
||
@touchmove="touchMove"
|
||
@touchend="touchEnd"
|
||
@touchcancel="touchCancel"
|
||
>
|
||
<!-- 完整背景图片 -->
|
||
<view
|
||
class="image-wrap"
|
||
:class="{ transit: origin.transit }"
|
||
:style="[comImageStyle('image-wrap')]"
|
||
>
|
||
<image
|
||
class="image"
|
||
:class="{ transit: origin.transit }"
|
||
:src="origin.imageUrl"
|
||
@load="imageLoad"
|
||
:style="[comImageStyle('image')]"
|
||
/>
|
||
</view>
|
||
<view class="mask-model"></view>
|
||
<view
|
||
class="frame-box"
|
||
:class="{ transit: origin.transit }"
|
||
:style="[comImageStyle('frame-box')]"
|
||
>
|
||
<view class="rect" :style="cirStyle">
|
||
<!-- 裁剪框图片 -->
|
||
<view
|
||
class="image-rect"
|
||
:class="{ transit: origin.transit }"
|
||
:style="[comImageStyle('image-rect')]"
|
||
>
|
||
<image
|
||
class="rect-img"
|
||
:class="{ transit: origin.transit }"
|
||
:src="origin.imageUrl"
|
||
:style="[comImageStyle('image')]"
|
||
/>
|
||
</view>
|
||
</view>
|
||
<!-- 矩阵框线条 -->
|
||
<view v-if="radius < 50" class="line-box">
|
||
<view class="line-one"></view>
|
||
<view class="line-two"></view>
|
||
<view class="line-three"></view>
|
||
<view class="line-four"></view>
|
||
</view>
|
||
<view
|
||
class="frame-left-top"
|
||
@touchstart.stop="(e) => touchStart(e, 'left-top')"
|
||
></view>
|
||
<view
|
||
class="frame-left-bottom"
|
||
@touchstart.stop="(e) => touchStart(e, 'left-bottom')"
|
||
></view>
|
||
<view
|
||
class="frame-right-top"
|
||
@touchstart.stop="(e) => touchStart(e, 'right-top')"
|
||
></view>
|
||
<view
|
||
class="frame-right-bottom"
|
||
@touchstart.stop="(e) => touchStart(e, 'right-bottom')"
|
||
></view>
|
||
</view>
|
||
</view>
|
||
<!-- 底部工具栏 -->
|
||
<view class="toolbar">
|
||
<view @tap.stop="onCancle" class="btn-cancel">取消</view>
|
||
<view
|
||
v-if="isRotateBtn && mode !== 'free'"
|
||
@tap.stop="onAngle"
|
||
class="btn-rotate"
|
||
>
|
||
<image src="../../static/svg/rotate.svg" data-type="inverse" />
|
||
</view>
|
||
<view @tap.stop="onConfirm" class="btn-confirm">确定</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
/**
|
||
* @name:防抖
|
||
* @param {function} fn
|
||
* @param {wait} wait
|
||
* @return {function}
|
||
*/
|
||
const debounce = (fn, wait = 1000) => {
|
||
let time = null;
|
||
return function (...args) {
|
||
if (time) clearTimeout(time);
|
||
time = setTimeout(() => {
|
||
fn.apply(this, args);
|
||
}, wait);
|
||
};
|
||
};
|
||
export default {
|
||
/**
|
||
* @property {String} mode 模式
|
||
* @value fixed 固定模式,裁剪出固定大小
|
||
* @value ratio 等比模式,宽高等比缩放
|
||
* @value free 自由模式,不限制宽高比
|
||
* @property {String} imageUrl 图片路径
|
||
* @property {Number} width 宽度
|
||
* @property {Number} height 高度
|
||
* @property {Number} maxWidth 最大宽度
|
||
* @property {Number} minHeight 最大高度
|
||
* @property {Number} scaleRatio 裁剪比列缩放
|
||
* @property {Number} minRatio 最小缩放
|
||
* @property {Number} maxRatio 最大缩放
|
||
* @property {Boolean} isRotateBtn 是否显示旋转
|
||
* @property {Boolean} isCutSize 是否导出高清裁剪原图
|
||
* @property {Number} radius 裁剪图片圆角半径,单位px
|
||
* @property {Number} delay 快速重复点击时间
|
||
*/
|
||
props: {
|
||
mode: {
|
||
type: String,
|
||
default: "ratio",
|
||
},
|
||
imageUrl: {
|
||
type: String,
|
||
default: "",
|
||
},
|
||
width: {
|
||
type: Number,
|
||
default: 200,
|
||
},
|
||
height: {
|
||
type: Number,
|
||
default: 200,
|
||
},
|
||
maxWidth: {
|
||
type: Number,
|
||
default: 1024,
|
||
},
|
||
maxHeight: {
|
||
type: Number,
|
||
default: 1024,
|
||
},
|
||
scaleRatio: {
|
||
type: Number,
|
||
default: 0.7,
|
||
},
|
||
minRatio: {
|
||
type: Number,
|
||
default: 1,
|
||
},
|
||
maxRatio: {
|
||
type: Number,
|
||
default: 3,
|
||
},
|
||
isRotateBtn: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
isCutSize: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
radius: {
|
||
type: Number,
|
||
default: 0,
|
||
},
|
||
delay: {
|
||
type: Number,
|
||
default: 250,
|
||
},
|
||
},
|
||
mounted() {
|
||
this.onConfirm = this.createConfirm(); //确定按钮防抖
|
||
},
|
||
data() {
|
||
return {
|
||
inInit: false, //是否初始化
|
||
canvasId: Math.random().toString(36).slice(-6), //获取组件实例
|
||
// 获取原始图片的大小
|
||
origin: {
|
||
imageUrl: "", //设置图片url
|
||
real: {
|
||
//原始图片宽高
|
||
width: 100,
|
||
height: 100,
|
||
},
|
||
body: {
|
||
//页面宽高
|
||
width: 100,
|
||
height: 100,
|
||
},
|
||
frame: {
|
||
//矩形框
|
||
left: 50,
|
||
top: 50,
|
||
width: 200,
|
||
height: 300,
|
||
},
|
||
bgImage: {
|
||
//背景框
|
||
left: 20,
|
||
top: 20,
|
||
width: 300,
|
||
height: 400,
|
||
},
|
||
rotate: 0,
|
||
transit: false,
|
||
canvasWidth: 100,
|
||
canvasHeight: 100,
|
||
},
|
||
//绘制手指触摸
|
||
move: {
|
||
touchType: "body", //移动的类型
|
||
start: {
|
||
//开始的位置
|
||
frame: {
|
||
left: 0,
|
||
top: 0,
|
||
width: 0,
|
||
height: 0,
|
||
},
|
||
bgImage: {
|
||
left: 0,
|
||
top: 0,
|
||
width: 0,
|
||
height: 0,
|
||
},
|
||
},
|
||
},
|
||
touch: {
|
||
touchStart: [], //记录开始触摸
|
||
},
|
||
frameSize: 150, //裁剪框最小尺寸
|
||
};
|
||
},
|
||
watch: {
|
||
// 监听原始图片的变动
|
||
imageUrl(val) {
|
||
if (val) {
|
||
uni.showLoading({
|
||
title: "请稍候...",
|
||
mask: true,
|
||
});
|
||
this.origin.imageUrl = val; //设置图片url
|
||
}
|
||
},
|
||
},
|
||
computed: {
|
||
//计算圆角比列
|
||
cirStyle() {
|
||
const { width } = this.origin.frame;
|
||
let scale = this.width / width;
|
||
let radius = this.radius / scale;
|
||
return `border-radius: ${radius}px`;
|
||
},
|
||
// 公共图片背景位置
|
||
comImageStyle() {
|
||
return (source) => {
|
||
const { left, top, width, height } = this.origin.bgImage;
|
||
if (source == "image-wrap") {
|
||
//计算背景盒子样式
|
||
const style = {};
|
||
style.left = left + "px";
|
||
style.top = top + "px";
|
||
style.width = width + "px";
|
||
style.height = height + "px";
|
||
return style;
|
||
} else if (source == "image") {
|
||
// 计算背景图片样式
|
||
let left = 0;
|
||
let top = 0;
|
||
let width = this.origin.bgImage.width;
|
||
let height = this.origin.bgImage.height;
|
||
|
||
if (this.origin.rotate % 180 != 0) {
|
||
width = this.origin.bgImage.height;
|
||
height = this.origin.bgImage.width;
|
||
top = width / 2 - height / 2;
|
||
left = height / 2 - width / 2;
|
||
}
|
||
const style = {};
|
||
style.left = left + "px";
|
||
style.top = top + "px";
|
||
style.width = width + "px";
|
||
style.height = height + "px";
|
||
style.transform = `rotate(${this.origin.rotate}deg)`;
|
||
return style;
|
||
} else if (source == "image-rect") {
|
||
// 计算裁剪框上显示的图片位置
|
||
const style = {};
|
||
style.left = left - this.origin.frame.left + "px";
|
||
style.top = top - this.origin.frame.top + "px";
|
||
style.width = width + "px";
|
||
style.height = height + "px";
|
||
return style;
|
||
} else if (source == "frame-box") {
|
||
//裁剪矩形框
|
||
const style = {};
|
||
style.left = this.origin.frame.left + "px";
|
||
style.top = this.origin.frame.top + "px";
|
||
style.width = this.origin.frame.width + "px";
|
||
style.height = this.origin.frame.height + "px";
|
||
return style;
|
||
}
|
||
};
|
||
},
|
||
},
|
||
methods: {
|
||
// 检测图片加载完成
|
||
imageLoad(e) {
|
||
const { width, height } = e.detail;
|
||
this.origin.real.width = width;
|
||
this.origin.real.height = height;
|
||
var query = uni.createSelectorQuery().in(this);
|
||
query
|
||
.select(".preview-body")
|
||
.boundingClientRect((data) => {
|
||
this.origin.body.width = data.width;
|
||
this.origin.body.height = data.height;
|
||
this.inInit = true;
|
||
this.imageReset(); //重置图片
|
||
})
|
||
.exec();
|
||
},
|
||
|
||
//重置图片
|
||
imageReset() {
|
||
this.origin.rotate = 0;
|
||
let frameRate = this.width / this.height; //裁剪比列
|
||
let frameHeight = this.origin.body.height * this.scaleRatio; //裁剪框图片高度 * 缩小0.7倍
|
||
let frameWidth = this.origin.body.width * this.scaleRatio; //裁剪框图片宽度 * 缩小0.7倍
|
||
// 缩放后的宽度/高度 > 组件裁剪的比例,就要对宽度重写
|
||
if (frameWidth / frameHeight > frameRate) {
|
||
frameWidth = frameHeight * frameRate;
|
||
} else {
|
||
frameHeight = frameWidth / frameRate;
|
||
}
|
||
|
||
// 裁剪框左边距=页面宽度-裁剪框宽度 / 2
|
||
let frameleft = (this.origin.body.width - frameWidth) / 2;
|
||
let frameTop = (this.origin.body.height - frameHeight) / 2;
|
||
this.origin.frame = {
|
||
left: frameleft,
|
||
top: frameTop,
|
||
width: frameWidth,
|
||
height: frameHeight,
|
||
};
|
||
|
||
// 背景图片位置:
|
||
let bgRate = this.origin.real.width / this.origin.real.height; //背景比列
|
||
let bgWidth = frameWidth;
|
||
let bgHeight = frameHeight;
|
||
|
||
// 裁剪框图片宽度/裁剪框图片高度>实际的图片比例
|
||
if (bgWidth / bgHeight > bgRate) {
|
||
bgHeight = bgWidth / bgRate;
|
||
} else {
|
||
bgWidth = bgHeight * bgRate;
|
||
}
|
||
|
||
// 背景左边距
|
||
let bgLeft = (frameWidth - bgWidth) / 2 + this.origin.frame.left;
|
||
// 背景右边距
|
||
let bgTop = (frameHeight - bgHeight) / 2 + this.origin.frame.top;
|
||
|
||
this.origin.bgImage = {
|
||
left: bgLeft,
|
||
top: bgTop,
|
||
width: bgWidth,
|
||
height: bgHeight,
|
||
};
|
||
uni.hideLoading();
|
||
},
|
||
// 取消按钮
|
||
onCancle() {
|
||
this.$emit("cancel");
|
||
},
|
||
onAngle() {
|
||
this.origin.rotate -= 90; // 旋转的角度
|
||
let width = this.origin.bgImage.height; // 背景框宽度
|
||
let height = this.origin.bgImage.width; // 背景框高度
|
||
let left = this.origin.bgImage.left; // 背景框左边距
|
||
let top = this.origin.bgImage.top; // 背景框顶边距
|
||
let fWidth = this.origin.frame.width; // 裁剪框宽度
|
||
let fLeft = this.origin.frame.left; // 裁剪框左边距
|
||
let fTop = this.origin.frame.top; // 裁剪框顶边距
|
||
|
||
// 左边距 = 矩形框左边距 + (背景框顶部 - 裁剪框顶部)
|
||
left = fLeft + (top - fTop);
|
||
|
||
// 顶部边距 = 矩形框顶边距 -(背景框高度 - 矩形框宽度 -(矩形框左边距 - 背景框左边距))
|
||
top = fTop - (height - fWidth - (fLeft - this.origin.bgImage.left));
|
||
|
||
this.origin.bgImage = {
|
||
left: left,
|
||
top: top,
|
||
width: width,
|
||
height: height,
|
||
};
|
||
this.origin.transit = true;
|
||
setTimeout(() => {
|
||
this.origin.transit = false;
|
||
}, 300);
|
||
},
|
||
|
||
// 确定按钮
|
||
onConfirm() {},
|
||
//确定按钮防抖
|
||
createConfirm() {
|
||
return debounce(() => {
|
||
// #ifdef H5 || APP-PLUS
|
||
this.confirmH5();
|
||
// #endif
|
||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
||
this.confirmWx();
|
||
// #endif
|
||
}, this.delay);
|
||
},
|
||
async confirmH5() {
|
||
let that = this;
|
||
let mx = this.computeMatrix(); //获取画布的信息
|
||
// 设备画布的宽高
|
||
this.origin.canvasWidth = mx.tw;
|
||
this.origin.canvasHeight = mx.th;
|
||
uni.showLoading({
|
||
title: "处理中",
|
||
});
|
||
|
||
// 设置宽高后等待更新完成在获取Canvas节点
|
||
await new Promise((resolve) => {
|
||
setTimeout(() => {
|
||
resolve();
|
||
}, 200);
|
||
});
|
||
|
||
// 创建一个画布
|
||
let ctx = uni.createCanvasContext(this.canvasId, this);
|
||
// 矩形内清除指定的像素
|
||
await ctx.clearRect(0, 0, mx.tw, mx.th);
|
||
//剪切圆角
|
||
await this.drawClipImage(ctx, mx);
|
||
// 旋转
|
||
ctx.rotate((this.origin.rotate * Math.PI) / 180);
|
||
//绘制图片
|
||
ctx.drawImage(
|
||
that.imageUrl,
|
||
mx.sx,
|
||
mx.sy,
|
||
mx.sw,
|
||
mx.sh,
|
||
mx.dx,
|
||
mx.dy,
|
||
mx.dw,
|
||
mx.dh
|
||
);
|
||
ctx.restore();
|
||
|
||
ctx.draw(false, () => {
|
||
uni.canvasToTempFilePath(
|
||
{
|
||
canvasId: that.canvasId,
|
||
// #ifdef H5
|
||
destWidth: that.isCutSize ? mx.tw : that.origin.frame.width,
|
||
destHeight: that.isCutSize ? mx.th : that.origin.frame.height,
|
||
// #endif
|
||
success: (res) => {
|
||
var path = res.tempFilePath;
|
||
// #ifdef H5
|
||
path = that.parseBlob(path);
|
||
that.$emit("confirm", {
|
||
errMsg: "parseBlob:ok",
|
||
tempFilePath: path,
|
||
});
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS
|
||
that.$emit("confirm", res);
|
||
// #endif
|
||
},
|
||
fail: (err) => {
|
||
console.log(err);
|
||
},
|
||
complete: () => {
|
||
uni.hideLoading(); //关闭loading
|
||
},
|
||
},
|
||
that
|
||
);
|
||
});
|
||
},
|
||
confirmWx() {
|
||
uni.showLoading({
|
||
title: "处理中",
|
||
});
|
||
let mx = this.computeMatrix(); //获取画布的信息
|
||
uni
|
||
.createSelectorQuery()
|
||
// #ifndef MP-ALIPAY
|
||
.in(this)
|
||
// #endif
|
||
.select("#canvas-cropper")
|
||
.fields({
|
||
node: true,
|
||
size: true,
|
||
})
|
||
.exec(async (res) => {
|
||
const textCanvas = res[0].node;
|
||
const ctx = textCanvas.getContext("2d");
|
||
|
||
// 初始化画布大小
|
||
textCanvas.width = mx.tw;
|
||
textCanvas.height = mx.th;
|
||
// //绘制canvas
|
||
await this.drawClipImage(ctx, mx); //剪切圆角
|
||
await ctx.rotate((this.origin.rotate * Math.PI) / 180);
|
||
await this.createImage(this.origin.imageUrl, mx, ctx, textCanvas);
|
||
|
||
// #ifdef MP-WEIXIN
|
||
wx.canvasToTempFilePath({
|
||
canvas: textCanvas,
|
||
canvasId: "canvasID",
|
||
success: (res) => {
|
||
this.$emit("confirm", res);
|
||
},
|
||
fail: (err) => {
|
||
console.log(err);
|
||
},
|
||
});
|
||
// #endif
|
||
|
||
// #ifdef MP-ALIPAY
|
||
textCanvas.toTempFilePath({
|
||
success: (res) => {
|
||
this.$emit("confirm", res);
|
||
},
|
||
fail: (err) => {
|
||
console.log(err);
|
||
},
|
||
});
|
||
// #endif
|
||
|
||
uni.hideLoading(); //关闭loading
|
||
});
|
||
},
|
||
//剪切图片圆角
|
||
drawClipImage(ctx, mx) {
|
||
let { radius } = this;
|
||
if (radius > 0) {
|
||
const w = Math.round(mx.tw); //画布宽度
|
||
const h = Math.round(mx.th); //画布高度
|
||
|
||
// 被缩放后的尺寸且裁剪的宽高相同
|
||
if (this.width === this.height && w == h && this.width < w) {
|
||
radius = (w / this.width) * radius;
|
||
}
|
||
// 被缩放后的尺寸且裁剪的宽高不相同
|
||
if (this.width != this.height && w != h && this.width < w) {
|
||
if (w > h) {
|
||
radius = (w / h) * radius;
|
||
} else {
|
||
radius = (h / w) * radius;
|
||
}
|
||
}
|
||
// 如果裁剪是圆形
|
||
if (w === h && radius >= w / 2) {
|
||
// 圆形
|
||
ctx.arc(w / 2, h / 2, w / 2, 0, 2 * Math.PI);
|
||
} else {
|
||
// 圆角矩形
|
||
if (w !== h) {
|
||
// 限制圆角半径不能超过短边的一半
|
||
radius = Math.min(w / 2, h / 2, radius);
|
||
}
|
||
// 其他形状
|
||
ctx.moveTo(radius, 0);
|
||
ctx.arcTo(w, 0, w, h, radius);
|
||
ctx.arcTo(w, h, 0, h, radius);
|
||
ctx.arcTo(0, h, 0, 0, radius);
|
||
ctx.arcTo(0, 0, w, 0, radius);
|
||
ctx.closePath();
|
||
}
|
||
ctx.clip(); //从原始画布剪切任意形状和尺寸的区域
|
||
ctx.restore(); //返回之前保存过的路径状态和属性
|
||
}
|
||
},
|
||
// 创建一个图片
|
||
createImage(img, mx, ctx, textCanvas) {
|
||
return new Promise((resolve) => {
|
||
const headerImg = textCanvas.createImage();
|
||
headerImg.src = img;
|
||
headerImg.onload = () => {
|
||
let result = ctx.drawImage(
|
||
headerImg,
|
||
mx.sx,
|
||
mx.sy,
|
||
mx.sw,
|
||
mx.sh,
|
||
mx.dx,
|
||
mx.dy,
|
||
mx.dw,
|
||
mx.dh
|
||
);
|
||
resolve(result);
|
||
};
|
||
});
|
||
},
|
||
// 计算矩阵
|
||
computeMatrix() {
|
||
let width = this.width;
|
||
let height = this.height;
|
||
let mul = this.origin.bgImage.width / this.origin.real.width;
|
||
if (this.origin.rotate % 180 != 0) {
|
||
mul = this.origin.bgImage.height / this.origin.real.width;
|
||
}
|
||
if (this.mode != "fixed") {
|
||
width = this.origin.frame.width / mul;
|
||
height = this.origin.frame.height / mul;
|
||
}
|
||
var rate = width / height;
|
||
if (width > this.maxWidth) {
|
||
width = this.maxWidth;
|
||
height = width / rate;
|
||
}
|
||
if (height > this.maxHeight) {
|
||
height = this.maxHeight;
|
||
width = height * rate;
|
||
}
|
||
var sx = (this.origin.frame.left - this.origin.bgImage.left) / mul;
|
||
var sy = (this.origin.frame.top - this.origin.bgImage.top) / mul;
|
||
var sw = this.origin.frame.width / mul;
|
||
var sh = this.origin.frame.height / mul;
|
||
var ox = sx + sw / 2;
|
||
var oy = sy + sh / 2;
|
||
if (this.origin.rotate % 180 != 0) {
|
||
var temp = sw;
|
||
sw = sh;
|
||
sh = temp;
|
||
}
|
||
var angle = this.origin.rotate % 360;
|
||
if (angle < 0) {
|
||
angle += 360;
|
||
}
|
||
if (angle == 270) {
|
||
var x = this.origin.real.width - oy;
|
||
var y = ox;
|
||
ox = x;
|
||
oy = y;
|
||
}
|
||
if (angle == 180) {
|
||
var x = this.origin.real.width - ox;
|
||
var y = this.origin.real.height - oy;
|
||
ox = x;
|
||
oy = y;
|
||
}
|
||
if (angle == 90) {
|
||
var x = oy;
|
||
var y = this.origin.real.height - ox;
|
||
ox = x;
|
||
oy = y;
|
||
}
|
||
sx = ox - sw / 2;
|
||
sy = oy - sh / 2;
|
||
let dr = { x: 0, y: 0, w: width, h: height };
|
||
dr = this.parseRect(dr, -this.origin.rotate);
|
||
return {
|
||
tw: width,
|
||
th: height,
|
||
sx: sx,
|
||
sy: sy,
|
||
sw: sw,
|
||
sh: sh,
|
||
dx: dr.x,
|
||
dy: dr.y,
|
||
dw: dr.w,
|
||
dh: dr.h,
|
||
};
|
||
},
|
||
//计算矩阵
|
||
parseRect(rect, angle) {
|
||
let x1 = rect.x;
|
||
let y1 = rect.y;
|
||
let x2 = rect.x + rect.w;
|
||
let y2 = rect.y + rect.h;
|
||
let p1 = this.parsePoint({ x: x1, y: y1 }, angle);
|
||
let p2 = this.parsePoint({ x: x2, y: y2 }, angle);
|
||
let result = {};
|
||
result.x = Math.min(p1.x, p2.x);
|
||
result.y = Math.min(p1.y, p2.y);
|
||
result.w = Math.abs(p2.x - p1.x);
|
||
result.h = Math.abs(p2.y - p1.y);
|
||
return result;
|
||
},
|
||
//计算x、y
|
||
parsePoint(point, angle) {
|
||
var result = {};
|
||
result.x =
|
||
point.x * Math.cos((angle * Math.PI) / 180) -
|
||
point.y * Math.sin((angle * Math.PI) / 180);
|
||
result.y =
|
||
point.y * Math.cos((angle * Math.PI) / 180) +
|
||
point.x * Math.sin((angle * Math.PI) / 180);
|
||
return result;
|
||
},
|
||
//base64转blob
|
||
parseBlob(base64) {
|
||
var arr = base64.split(",");
|
||
var mime = arr[0].match(/:(.*?);/)[1];
|
||
var bstr = atob(arr[1]);
|
||
var n = bstr.length;
|
||
var u8arr = new Uint8Array(n);
|
||
for (var i = 0; i < n; i++) {
|
||
u8arr[i] = bstr.charCodeAt(i);
|
||
}
|
||
var url = URL || webkitURL;
|
||
return url.createObjectURL(new Blob([u8arr], { type: mime }));
|
||
},
|
||
touchStart(e, touchType = "") {
|
||
// #ifdef APP-PLUS || H5
|
||
if (e.preventDefault) {
|
||
e.preventDefault();
|
||
}
|
||
if (e.stopPropagation) {
|
||
e.stopPropagation();
|
||
}
|
||
// #endif
|
||
|
||
switch (touchType) {
|
||
case "body":
|
||
this.move.touchType = "body";
|
||
break;
|
||
case "left-top":
|
||
this.move.touchType = "left-top";
|
||
break;
|
||
case "left-bottom":
|
||
this.move.touchType = "left-bottom";
|
||
break;
|
||
case "right-top":
|
||
this.move.touchType = "right-top";
|
||
break;
|
||
case "right-bottom":
|
||
this.move.touchType = "right-bottom";
|
||
break;
|
||
default:
|
||
this.move.touchType = "body";
|
||
break;
|
||
}
|
||
this.touch.touchStart = e.touches;
|
||
// 裁剪框的开始位置
|
||
this.move.start.frame.left = this.origin.frame.left;
|
||
this.move.start.frame.top = this.origin.frame.top;
|
||
this.move.start.frame.width = this.origin.frame.width;
|
||
this.move.start.frame.height = this.origin.frame.height;
|
||
|
||
// 背景图片的开始位置
|
||
this.move.start.bgImage.left = this.origin.bgImage.left;
|
||
this.move.start.bgImage.top = this.origin.bgImage.top;
|
||
this.move.start.bgImage.width = this.origin.bgImage.width;
|
||
this.move.start.bgImage.height = this.origin.bgImage.height;
|
||
return false;
|
||
},
|
||
touchMove(e) {
|
||
// #ifdef APP-PLUS || H5
|
||
if (e.preventDefault) {
|
||
e.preventDefault();
|
||
}
|
||
if (e.stopPropagation) {
|
||
e.stopPropagation();
|
||
}
|
||
// #endif
|
||
// 单手操作
|
||
if (this.touch.touchStart.length == 1) {
|
||
if (this.move.touchType == "body") {
|
||
// 移动图片
|
||
this.moveImage(this.touch.touchStart[0], e.touches[0]);
|
||
} else {
|
||
if (this.mode != "fixed") {
|
||
// 放大缩小图片
|
||
this.scaleFrame(this.touch.touchStart[0], e.touches[0]);
|
||
}
|
||
}
|
||
} else if (this.touch.touchStart.length == 2 && e.touches.length == 2) {
|
||
var ta = this.touch.touchStart[0];
|
||
var tb = this.touch.touchStart[1];
|
||
var tc = e.touches[0];
|
||
var td = e.touches[1];
|
||
if (ta.identifier != tc.identifier) {
|
||
var temp = tc;
|
||
tc = td;
|
||
td = temp;
|
||
}
|
||
this.scaleImage(ta, tb, tc, td);
|
||
}
|
||
},
|
||
// 移动图片
|
||
moveImage(ta, tb) {
|
||
let mx = tb.clientX - ta.clientX;
|
||
let my = tb.clientY - ta.clientY;
|
||
// 开始移动图片背景 + 移动的左边距离
|
||
this.origin.bgImage.left = this.move.start.bgImage.left + mx;
|
||
this.origin.bgImage.top = this.move.start.bgImage.top + my;
|
||
|
||
let frameLeft = this.origin.frame.left;
|
||
let frameTop = this.origin.frame.top;
|
||
let frameWidth = this.origin.frame.width;
|
||
let frameHeight = this.origin.frame.height;
|
||
|
||
// 左边触边
|
||
if (this.origin.bgImage.left > frameLeft) {
|
||
this.origin.bgImage.left = frameLeft;
|
||
}
|
||
// 头部触边
|
||
if (this.origin.bgImage.top > frameTop) {
|
||
this.origin.bgImage.top = frameTop;
|
||
}
|
||
// 右边触边
|
||
if (this.origin.bgImage.left + this.origin.bgImage.width < frameLeft + frameWidth) {
|
||
this.origin.bgImage.left = frameLeft + frameWidth - this.origin.bgImage.width;
|
||
}
|
||
// 底部触边:背景距离头部+背景的高度 < 背景高度 + 矩形框顶部
|
||
if (this.origin.bgImage.top + this.origin.bgImage.height < frameTop + frameHeight) {
|
||
this.origin.bgImage.top = frameTop + frameHeight - this.origin.bgImage.height;
|
||
}
|
||
},
|
||
// 缩放裁剪框
|
||
scaleFrame(ta, tb) {
|
||
let mx = tb.clientX - ta.clientX;
|
||
let my = tb.clientY - ta.clientY;
|
||
let x1 = this.move.start.frame.left;
|
||
let y1 = this.move.start.frame.top;
|
||
let x2 = this.move.start.frame.left + this.move.start.frame.width;
|
||
let y2 = this.move.start.frame.top + this.move.start.frame.height;
|
||
let cx1 = false;
|
||
let cy1 = false;
|
||
let cx2 = false;
|
||
let cy2 = false;
|
||
let mix = this.frameSize;
|
||
let rate = this.origin.frame.width / this.origin.frame.height;
|
||
const { width: fWidth, height: fHeight } = this.origin.frame;
|
||
const { width: bgWidth, height: bgHeight } = this.origin.bgImage;
|
||
const { width: realWidth, height: realHeight } = this.origin.real;
|
||
|
||
//左上角
|
||
if (this.move.touchType == "left-top") {
|
||
x1 += mx;
|
||
y1 += my;
|
||
cx1 = true;
|
||
cy1 = true;
|
||
// 左下角
|
||
} else if (this.move.touchType == "left-bottom") {
|
||
x1 += mx;
|
||
y2 += my;
|
||
cx1 = true;
|
||
cy2 = true;
|
||
// 右上角
|
||
} else if (this.move.touchType == "right-top") {
|
||
x2 += mx;
|
||
y1 += my;
|
||
cx2 = true;
|
||
cy1 = true;
|
||
// 右下角
|
||
} else if (this.move.touchType == "right-bottom") {
|
||
x2 += mx;
|
||
y2 += my;
|
||
cx2 = true;
|
||
cy2 = true;
|
||
}
|
||
|
||
if (x1 < this.origin.bgImage.left) {
|
||
x1 = this.origin.bgImage.left;
|
||
}
|
||
if (y1 < this.origin.bgImage.top) {
|
||
y1 = this.origin.bgImage.top;
|
||
}
|
||
if (x2 > this.origin.bgImage.left + this.origin.bgImage.width) {
|
||
x2 = this.origin.bgImage.left + this.origin.bgImage.width;
|
||
}
|
||
if (y2 > this.origin.bgImage.top + this.origin.bgImage.height) {
|
||
y2 = this.origin.bgImage.top + this.origin.bgImage.height;
|
||
}
|
||
if (cx1) {
|
||
if (x1 > x2 - mix) {
|
||
x1 = x2 - mix;
|
||
}
|
||
}
|
||
if (cy1) {
|
||
if (y1 > y2 - mix) {
|
||
y1 = y2 - mix;
|
||
}
|
||
}
|
||
if (cx2) {
|
||
if (x2 < x1 + mix) {
|
||
x2 = x1 + mix;
|
||
}
|
||
}
|
||
if (cy2) {
|
||
if (y2 < y1 + mix) {
|
||
y2 = y1 + mix;
|
||
}
|
||
}
|
||
if (cx1) {
|
||
if (this.mode != "free") {
|
||
var val = x2 - rate * (y2 - y1);
|
||
if (x1 < val) {
|
||
x1 = val;
|
||
}
|
||
}
|
||
}
|
||
if (cy1) {
|
||
if (this.mode != "free") {
|
||
var val = y2 - (x2 - x1) / rate;
|
||
if (y1 < val) {
|
||
y1 = val;
|
||
}
|
||
}
|
||
}
|
||
if (cx2) {
|
||
if (this.mode != "free") {
|
||
var val = rate * (y2 - y1) + x1;
|
||
if (x2 > val) {
|
||
x2 = val;
|
||
}
|
||
}
|
||
}
|
||
if (cy2) {
|
||
if (this.mode != "free") {
|
||
var val = (x2 - x1) / rate + y1;
|
||
if (y2 > val) {
|
||
y2 = val;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 裁剪框缩放限制
|
||
const owFrame = x2 - x1;
|
||
const ohFrame = y2 - y1;
|
||
|
||
if (owFrame < fWidth || ohFrame < fHeight) {
|
||
const currentRatio = this.scaleRatio * this.maxRatio;
|
||
const isScale =
|
||
bgWidth * currentRatio >= realWidth / this.scaleRatio &&
|
||
bgHeight * currentRatio >= realHeight / this.scaleRatio;
|
||
if (isScale) {
|
||
return false;
|
||
}
|
||
}
|
||
this.origin.frame.left = x1;
|
||
this.origin.frame.top = y1;
|
||
this.origin.frame.width = x2 - x1;
|
||
this.origin.frame.height = y2 - y1;
|
||
},
|
||
scaleImage(ta, tb, tc, td) {
|
||
let x1 = ta.clientX;
|
||
let y1 = ta.clientY;
|
||
let x2 = tb.clientX;
|
||
let y2 = tb.clientY;
|
||
let x3 = tc.clientX;
|
||
let y3 = tc.clientY;
|
||
let x4 = td.clientX;
|
||
let y4 = td.clientY;
|
||
let ol = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
|
||
let el = Math.sqrt((x3 - x4) * (x3 - x4) + (y3 - y4) * (y3 - y4));
|
||
let ocx = (x1 + x2) / 2;
|
||
let ocy = (y1 + y2) / 2;
|
||
let ecx = (x3 + x4) / 2;
|
||
let ecy = (y3 + y4) / 2;
|
||
let ax = ecx - ocx;
|
||
let ay = ecy - ocy;
|
||
let scale = el / ol;
|
||
|
||
// 检测放大/缩小倍率
|
||
if (this.move.start.bgImage.width * scale < this.origin.frame.width) {
|
||
scale = this.origin.frame.width / this.move.start.bgImage.width;
|
||
}
|
||
|
||
if (this.move.start.bgImage.height * scale < this.origin.frame.height) {
|
||
scale = this.origin.frame.height / this.move.start.bgImage.height;
|
||
}
|
||
|
||
// 最大缩放
|
||
if (
|
||
this.move.start.bgImage.width * scale >
|
||
this.origin.frame.width * this.maxRatio
|
||
) {
|
||
return;
|
||
}
|
||
|
||
this.origin.bgImage.left =
|
||
this.move.start.bgImage.left +
|
||
ax -
|
||
(ocx - this.move.start.bgImage.left) * (scale - 1);
|
||
|
||
this.origin.bgImage.top =
|
||
this.move.start.bgImage.top +
|
||
ay -
|
||
(ocy - this.move.start.bgImage.top) * (scale - 1);
|
||
|
||
this.origin.bgImage.width = this.move.start.bgImage.width * scale;
|
||
this.origin.bgImage.height = this.move.start.bgImage.height * scale;
|
||
|
||
if (this.origin.bgImage.left > this.origin.frame.left) {
|
||
this.origin.bgImage.left = this.origin.frame.left;
|
||
}
|
||
if (this.origin.bgImage.top > this.origin.frame.top) {
|
||
this.origin.bgImage.top = this.origin.frame.top;
|
||
}
|
||
if (
|
||
this.origin.bgImage.left + this.origin.bgImage.width <
|
||
this.origin.frame.left + this.origin.frame.width
|
||
) {
|
||
this.origin.bgImage.left =
|
||
this.origin.frame.left + this.origin.frame.width - this.origin.bgImage.width;
|
||
}
|
||
if (
|
||
this.origin.bgImage.top + this.origin.bgImage.height <
|
||
this.origin.frame.top + this.origin.frame.height
|
||
) {
|
||
this.origin.bgImage.top =
|
||
this.origin.frame.top + this.origin.frame.height - this.origin.bgImage.height;
|
||
}
|
||
},
|
||
// 手指触摸动作结束
|
||
touchEnd() {
|
||
this.touch.touchStart = [];
|
||
this.resizeImage(); //调整图片的大小
|
||
},
|
||
// 手指触摸动作被打断,如来电提醒,弹窗
|
||
touchCancel() {
|
||
this.touch.touchStart = [];
|
||
},
|
||
// 调整图片的大小
|
||
resizeImage() {
|
||
var rate = this.origin.frame.width / this.origin.frame.height; //比列
|
||
var width = this.origin.body.width * this.scaleRatio; //裁剪框图片高度 * 缩小0.7倍
|
||
var height = this.origin.body.height * this.scaleRatio; //裁剪框图片宽度 * 缩小0.7倍
|
||
// 图片的位置:
|
||
// 缩放后的宽度/高度 > 组件裁剪的比例,就要对宽度重写
|
||
if (width / height > rate) {
|
||
width = height * rate;
|
||
} else {
|
||
height = width / rate;
|
||
}
|
||
var left = (this.origin.body.width - width) / 2;
|
||
var top = (this.origin.body.height - height) / 2;
|
||
var mul = width / this.origin.frame.width;
|
||
var ox = this.origin.frame.left - this.origin.bgImage.left;
|
||
var oy = this.origin.frame.top - this.origin.bgImage.top;
|
||
|
||
this.origin.frame = {
|
||
left: left,
|
||
top: top,
|
||
width: width,
|
||
height: height,
|
||
};
|
||
|
||
width = this.origin.bgImage.width * mul;
|
||
height = this.origin.bgImage.height * mul;
|
||
left = this.origin.frame.left - ox * mul;
|
||
top = this.origin.frame.top - oy * mul;
|
||
this.origin.bgImage = {
|
||
left: left,
|
||
top: top,
|
||
width: width,
|
||
height: height,
|
||
};
|
||
if (mul != 1) {
|
||
this.origin.transit = true;
|
||
setTimeout(() => {
|
||
this.origin.transit = false;
|
||
}, 300);
|
||
}
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style>
|
||
page {
|
||
overflow: hidden;
|
||
overscroll-behavior: none;
|
||
}
|
||
</style>
|
||
<style lang="scss" scoped>
|
||
@import "./tt-cropper";
|
||
</style>
|