1.技术背景
随着都市类程序开发业务推进,在各类活动、个人信息等模块逐渐依赖图像裁剪、上传等功能,那么如何实现图像缩放、裁剪就成了前端工程师们关注优化的重点。
2.应用技术
本次使用的技术主要是依赖canvas,又叫画布,是H5新增内容,可以使用js脚本在其中绘制图像的元素。
可以完成图片处理、动画渲染、图表渲染等操作。
最近用uni-app的推流组件做了个图片裁切、上传的组件,拍照、选择照片完成后还需要对图片进行裁切,才能真正使用。
3.处理思路
使用两个canvas分别为操作canvas、目标canvas,操作canvas通过touchstart、touchmove、touchend、touchcancel实时跟进图片缩放、移动状态,点击确认后,使用canvas.drawImage()将前面的操作canvas绘制到目标canvas,最终调用canvas.toDataUrl()将目标canvas转为base64编码的图片传给接口。
3.1 关键方法
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
参数值
参数 | 描述 |
---|---|
img | 规定要使用的图像、画布或视频。 |
sx | 可选。开始剪切的 x 坐标位置。 |
sy | 可选。开始剪切的 y 坐标位置。 |
swidth | 可选。被剪切图像的宽度。 |
sheight | 可选。被剪切图像的高度。 |
x | 在画布上放置图像的 x 坐标位置。 |
y | 在画布上放置图像的 y 坐标位置。 |
width | 可选。要使用的图像的宽度。(伸展或缩小图像) |
height | 可选。要使用的图像的高度。(伸展或缩小图像) |

