Files
vpUni/.svn/pristine/1e/1ec38289d3278199a61db07aadd5ff06132f3dc1.svn-base
2026-03-09 16:39:03 +08:00

1098 lines
27 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="t-cropper" v-show="props.imageUrl" >
<!-- #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: canvasWidth + 'px', height: 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: transit }"
:style="comImageStyle('image-wrap')"
>
<image
class="image"
:class="{ transit: transit }"
:src="imageUrl"
@load="imageLoad"
:style="comImageStyle('image')"
/>
</view>
<view class="mask-model"></view>
<view
class="frame-box"
:class="{ transit: transit }"
:style="comImageStyle('frame-box')"
>
<view class="rect" :style="cirStyle">
<!-- 裁剪框图片 -->
<view
class="image-rect"
:class="{ transit: transit }"
:style="comImageStyle('image-rect')"
>
<image
class="rect-img"
:class="{ transit: transit }"
:src="imageUrl"
:style="comImageStyle('image')"
/>
</view>
</view>
<!-- 矩阵框线条 -->
<view v-if="props.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="props.isRotateBtn && props.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 setup>
import { reactive, ref, watchEffect, computed, getCurrentInstance, toRefs } from "vue";
/**
* @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 {Number} radius 裁剪图片圆角半径单位px
* @property {Number} delay 快速重复点击时间
*/
const props = defineProps({
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,
},
radius: {
type: Number,
default: 0,
},
delay: {
type: Number,
default: 250,
},
});
// 获取组件实例
const instance = getCurrentInstance();
const inInit = ref(false);
const $emit = defineEmits(["confirm", "cancel"]);
const canvasId = ref(Math.random().toString(36).slice(-6));
//获取原始图片的大小
const origin = reactive({
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,
frameSize: 150, //裁剪框最小尺寸
});
const { transit, rotate, imageUrl, canvasWidth, canvasHeight } = toRefs(origin);
//监听原始图片的变动
watchEffect(() => {
if (props.imageUrl) {
uni.showLoading({
title: "请稍候...",
mask: true,
});
origin.imageUrl = props.imageUrl; //设置图片url}
}
});
//检测图片加载完成
const imageLoad = (e) => {
const { width, height } = e.detail;
origin.real.width = width;
origin.real.height = height;
var query = uni.createSelectorQuery().in(instance);
query
.select(".preview-body")
.boundingClientRect((data) => {
origin.body.width = data.width;
origin.body.height = data.height;
inInit.value = true;
imageReset(); //重置图片
})
.exec();
};
//重置图片
const imageReset = () => {
origin.rotate = 0;
let frameRate = props.width / props.height; //裁剪比列
let frameHeight = origin.body.height * props.scaleRatio; //裁剪框图片高度 * 缩小0.7倍
let frameWidth = origin.body.width * props.scaleRatio; //裁剪框图片宽度 * 缩小0.7倍
// 裁剪框的位置:
// 缩放后的宽度/高度 > 组件裁剪的比例,就要对宽度重写
if (frameWidth / frameHeight > frameRate) {
frameWidth = frameHeight * frameRate;
} else {
frameHeight = frameWidth / frameRate;
}
// 裁剪框左边距=页面宽度-裁剪框宽度 / 2
let frameleft = (origin.body.width - frameWidth) / 2;
let frameTop = (origin.body.height - frameHeight) / 2;
origin.frame = {
left: frameleft,
top: frameTop,
width: frameWidth,
height: frameHeight,
};
// 背景图片位置:
let bgRate = origin.real.width / 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 + origin.frame.left;
// 背景右边距
let bgTop = (frameHeight - bgHeight) / 2 + origin.frame.top;
origin.bgImage = {
left: bgLeft,
top: bgTop,
width: bgWidth,
height: bgHeight,
};
uni.hideLoading();
};
//公共图片背景位置
const comImageStyle = computed(() => (source) => {
const { left, top, width, height } = origin.bgImage;
if (source == "image-wrap") {
//计算背景盒子样式
return `
top: ${top}px;
left: ${left}px;
width: ${width}px;
height: ${height}px;
`;
} else if (source == "image") {
// 计算背景图片样式
let left = 0;
let top = 0;
let width = origin.bgImage.width;
let height = origin.bgImage.height;
if (origin.rotate % 180 != 0) {
width = origin.bgImage.height;
height = 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(${origin.rotate}deg)`;
return style;
} else if (source == "image-rect") {
// 计算裁剪框上显示的图片位置
return `
top: ${top - origin.frame.top}px;
left: ${left - origin.frame.left}px;
width: ${width}px;
height: ${height}px;
`;
} else if (source == "frame-box") {
return `
left: ${origin.frame.left}px;
top: ${origin.frame.top}px;
width: ${origin.frame.width}px;
height: ${origin.frame.height}px;
`;
}
});
//计算圆角比列
const cirStyle = computed(() => {
const { width } = origin.frame;
let scale = props.width / width;
let radius = props.radius / scale;
return `
border-radius: ${radius}px
`;
});
// 取消按钮
const onCancle = () => {
$emit("cancel");
};
// 旋转按钮
const onAngle = () => {
origin.rotate -= 90; // 旋转的角度
let width = origin.bgImage.height; // 背景框宽度
let height = origin.bgImage.width; // 背景框高度
let left = origin.bgImage.left; // 背景框左边距
let top = origin.bgImage.top; // 背景框顶边距
let fWidth = origin.frame.width; // 裁剪框宽度
let fLeft = origin.frame.left; // 裁剪框左边距
let fTop = origin.frame.top; // 裁剪框顶边距
// 左边距 = 矩形框左边距 + (背景框顶部 - 裁剪框顶部)
left = fLeft + (top - fTop);
// 顶部边距 = 矩形框顶边距 -(背景框高度 - 矩形框宽度 -(矩形框左边距 - 背景框左边距))
top = fTop - (height - fWidth - (fLeft - origin.bgImage.left));
origin.bgImage = {
left: left,
top: top,
width: width,
height: height,
};
origin.transit = true;
setTimeout(() => {
origin.transit = false;
}, 300);
};
/**
* @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);
};
};
// 确定按钮
const onConfirm = debounce(async () => {
// #ifdef H5 || APP-PLUS
confirmH5();
// #endif
// #ifdef MP-WEIXIN || MP-ALIPAY
confirmWx();
// #endif
}, props.delay);
// h5裁剪确认
const confirmH5 = async () => {
let mx = computeMatrix(); //获取画布的信息
// 设备画布的宽高
origin.canvasWidth = mx.tw;
origin.canvasHeight = mx.th;
uni.showLoading({
title: "处理中",
});
// 设置宽高后等待更新完成在获取Canvas节点
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 200);
});
// 创建一个画布
let ctx = uni.createCanvasContext(canvasId.value, instance);
// 矩形内清除指定的像素
await ctx.clearRect(0, 0, mx.tw, mx.th);
//剪切圆角
await drawClipImage(ctx, mx);
// 旋转
ctx.rotate((origin.rotate * Math.PI) / 180);
//绘制图片
ctx.drawImage(props.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: canvasId.value,
success: (res) => {
var path = res.tempFilePath;
// #ifdef H5
path = parseBlob(path);
$emit("confirm", {
errMsg: "parseBlob:ok",
tempFilePath: path,
});
// #endif
// #ifdef APP-PLUS
$emit("confirm", res);
// #endif
},
fail: (err) => {
console.log(err);
},
complete: () => {
uni.hideLoading(); //关闭loading
},
},
instance
);
});
};
// 微信确认
const confirmWx = () => {
uni.showLoading({
title: "处理中",
});
let mx = computeMatrix(); //获取画布的信息
uni
.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(instance)
// #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 drawClipImage(ctx, mx); //剪切圆角
await ctx.rotate((origin.rotate * Math.PI) / 180);
await createImage(origin.imageUrl, mx, ctx, textCanvas);
// #ifdef MP-WEIXIN
wx.canvasToTempFilePath({
canvas: textCanvas,
canvasId: "canvasID",
success: (res) => {
$emit("confirm", res);
},
fail: (err) => {
console.log(err);
},
});
// #endif
// #ifdef MP-ALIPAY
textCanvas.toTempFilePath({
success: (res) => {
$emit("confirm", res);
},
fail: (err) => {
console.log(err);
},
});
// #endif
uni.hideLoading(); //关闭loading
});
};
//剪切图片圆角
const drawClipImage = (ctx, mx) => {
let { radius } = props;
if (radius > 0) {
const w = Math.round(mx.tw); //画布宽度
const h = Math.round(mx.th); //画布高度
// 被缩放后的尺寸且裁剪的宽高相同
if (props.width === props.height && w == h && props.width < w) {
radius = (w / props.width) * radius;
}
// 被缩放后的尺寸且裁剪的宽高不相同
if (props.width != props.height && w != h && props.width < w) {
console.log("11111111");
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(); //返回之前保存过的路径状态和属性
}
};
// 创建一个图片
const 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);
};
});
};
//计算矩阵
const computeMatrix = () => {
let width = props.width;
let height = props.height;
let mul = origin.bgImage.width / origin.real.width;
if (origin.rotate % 180 != 0) {
mul = origin.bgImage.height / origin.real.width;
}
if (props.mode != "fixed") {
width = origin.frame.width / mul;
height = origin.frame.height / mul;
}
var rate = width / height;
if (width > props.maxWidth) {
width = props.maxWidth;
height = width / rate;
}
if (height > props.maxHeight) {
height = props.maxHeight;
width = height * rate;
}
var sx = (origin.frame.left - origin.bgImage.left) / mul;
var sy = (origin.frame.top - origin.bgImage.top) / mul;
var sw = origin.frame.width / mul;
var sh = origin.frame.height / mul;
var ox = sx + sw / 2;
var oy = sy + sh / 2;
if (origin.rotate % 180 != 0) {
var temp = sw;
sw = sh;
sh = temp;
}
var angle = origin.rotate % 360;
if (angle < 0) {
angle += 360;
}
if (angle == 270) {
var x = origin.real.width - oy;
var y = ox;
ox = x;
oy = y;
}
if (angle == 180) {
var x = origin.real.width - ox;
var y = origin.real.height - oy;
ox = x;
oy = y;
}
if (angle == 90) {
var x = oy;
var y = 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 = parseRect(dr, -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,
};
};
// 计算矩阵
const 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 = parsePoint({ x: x1, y: y1 }, angle);
let p2 = 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
const 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
const 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 }));
};
/**
* 绘制手指触摸
*/
const move = reactive({
touchType: "body", //移动的类型
start: {
//开始的位置
frame: {
left: 0,
top: 0,
width: 0,
height: 0,
},
bgImage: {
left: 0,
top: 0,
width: 0,
height: 0,
},
},
});
const touch = reactive({
touchStart: [], //记录开始触摸
});
// 手指触摸动作开始
const touchStart = (e, touchType = "") => {
// #ifdef APP-PLUS || H5
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
// #endif
switch (touchType) {
case "body":
move.touchType = "body";
break;
case "left-top":
move.touchType = "left-top";
break;
case "left-bottom":
move.touchType = "left-bottom";
break;
case "right-top":
move.touchType = "right-top";
break;
case "right-bottom":
move.touchType = "right-bottom";
break;
default:
move.touchType = "body";
break;
}
touch.touchStart = e.touches;
// 裁剪框的开始位置
move.start.frame.left = origin.frame.left;
move.start.frame.top = origin.frame.top;
move.start.frame.width = origin.frame.width;
move.start.frame.height = origin.frame.height;
// 背景图片的开始位置
move.start.bgImage.left = origin.bgImage.left;
move.start.bgImage.top = origin.bgImage.top;
move.start.bgImage.width = origin.bgImage.width;
move.start.bgImage.height = origin.bgImage.height;
return false;
};
// 手指触摸后移动
const touchMove = (e) => {
// #ifdef APP-PLUS || H5
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
// #endif
// 单手操作
if (touch.touchStart.length == 1) {
if (move.touchType == "body") {
// 移动图片
moveImage(touch.touchStart[0], e.touches[0]);
} else {
if (props.mode != "fixed") {
// 放大缩小图片
scaleFrame(touch.touchStart[0], e.touches[0]);
}
}
} else if (touch.touchStart.length == 2 && e.touches.length == 2) {
var ta = touch.touchStart[0];
var tb = 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;
}
scaleImage(ta, tb, tc, td);
}
};
// 移动图片
const moveImage = (ta, tb) => {
let mx = tb.clientX - ta.clientX;
let my = tb.clientY - ta.clientY;
// 开始移动图片背景 + 移动的左边距离
origin.bgImage.left = move.start.bgImage.left + mx;
origin.bgImage.top = move.start.bgImage.top + my;
let frameLeft = origin.frame.left;
let frameTop = origin.frame.top;
let frameWidth = origin.frame.width;
let frameHeight = origin.frame.height;
// 左边触边
if (origin.bgImage.left > frameLeft) {
origin.bgImage.left = frameLeft;
}
// 头部触边
if (origin.bgImage.top > frameTop) {
origin.bgImage.top = frameTop;
}
// 右边触边
if (origin.bgImage.left + origin.bgImage.width < frameLeft + frameWidth) {
origin.bgImage.left = frameLeft + frameWidth - origin.bgImage.width;
}
// 底部触边:背景距离头部+背景的高度 < 背景高度 + 矩形框顶部
if (origin.bgImage.top + origin.bgImage.height < frameTop + frameHeight) {
origin.bgImage.top = frameTop + frameHeight - origin.bgImage.height;
}
};
//缩放裁剪框
const scaleFrame = (ta, tb) => {
let mx = tb.clientX - ta.clientX;
let my = tb.clientY - ta.clientY;
let x1 = move.start.frame.left;
let y1 = move.start.frame.top;
let x2 = move.start.frame.left + move.start.frame.width;
let y2 = move.start.frame.top + move.start.frame.height;
let cx1 = false;
let cy1 = false;
let cx2 = false;
let cy2 = false;
let mix = origin.frameSize;
let rate = origin.frame.width / origin.frame.height;
const { width: fWidth, height: fHeight } = origin.frame;
const { width: bgWidth, height: bgHeight } = origin.bgImage;
const { width: realWidth, height: realHeight } = origin.real;
//左上角
if (move.touchType == "left-top") {
x1 += mx;
y1 += my;
cx1 = true;
cy1 = true;
// 左下角
} else if (move.touchType == "left-bottom") {
x1 += mx;
y2 += my;
cx1 = true;
cy2 = true;
// 右上角
} else if (move.touchType == "right-top") {
x2 += mx;
y1 += my;
cx2 = true;
cy1 = true;
// 右下角
} else if (move.touchType == "right-bottom") {
x2 += mx;
y2 += my;
cx2 = true;
cy2 = true;
}
if (x1 < origin.bgImage.left) {
x1 = origin.bgImage.left;
}
if (y1 < origin.bgImage.top) {
y1 = origin.bgImage.top;
}
if (x2 > origin.bgImage.left + origin.bgImage.width) {
x2 = origin.bgImage.left + origin.bgImage.width;
}
if (y2 > origin.bgImage.top + origin.bgImage.height) {
y2 = origin.bgImage.top + 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 (props.mode != "free") {
var val = x2 - rate * (y2 - y1);
if (x1 < val) {
x1 = val;
}
}
}
if (cy1) {
if (props.mode != "free") {
var val = y2 - (x2 - x1) / rate;
if (y1 < val) {
y1 = val;
}
}
}
if (cx2) {
if (props.mode != "free") {
var val = rate * (y2 - y1) + x1;
if (x2 > val) {
x2 = val;
}
}
}
if (cy2) {
if (props.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 = props.scaleRatio * props.maxRatio;
const isScale =
bgWidth * currentRatio >= realWidth / props.scaleRatio &&
bgHeight * currentRatio >= realHeight / props.scaleRatio;
if (isScale) {
return false;
}
}
origin.frame.left = x1;
origin.frame.top = y1;
origin.frame.width = x2 - x1;
origin.frame.height = y2 - y1;
};
//双指缩放图片
const 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 (move.start.bgImage.width * scale < origin.frame.width) {
scale = origin.frame.width / move.start.bgImage.width;
}
if (move.start.bgImage.height * scale < origin.frame.height) {
scale = origin.frame.height / move.start.bgImage.height;
}
// 最大缩放
if (move.start.bgImage.width * scale > origin.frame.width * props.maxRatio) {
return;
}
origin.bgImage.left =
move.start.bgImage.left + ax - (ocx - move.start.bgImage.left) * (scale - 1);
origin.bgImage.top =
move.start.bgImage.top + ay - (ocy - move.start.bgImage.top) * (scale - 1);
origin.bgImage.width = move.start.bgImage.width * scale;
origin.bgImage.height = move.start.bgImage.height * scale;
if (origin.bgImage.left > origin.frame.left) {
origin.bgImage.left = origin.frame.left;
}
if (origin.bgImage.top > origin.frame.top) {
origin.bgImage.top = origin.frame.top;
}
if (
origin.bgImage.left + origin.bgImage.width <
origin.frame.left + origin.frame.width
) {
origin.bgImage.left = origin.frame.left + origin.frame.width - origin.bgImage.width;
}
if (
origin.bgImage.top + origin.bgImage.height <
origin.frame.top + origin.frame.height
) {
origin.bgImage.top = origin.frame.top + origin.frame.height - origin.bgImage.height;
}
};
// 手指触摸动作结束
const touchEnd = () => {
touch.touchStart = [];
resizeImage(); //调整图片的大小
};
// 手指触摸动作被打断,如来电提醒,弹窗
const touchCancel = () => {
touch.touchStart = [];
};
// 调整图片的大小
const resizeImage = () => {
var rate = origin.frame.width / origin.frame.height; //比列
var width = origin.body.width * props.scaleRatio; //裁剪框图片高度 * 缩小0.7倍
var height = origin.body.height * props.scaleRatio; //裁剪框图片宽度 * 缩小0.7倍
// 图片的位置:
// 缩放后的宽度/高度 > 组件裁剪的比例,就要对宽度重写
if (width / height > rate) {
width = height * rate;
} else {
height = width / rate;
}
var left = (origin.body.width - width) / 2;
var top = (origin.body.height - height) / 2;
var mul = width / origin.frame.width;
var ox = origin.frame.left - origin.bgImage.left;
var oy = origin.frame.top - origin.bgImage.top;
origin.frame = {
left: left,
top: top,
width: width,
height: height,
};
width = origin.bgImage.width * mul;
height = origin.bgImage.height * mul;
left = origin.frame.left - ox * mul;
top = origin.frame.top - oy * mul;
origin.bgImage = {
left: left,
top: top,
width: width,
height: height,
};
if (mul != 1) {
origin.transit = true;
setTimeout(() => {
origin.transit = false;
}, 300);
}
};
/**
* 绘制手指触摸结束
*/
</script>
<style>
page {
overflow: hidden;
overscroll-behavior: none;
}
</style>
<style lang="scss" scoped>
@import "./t-cropper";
</style>