4. 具体实现
4.1创建操作canvas、目标canvas
mounted() {
// 防止H5用户在数据挂在前移动图片
//#ifdef H5
this.$el.addEventListener('touchmove', (ev) => {
ev.preventDefault();
});
// #endif
// 创建canvas
this.context = uni.createCanvasContext('canvas', this);//操作canvas
this.targetContext = uni.createCanvasContext('target', this);//目标canvas
},
4.2 图片加载初始化
其实这一部分就是数据计算,也就是开始涉及到一些图像缩放的算法逻辑,这一部分理解的时候需要花一点时间。
这边其实最要关注的就是一个时间点,两个主体。
时间点:图片加载完成后的如何对图片位置、大小以及对应的裁剪框位置、大小初始化,即图片和裁剪框的相对关系;
两个主体:图像、裁剪框
<template>
<view v-show="url" class="ksp-image-cutter">
<!-- 目标图像载体 -->
<canvas
id="target"
:style="{ width: target.width + 'px', height: target.height + 'px' }"
canvas-id="target"
></canvas>
<!-- 主体 -->
<view class="body">
<!-- 裁剪的图片 -->
<image
v-if="url"
lazy-load
class="image"
:style="{
left: image.left + 'px',
top: image.top + 'px',
width: image.width + 'px',
height: image.height + 'px',
}"
:src="url"
@load="imageLoad"
></image>
<!-- <view v-if="mask.show" class="mask"></view> -->
<!-- 裁剪区域 -->
<view
class="plank"
@touchstart="touchStart($event, 'plank')"
@touchmove="touchMove"
@touchend="touchEnd"
@touchcancel="touchCancel"
>
<view
class="frame"
:style="{
left: frame.left + 'px',
top: frame.top + 'px',
width: frame.width + 'px',
height: frame.height + 'px',
}"
@touchstart="touchStart($event, 'frame')"
@touchstart.stop.prevent="touchHandle"
>
<!-- 裁剪载体 -->
<canvas
v-if="mask.show"
class="canvas"
:style="{ width: frame.width + 'px', height: frame.height + 'px' }"
canvas-id="canvas"
></canvas>
<!-- 窗口 -->
<view class="rect"></view>
<!-- 分割线 -->
<!-- <view class="line-one"></view>
<view class="line-two"></view>
<view class="line-three"></view>
<view class="line-four"></view> -->
<!-- 窗口调节键 -->
<view
class="frame-left"
@touchstart="touchStart($event, 'left')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-right"
@touchstart="touchStart($event, 'right')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-top"
@touchstart="touchStart($event, 'top')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-bottom"
@touchstart="touchStart($event, 'bottom')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-left-top"
@touchstart="touchStart($event, 'left-top')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-left-bottom"
@touchstart="touchStart($event, 'left-bottom')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-right-top"
@touchstart="touchStart($event, 'right-top')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-right-bottom"
@touchstart="touchStart($event, 'right-bottom')"
@touchstart.stop.prevent="touchHandle"
></view>
</view>
</view>
</view>
<!-- 操作区域 -->
<view class="toolbar">
<button class="btn-cancel" @tap="oncancel">取消</button>
<button class="btn-ok" @tap="onok">选取</button>
</view>
</view>
</template>
methods: {
// 图片首次加载
imageLoad(ev) {
// 加载动画
this.mask.show = true;
//图像原本宽高获取 (关键数据:图片缩放依赖)
this.real.width = ev.detail.width;
this.real.height = ev.detail.height;
//图片目前的宽高。(关键数据:图片缩放后的大小)
this.image.width = ev.detail.width;
this.image.height = ev.detail.height;
// 裁剪区域的大小
this.frame.width = this.width;
this.frame.height = this.height;
// 裁剪大小是否固定
if (!this.fixed) {
this.frame.width = this.image.width;
this.frame.height = this.image.height;
}
// 获取相关元素基本信息
const query = uni.createSelectorQuery().in(this);
query
.select('.body')
.boundingClientRect((data) => {
//容器宽高
const bw = data.width;
const bh = data.height;
// 裁剪大小
const fw = this.frame.width;
const fh = this.frame.height;
// 计算缩放比例
let tw = bw * 0.8;
let th = bh * 0.8;
let sx = tw / fw;
let sy = th / fh;
let scale = sx;
if (sx < sy) {
scale = sy;
}
//计算矢量位移
tw = fw * scale;
th = fh * scale;
const tx = (bw - tw) / 2;
const ty = (bh - th) / 2;
this.frame.width = tw;
this.frame.height = th;
this.frame.left = tx;
this.frame.top = ty;
const iw = this.image.width;
const ih = this.image.height;
sx = tw / iw;
sy = th / ih;
scale = sx;
if (sx < sy) {
scale = sy;
}
this.image.width = iw * scale;
this.image.height = ih * scale;
this.image.left = (bw - this.image.width) / 2;
this.image.top = (bh - this.image.height) / 2;
setTimeout(() => {
// 图片大小
this.trimImage();
}, 100);
})
.exec();
},
// 初始化图片大小、位置
trimImage() {
this.mask.show = true;
const query = uni.createSelectorQuery().in(this);
query
.select('.body')
.boundingClientRect((data) => {
const bw = data.width;
const bh = data.height;
const fw = this.frame.width;
const fh = this.frame.height;
let tw = bw;
let th = bh;
// let tw = bw * 0.8;
// let th = bh * 0.8;
const sx = tw / fw;
const sy = th / fh;
let scale = sx;
if (sx > sy) {
scale = sy;
}
tw = fw * scale;
th = fh * scale;
const tx = (bw - tw) / 2;
const ty = (bh - th) / 2;
const ax =
tx -
this.frame.left +
(this.frame.left - this.image.left) * (1 - scale);
const ay =
ty -
this.frame.top +
(this.frame.top - this.image.top) * (1 - scale);
this.frame.width = tw;
this.frame.height = th;
this.frame.left = tx;
this.frame.top = ty;
this.image.width *= scale;
this.image.height *= scale;
this.image.left += ax;
this.image.top += ay;
})
.exec();
setTimeout(() => {
// 计算缩放、位移数据
const scale = this.image.width / this.real.width;
const x = (this.frame.left - this.image.left) / scale;
const y = (this.frame.top - this.image.top) / scale;
const width = this.frame.width / scale;
const height = this.frame.height / scale;
// 将图片绘制到操作canvas
this.context.drawImage(
this.url,
x,
y,
width,
height,
0,
0,
this.frame.width,
this.frame.height
);
// 开始绘制
this.context.draw(false);
}, 100);
},
}
4.3 触摸事件处理
// 阻止手指事件冒泡传递
touchHandle() {},
// 手指开始触摸事件
touchStart(ev, type) {
this.stopTime();
this.mask.show = false;
if (this.touches.length === 0) {
this.type = type;
this.start.frame.left = this.frame.left;
this.start.frame.top = this.frame.top;
this.start.frame.width = this.frame.width;
this.start.frame.height = this.frame.height;
this.start.image.left = this.image.left;
this.start.image.top = this.image.top;
this.start.image.width = this.image.width;
this.start.image.height = this.image.height;
}
const touches = ev.changedTouches;
for (let i = 0; i < touches.length; i++) {
const touch = touches[i];
// this.touches[touch.identifier] = touch;
this.touches.push(touch);
}
},
// 手指移动事件
touchMove(ev) {
this.stopTime();
ev.preventDefault();
const touches = ev.touches;
if (this.touches.length === 1) {
if (this.type === 'plank' || this.type === 'frame' || this.fixed) {
this.moveImage(this.touches[0], touches[0]);
} else {
this.scaleFrame(this.touches[0], touches[0], this.type);
}
} else if (this.touches.length === 2 && touches.length === 2) {
const ta = this.touches[0];
const tb = this.touches[1];
let tc = touches[0];
let td = touches[1];
if (ta.identifier !== tc.identifier) {
const temp = tc;
tc = td;
td = temp;
}
this.scaleImage(ta, tb, tc, td);
}
},
touchEnd(ev) {
this.type = '';
this.touches = [];
this.startTime();
},
touchCancel(ev) {
this.type = '';
this.touches = [];
this.startTime();
},
startTime() {
this.stopTime();
this.timeoutId = setTimeout(() => {
this.trimImage();
}, 800);
},
stopTime() {
if (this.timeoutId >= 0) {
clearTimeout(this.timeoutId);
this.timeoutId = -1;
}
},
4.4 图片放大事件
这里图片移动主要代码难点、注意点有以下两点:
1.移动范围的控制,即保证裁剪框保证在图片区域内。
2.图片缩放过程中会存在位移,即图像缩放一定是矢量位移。
// 图片移动
moveImage(ta, tb) {
const ax = tb.clientX - ta.clientX;
const ay = tb.clientY - ta.clientY;
this.image.left = this.start.image.left + ax;
this.image.top = this.start.image.top + ay;
if (this.image.left > this.frame.left) {
this.image.left = this.frame.left;
}
if (this.image.top > this.frame.top) {
this.image.top = this.frame.top;
}
if (
this.image.left + this.image.width <
this.frame.left + this.frame.width
) {
this.image.left = this.frame.left + this.frame.width - this.image.width;
}
if (
this.image.top + this.image.height <
this.frame.top + this.frame.height
) {
this.image.top = this.frame.top + this.frame.height - this.image.height;
}
},
// 图片放大
scaleImage(ta, tb, tc, td) {
const x1 = ta.clientX;
const y1 = ta.clientY;
const x2 = tb.clientX;
const y2 = tb.clientY;
const x3 = tc.clientX;
const y3 = tc.clientY;
const x4 = td.clientX;
const y4 = td.clientY;
const ol = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
const el = Math.sqrt((x3 - x4) * (x3 - x4) + (y3 - y4) * (y3 - y4));
const ocx = (x1 + x2) / 2;
const ocy = (y1 + y2) / 2;
const ecx = (x3 + x4) / 2;
const ecy = (y3 + y4) / 2;
const ax = ecx - ocx;
const ay = ecy - ocy;
let scale = el / ol;
if (this.start.image.width * scale < this.frame.width) {
scale = this.frame.width / this.start.image.width;
}
if (this.start.image.height * scale < this.frame.height) {
scale = this.frame.height / this.start.image.height;
}
if (this.start.image.width * scale < this.frame.width) {
scale = this.frame.width / this.start.image.width;
}
this.image.left =
this.start.image.left +
ax -
(ocx - this.start.image.left) * (scale - 1);
this.image.top =
this.start.image.top + ay - (ocy - this.start.image.top) * (scale - 1);
this.image.width = this.start.image.width * scale;
this.image.height = this.start.image.height * scale;
if (this.image.left > this.frame.left) {
this.image.left = this.frame.left;
}
if (this.image.top > this.frame.top) {
this.image.top = this.frame.top;
}
if (
this.image.left + this.image.width <
this.frame.left + this.frame.width
) {
this.image.left = this.frame.left + this.frame.width - this.image.width;
}
if (
this.image.top + this.image.height <
this.frame.top + this.frame.height
) {
this.image.top = this.frame.top + this.frame.height - this.image.height;
}
},
4.5 图片绘制上传
理解前面代码后,下面的代码没有难点了,重点掌握CanvasContext.toDataURL({})方法
canvas.toDataURL(type, encoderOptions)
type 可选
图片格式,默认为 image/png
encoderOptions 可选
在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略
**方法返回一个包含图片展示的 data URI **。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。
// 确认按钮事件
onok() {
const scale = this.image.width / this.real.width;
const x = (this.frame.left - this.image.left) / scale;
const y = (this.frame.top - this.image.top) / scale;
const width = this.frame.width / scale;
const height = this.frame.height / scale;
let tw = width;
let th = height;
if (this.fixed) {
tw = this.width / 2;
th = this.height / 2;
} else {
if (tw > this.maxWidth / 2) {
const sc = this.maxWidth / 2 / tw;
tw = tw * sc;
th = th * sc;
}
if (th > this.maxHeight / 2) {
let sc = this.maxHeight / 2 / th;
th = th * sc;
tw = tw * sc;
}
}
this.target.width = tw;
this.target.height = th;
// uni.showLoading({
// title: '正在裁剪',
// });
setTimeout(() => {
this.targetContext.drawImage(
this.url,
x,
y,
width,
height,
0,
0,
tw,
th
);
this.targetContext.draw(false, () => {
const CanvasContext = my.createCanvasContext('target');
CanvasContext.toDataURL({}).then((dataURL) => {
this.upLoadPic(dataURL);
});
});
}, 100);
},
5.完整代码
组件式开发,开箱即用,注意这边依赖uni框架、小程序下的native交互,H5慎用!!
<template>
<view v-show="url" class="ksp-image-cutter">
<!-- 目标图像载体 -->
<canvas
id="target"
:style="{ width: target.width + 'px', height: target.height + 'px' }"
canvas-id="target"
></canvas>
<!-- 主体 -->
<view class="body">
<!-- 裁剪的图片 -->
<image
v-if="url"
lazy-load
class="image"
:style="{
left: image.left + 'px',
top: image.top + 'px',
width: image.width + 'px',
height: image.height + 'px',
}"
:src="url"
@load="imageLoad"
></image>
<!-- <view v-if="mask.show" class="mask"></view> -->
<!-- 裁剪区域 -->
<view
class="plank"
@touchstart="touchStart($event, 'plank')"
@touchmove="touchMove"
@touchend="touchEnd"
@touchcancel="touchCancel"
>
<view
class="frame"
:style="{
left: frame.left + 'px',
top: frame.top + 'px',
width: frame.width + 'px',
height: frame.height + 'px',
}"
@touchstart="touchStart($event, 'frame')"
@touchstart.stop.prevent="touchHandle"
>
<!-- 裁剪载体 -->
<canvas
v-if="mask.show"
class="canvas"
:style="{ width: frame.width + 'px', height: frame.height + 'px' }"
canvas-id="canvas"
></canvas>
<!-- 窗口 -->
<view class="rect"></view>
<!-- 分割线 -->
<!-- <view class="line-one"></view>
<view class="line-two"></view>
<view class="line-three"></view>
<view class="line-four"></view> -->
<!-- 窗口调节键 -->
<view
class="frame-left"
@touchstart="touchStart($event, 'left')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-right"
@touchstart="touchStart($event, 'right')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-top"
@touchstart="touchStart($event, 'top')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-bottom"
@touchstart="touchStart($event, 'bottom')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-left-top"
@touchstart="touchStart($event, 'left-top')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-left-bottom"
@touchstart="touchStart($event, 'left-bottom')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-right-top"
@touchstart="touchStart($event, 'right-top')"
@touchstart.stop.prevent="touchHandle"
></view>
<view
class="frame-right-bottom"
@touchstart="touchStart($event, 'right-bottom')"
@touchstart.stop.prevent="touchHandle"
></view>
</view>
</view>
</view>
<!-- 操作区域 -->
<view class="toolbar">
<button class="btn-cancel" @tap="oncancel">取消</button>
<button class="btn-ok" @tap="onok">选取</button>
</view>
</view>
</template>
<script>
import { getIn } from '@/common/script/utils';
import { myYaYunService } from '@/pages/myyayun/common/myYaYunService';
import { log } from 'lodash-decorators/utils';
export default {
props: {
url: {
type: String,
default: '',
},
fixed: {
type: Boolean,
default: false,
},
width: {
type: Number,
default: 200,
},
height: {
type: Number,
default: 200,
},
maxWidth: {
type: Number,
default: 1024,
},
maxHeight: {
type: Number,
default: 1024,
},
blob: {
type: Boolean,
default: true,
},
},
data() {
return {
mask: {
show: false,
},
frame: {
left: 50,
top: 50,
width: this.width,
height: this.height,
},
image: {
left: 20,
top: 20,
width: 300,
height: 400,
},
real: {
width: 100,
height: 100,
},
target: {
width: this.width,
height: this.height,
},
touches: [],
type: '',
start: {
frame: {
left: 0,
top: 0,
width: 0,
height: 0,
},
image: {
left: 0,
top: 0,
width: 0,
height: 0,
},
},
timeoutId: -1,
context: null,
};
},
mounted() {
//#ifdef H5
this.$el.addEventListener('touchmove', (ev) => {
ev.preventDefault();
});
// #endif
// 创建canvas
this.context = uni.createCanvasContext('canvas', this);
this.targetContext = uni.createCanvasContext('target', this);
},
methods: {
// 图片首次加载
imageLoad(ev) {
this.mask.show = true;
this.real.width = ev.detail.width;
this.real.height = ev.detail.height;
this.image.width = ev.detail.width;
this.image.height = ev.detail.height;
this.frame.width = this.width;
this.frame.height = this.height;
if (!this.fixed) {
this.frame.width = this.image.width;
this.frame.height = this.image.height;
}
const query = uni.createSelectorQuery().in(this);
query
.select('.body')
.boundingClientRect((data) => {
const bw = data.width;
const bh = data.height;
const fw = this.frame.width;
const fh = this.frame.height;
let tw = bw * 0.8;
let th = bh * 0.8;
let sx = tw / fw;
let sy = th / fh;
let scale = sx;
if (sx < sy) {
scale = sy;
}
tw = fw * scale;
th = fh * scale;
const tx = (bw - tw) / 2;
const ty = (bh - th) / 2;
this.frame.width = tw;
this.frame.height = th;
this.frame.left = tx;
this.frame.top = ty;
const iw = this.image.width;
const ih = this.image.height;
sx = tw / iw;
sy = th / ih;
scale = sx;
if (sx < sy) {
scale = sy;
}
this.image.width = iw * scale;
this.image.height = ih * scale;
this.image.left = (bw - this.image.width) / 2;
this.image.top = (bh - this.image.height) / 2;
setTimeout(() => {
this.trimImage();
}, 100);
})
.exec();
},
// 阻止手指事件冒泡传递
touchHandle() {},
// 手指开始触摸事件
touchStart(ev, type) {
this.stopTime();
this.mask.show = false;
if (this.touches.length === 0) {
this.type = type;
this.start.frame.left = this.frame.left;
this.start.frame.top = this.frame.top;
this.start.frame.width = this.frame.width;
this.start.frame.height = this.frame.height;
this.start.image.left = this.image.left;
this.start.image.top = this.image.top;
this.start.image.width = this.image.width;
this.start.image.height = this.image.height;
}
const touches = ev.changedTouches;
for (let i = 0; i < touches.length; i++) {
const touch = touches[i];
// this.touches[touch.identifier] = touch;
this.touches.push(touch);
}
},
// 手指移动事件
touchMove(ev) {
this.stopTime();
ev.preventDefault();
const touches = ev.touches;
if (this.touches.length === 1) {
if (this.type === 'plank' || this.type === 'frame' || this.fixed) {
this.moveImage(this.touches[0], touches[0]);
} else {
this.scaleFrame(this.touches[0], touches[0], this.type);
}
} else if (this.touches.length === 2 && touches.length === 2) {
const ta = this.touches[0];
const tb = this.touches[1];
let tc = touches[0];
let td = touches[1];
if (ta.identifier !== tc.identifier) {
const temp = tc;
tc = td;
td = temp;
}
this.scaleImage(ta, tb, tc, td);
}
},
touchEnd(ev) {
this.type = '';
this.touches = [];
this.startTime();
},
touchCancel(ev) {
this.type = '';
this.touches = [];
this.startTime();
},
startTime() {
this.stopTime();
this.timeoutId = setTimeout(() => {
this.trimImage();
}, 800);
},
stopTime() {
if (this.timeoutId >= 0) {
clearTimeout(this.timeoutId);
this.timeoutId = -1;
}
},
// 初始化图片大小、位置
trimImage() {
this.mask.show = true;
const query = uni.createSelectorQuery().in(this);
query
.select('.body')
.boundingClientRect((data) => {
const bw = data.width;
const bh = data.height;
const fw = this.frame.width;
const fh = this.frame.height;
let tw = bw;
let th = bh;
// let tw = bw * 0.8;
// let th = bh * 0.8;
const sx = tw / fw;
const sy = th / fh;
let scale = sx;
if (sx > sy) {
scale = sy;
}
tw = fw * scale;
th = fh * scale;
const tx = (bw - tw) / 2;
const ty = (bh - th) / 2;
const ax =
tx -
this.frame.left +
(this.frame.left - this.image.left) * (1 - scale);
const ay =
ty -
this.frame.top +
(this.frame.top - this.image.top) * (1 - scale);
this.frame.width = tw;
this.frame.height = th;
this.frame.left = tx;
this.frame.top = ty;
this.image.width *= scale;
this.image.height *= scale;
this.image.left += ax;
this.image.top += ay;
})
.exec();
setTimeout(() => {
const scale = this.image.width / this.real.width;
const x = (this.frame.left - this.image.left) / scale;
const y = (this.frame.top - this.image.top) / scale;
const width = this.frame.width / scale;
const height = this.frame.height / scale;
this.context.drawImage(
this.url,
x,
y,
width,
height,
0,
0,
this.frame.width,
this.frame.height
);
this.context.draw(false);
}, 100);
},
// 图片移动
moveImage(ta, tb) {
const ax = tb.clientX - ta.clientX;
const ay = tb.clientY - ta.clientY;
this.image.left = this.start.image.left + ax;
this.image.top = this.start.image.top + ay;
if (this.image.left > this.frame.left) {
this.image.left = this.frame.left;
}
if (this.image.top > this.frame.top) {
this.image.top = this.frame.top;
}
if (
this.image.left + this.image.width <
this.frame.left + this.frame.width
) {
this.image.left = this.frame.left + this.frame.width - this.image.width;
}
if (
this.image.top + this.image.height <
this.frame.top + this.frame.height
) {
this.image.top = this.frame.top + this.frame.height - this.image.height;
}
},
// 图片放大
scaleImage(ta, tb, tc, td) {
const x1 = ta.clientX;
const y1 = ta.clientY;
const x2 = tb.clientX;
const y2 = tb.clientY;
const x3 = tc.clientX;
const y3 = tc.clientY;
const x4 = td.clientX;
const y4 = td.clientY;
const ol = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
const el = Math.sqrt((x3 - x4) * (x3 - x4) + (y3 - y4) * (y3 - y4));
const ocx = (x1 + x2) / 2;
const ocy = (y1 + y2) / 2;
const ecx = (x3 + x4) / 2;
const ecy = (y3 + y4) / 2;
const ax = ecx - ocx;
const ay = ecy - ocy;
let scale = el / ol;
if (this.start.image.width * scale < this.frame.width) {
scale = this.frame.width / this.start.image.width;
}
if (this.start.image.height * scale < this.frame.height) {
scale = this.frame.height / this.start.image.height;
}
if (this.start.image.width * scale < this.frame.width) {
scale = this.frame.width / this.start.image.width;
}
this.image.left =
this.start.image.left +
ax -
(ocx - this.start.image.left) * (scale - 1);
this.image.top =
this.start.image.top + ay - (ocy - this.start.image.top) * (scale - 1);
this.image.width = this.start.image.width * scale;
this.image.height = this.start.image.height * scale;
if (this.image.left > this.frame.left) {
this.image.left = this.frame.left;
}
if (this.image.top > this.frame.top) {
this.image.top = this.frame.top;
}
if (
this.image.left + this.image.width <
this.frame.left + this.frame.width
) {
this.image.left = this.frame.left + this.frame.width - this.image.width;
}
if (
this.image.top + this.image.height <
this.frame.top + this.frame.height
) {
this.image.top = this.frame.top + this.frame.height - this.image.height;
}
},
// 放大事件
scaleFrame(ta, tb, type) {
const ax = tb.clientX - ta.clientX;
const ay = tb.clientY - ta.clientY;
let x1 = this.start.frame.left;
let y1 = this.start.frame.top;
let x2 = this.start.frame.left + this.start.frame.width;
let y2 = this.start.frame.top + this.start.frame.height;
if (type === 'left') {
x1 += ax;
} else if (type === 'right') {
x2 += ax;
} else if (type === 'top') {
y1 += ay;
} else if (type === 'bottom') {
y2 += ay;
} else if (type === 'left-top') {
x1 += ax;
y1 += ay;
} else if (type === 'left-bottom') {
x1 += ax;
y2 += ay;
} else if (type === 'right-top') {
x2 += ax;
y1 += ay;
} else if (type === 'right-bottom') {
x2 += ax;
y2 += ay;
}
if (x1 < this.image.left) {
x1 = this.image.left;
}
if (y1 < this.image.top) {
y1 = this.image.top;
}
if (x2 > this.image.left + this.image.width) {
x2 = this.image.left + this.image.width;
}
if (y2 > this.image.top + this.image.height) {
y2 = this.image.top + this.image.height;
}
this.frame.left = x1;
this.frame.top = y1;
this.frame.width = x2 - x1;
this.frame.height = y2 - y1;
},
// 确认按钮事件
onok() {
const scale = this.image.width / this.real.width;
const x = (this.frame.left - this.image.left) / scale;
const y = (this.frame.top - this.image.top) / scale;
const width = this.frame.width / scale;
const height = this.frame.height / scale;
let tw = width;
let th = height;
if (this.fixed) {
tw = this.width / 2;
th = this.height / 2;
} else {
if (tw > this.maxWidth / 2) {
const sc = this.maxWidth / 2 / tw;
tw = tw * sc;
th = th * sc;
}
if (th > this.maxHeight / 2) {
let sc = this.maxHeight / 2 / th;
th = th * sc;
tw = tw * sc;
}
}
this.target.width = tw;
this.target.height = th;
// uni.showLoading({
// title: '正在裁剪',
// });
setTimeout(() => {
this.targetContext.drawImage(
this.url,
x,
y,
width,
height,
0,
0,
tw,
th
);
this.targetContext.draw(false, () => {
const CanvasContext = my.createCanvasContext('target');
CanvasContext.toDataURL({}).then((dataURL) => {
this.upLoadPic(dataURL);
});
});
}, 100);
},
// 取消
oncancel() {
// 抛出失败事件
this.$emit('cancel');
},
// 上传图片
async upLoadPic(base64Data) {
const [err, res] = await myYaYunService.upLoadPicApi(base64Data);
if (res) {
let url = res.data;
// console.log('url',url);
if (url && url.length > 0) {
this.imgCallBack('', url);
}
}
},
// 抛出成功事件
imgCallBack(action, url) {
this.$emit('ok', {
path: url,
});
},
},
};
</script>
<style scoped>
page {
background-color: black;
}
.ksp-image-cutter {
/* position: absolute; */
position: fixed;
width: 100%;
height: 100%;
top: 0;
bottom: 0;
z-index: 1000;
background-color: black;
}
.toolbar {
position: absolute;
width: 100%;
height: 134rpx;
left: 0rpx;
bottom: 10rpx;
box-sizing: border-box;
}
.btn-cancel {
position: absolute;
width: 339rpx;
height: 98rpx;
left: 24rpx;
font-size: 36rpx;
color: #fff;
border: none;
background: none;
}
.btn-ok {
position: absolute;
width: 339rpx;
height: 98rpx;
right: 24rpx;
font-size: 36rpx;
color: #fff;
border: none;
background: none;
}
.body {
position: absolute;
left: 0rpx;
right: 0rpx;
top: 0rpx;
bottom: 0rpx;
background: black;
overflow: hidden;
}
.mask {
position: absolute;
left: 0rpx;
right: 0rpx;
top: 0rpx;
bottom: 0rpx;
background: black;
opacity: 0.4;
}
.plank {
position: absolute;
left: 0rpx;
right: 0rpx;
top: 0rpx;
bottom: 0rpx;
}
.image {
position: absolute;
}
.frame {
position: absolute;
}
.canvas {
position: absolute;
display: block;
left: 0px;
top: 0px;
}
.rect {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
/* width: 600rpx;
height: 600rpx; */
padding: 211rpx 375rpx;
border: 1000rpx solid rgba(0, 0, 0, 0.5);
/* border-radius: 50%; */
}
.line-one {
position: absolute;
width: 100%;
height: 1px;
background: white;
left: 0;
top: 33.3%;
}
.line-two {
position: absolute;
width: 100%;
height: 1px;
background: white;
left: 0;
top: 66.7%;
}
.line-three {
position: absolute;
width: 1px;
height: 100%;
background: white;
top: 0;
left: 33.3%;
}
.line-four {
position: absolute;
width: 1px;
height: 100%;
background: white;
top: 0;
left: 66.7%;
}
.frame-left {
position: absolute;
height: 100%;
width: 8px;
left: -4px;
top: 0;
}
.frame-right {
position: absolute;
height: 100%;
width: 8px;
right: -4px;
top: 0;
}
.frame-top {
position: absolute;
width: 100%;
height: 8px;
top: -4px;
left: 0;
}
.frame-bottom {
position: absolute;
width: 100%;
height: 8px;
bottom: -4px;
left: 0;
}
.frame-left-top {
position: absolute;
width: 20px;
height: 20px;
left: -6px;
top: -6px;
}
.frame-left-bottom {
position: absolute;
width: 20px;
height: 20px;
left: -6px;
bottom: -6px;
}
.frame-right-top {
position: absolute;
width: 20px;
height: 20px;
right: -6px;
top: -6px;
}
.frame-right-bottom {
position: absolute;
width: 20px;
height: 20px;
right: -6px;
bottom: -6px;
}
</style>