1075 lines
25 KiB
Vue
1075 lines
25 KiB
Vue
<template>
|
||
<view class="lime-clipper" :class="{open: value}" disable-scroll :style="'z-index: ' + zIndex + ';' + customStyle">
|
||
<view class="lime-clipper-mask" @touchstart.stop.prevent="clipTouchStart"
|
||
@touchmove.stop.prevent="clipTouchMove" @touchend.stop.prevent="clipTouchEnd">
|
||
<view class="lime-clipper__content" :style="clipStyle">
|
||
<view class="lime-clipper__edge" v-for="(item, index) in [0, 0, 0, 0]" :key="index"></view>
|
||
</view>
|
||
</view>
|
||
<image class="lime-clipper-image" @error="imageLoad" @load="imageLoad" @touchstart="imageTouchStart"
|
||
@touchmove="imageTouchMove" @touchend="imageTouchEnd" :src="image"
|
||
:mode="imageWidth == 'auto' ? 'widthFix' : 'scaleToFill'" v-if="image" :style="imageStyle" />
|
||
<canvas v-if="_canvasId" :canvas-id="_canvasId" :id="_canvasId" disable-scroll
|
||
:style="'width: ' + canvasWidth * scaleRatio + 'px; height:' + canvasHeight * scaleRatio + 'px;'"
|
||
class="lime-clipper-canvas"></canvas>
|
||
<view class="lime-clipper-tools">
|
||
<view class="lime-clipper-tools__btns">
|
||
<!-- <view v-if="isShowCancelBtn" @tap="cancel">
|
||
<slot name="cancel" v-if="$slots.cancel" />
|
||
<view v-else class="cancel">取消</view>
|
||
</view>
|
||
<view v-if="isShowPhotoBtn" @tap="uploadImage">
|
||
<slot name="photo" v-if="$slots.photo" />
|
||
<image v-else src="/uni_modules/lime-clipper/static/photo.svg" />
|
||
</view>
|
||
<view v-if="isShowRotateBtn" @tap="rotate">
|
||
<slot name="rotate" v-if="$slots.rotate" />
|
||
<image v-else src="/uni_modules/lime-clipper/static/rotate.svg" data-type="inverse" />
|
||
</view>
|
||
<view v-if="isShowConfirmBtn" @tap="confirm" >
|
||
<slot name="confirm" v-if="$slots.confirm" />
|
||
<view v-else class="confirm">确定</view>
|
||
</view> -->
|
||
<view v-if="isShowCancelBtn" @tap="cancel">
|
||
<slot name="cancel">
|
||
<text class="text cancel">取消</text>
|
||
</slot>
|
||
</view>
|
||
<view v-if="isShowPhotoBtn" @tap="uploadImage">
|
||
<slot name="photo">
|
||
<text class="text icon photo"></text>
|
||
</slot>
|
||
</view>
|
||
<view v-if="isShowRotateBtn" @tap="rotate">
|
||
<slot name="rotate">
|
||
<text class="text icon rotate"></text>
|
||
</slot>
|
||
</view>
|
||
<view v-if="isShowConfirmBtn" @tap="confirm">
|
||
<slot name="confirm">
|
||
<text class="text confirm" :style="{background: confirmBgColor}">确定</text>
|
||
</slot>
|
||
</view>
|
||
</view>
|
||
<slot></slot>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
/**
|
||
* Clipper 图片裁剪组件
|
||
* @description 用于实现图片裁剪功能的交互式组件,支持缩放、旋转、比例锁定等高级功能
|
||
* @tutorial https://ext.dcloud.net.cn/plugin?name=lime-clipper
|
||
*
|
||
* @property {string} customStyle 自定义容器样式(支持CSS字符串)
|
||
* @property {number} zIndex]画布层级(默认:999)
|
||
* @property {string} imageUrl 源图片地址(支持本地/网络路径)
|
||
* @property {'jpg' | 'png'} fileType 输出图片格式(默认:jpg)
|
||
* @property {number} quality 图片压缩质量(0-1,默认0.8)
|
||
* @property {number} width 裁剪区域默认宽度(单位rpx)
|
||
* @property {number} height 裁剪区域默认高度(单位rpx)
|
||
* @property {number} minWidth 最小裁剪宽度(默认:50px)
|
||
* @property {number} maxWidth 最大裁剪宽度(默认:屏幕宽度)
|
||
* @property {number} destWidth 输出图片宽度(默认按裁剪尺寸)
|
||
* @property {number} destHeight 输出图片高度(默认按裁剪尺寸)
|
||
* @property {number} minHeight 最小裁剪高度(默认:50px)
|
||
* @property {number} maxHeight 最大裁剪高度(默认:屏幕高度)
|
||
* @property {boolean} isLockWidth 锁定裁剪宽度(禁止调整)
|
||
* @property {boolean} isLockHeight 锁定裁剪高度(禁止调整)
|
||
* @property {boolean} isLockRatio 锁定宽高比(默认false)
|
||
* @property {number} scaleRatio 初始缩放比例(默认1.0)
|
||
* @property {number} minRatio 最小缩放比例(默认0.5)
|
||
* @property {number} maxRatio 最大缩放比例(默认3.0)
|
||
* @property {boolean} isDisableScale 禁用缩放功能
|
||
* @property {boolean} isDisableRotate 禁用旋转功能
|
||
* @property {boolean} isLimitMove 限制移动不超出图片范围
|
||
* @property {boolean} isShowPhotoBtn 显示相册选择按钮
|
||
* @property {boolean} isShowRotateBtn 显示旋转按钮
|
||
* @property {boolean} isShowConfirmBtn 显示确认按钮
|
||
* @property {boolean} isShowCancelBtn 显示取消按钮
|
||
* @property {number} rotateAngle 初始旋转角度(单位度)
|
||
* @property {any} source 自定义图片源(预留扩展)
|
||
* @property {string} confirmBgColor 确认按钮背景色
|
||
* @property {string} canvasId 画布唯一标识(多实例时必填)
|
||
* @event {Function} ready 准完成触发
|
||
* @event {Function} change 变化时触发
|
||
* @event {Function} rotate 旋转时触发
|
||
* @event {Function} cancel 取消时触发
|
||
* @event {Function} success 点击确定时成功生成图片后触发
|
||
*/
|
||
|
||
import {
|
||
determineDirection,
|
||
calcImageOffset,
|
||
calcImageScale,
|
||
calcImageSize,
|
||
calcPythagoreanTheorem,
|
||
clipTouchMoveOfCalculate,
|
||
imageTouchMoveOfCalcOffset,
|
||
} from './utils.js';
|
||
const cache = {}
|
||
export default {
|
||
name: 'lime-clipper',
|
||
props: {
|
||
value: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
// #ifdef MP-WEIXIN
|
||
type: {
|
||
type: String,
|
||
default: '2d'
|
||
},
|
||
// #endif
|
||
customStyle: {
|
||
type: String,
|
||
},
|
||
zIndex: {
|
||
type: Number,
|
||
default: 99
|
||
},
|
||
imageUrl: {
|
||
type: String
|
||
},
|
||
fileType: {
|
||
type: String,
|
||
default: 'png'
|
||
},
|
||
quality: {
|
||
type: Number,
|
||
default: 1
|
||
},
|
||
width: {
|
||
type: Number,
|
||
default: 400
|
||
},
|
||
height: {
|
||
type: Number,
|
||
default: 400
|
||
},
|
||
minWidth: {
|
||
type: Number,
|
||
default: 200
|
||
},
|
||
maxWidth: {
|
||
type: Number,
|
||
default: 600
|
||
},
|
||
destWidth: Number,
|
||
destHeight: Number,
|
||
minHeight: {
|
||
type: Number,
|
||
default: 200
|
||
},
|
||
maxHeight: {
|
||
type: Number,
|
||
default: 600
|
||
},
|
||
isLockWidth: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
isLockHeight: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
isLockRatio: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
scaleRatio: {
|
||
type: Number,
|
||
default: 1
|
||
},
|
||
minRatio: {
|
||
type: Number,
|
||
default: 0.5
|
||
},
|
||
maxRatio: {
|
||
type: Number,
|
||
default: 2
|
||
},
|
||
isDisableScale: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
isDisableRotate: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
isLimitMove: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
isShowPhotoBtn: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
isShowRotateBtn: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
isShowConfirmBtn: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
isShowCancelBtn: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
rotateAngle: {
|
||
type: Number,
|
||
default: 90
|
||
},
|
||
source: {
|
||
type: Object,
|
||
default: () => ({
|
||
album: '从相册中选择',
|
||
camera: '拍照',
|
||
// #ifdef MP-WEIXIN
|
||
message: '从微信中选择'
|
||
// #endif
|
||
})
|
||
},
|
||
confirmBgColor: {
|
||
type: String,
|
||
default: null
|
||
},
|
||
canvasId: {
|
||
type: String,
|
||
default: null
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
canvasWidth: 0,
|
||
canvasHeight: 0,
|
||
clipX: 0,
|
||
clipY: 0,
|
||
clipWidth: 0,
|
||
clipHeight: 0,
|
||
animation: false,
|
||
imageWidth: 0,
|
||
imageHeight: 0,
|
||
imageTop: 0,
|
||
imageLeft: 0,
|
||
scale: 1,
|
||
angle: 0,
|
||
image: '',
|
||
imageInit: false,
|
||
sysinfo: {},
|
||
throttleTimer: null,
|
||
throttleFlag: true,
|
||
timeClipCenter: null,
|
||
flagClipTouch: false,
|
||
flagEndTouch: false,
|
||
clipStart: {},
|
||
animationTimer: null,
|
||
touchRelative: [{
|
||
x: 0,
|
||
y: 0
|
||
}],
|
||
hypotenuseLength: 0,
|
||
ctx: null,
|
||
// canvasId: 'lime-clipper'
|
||
};
|
||
},
|
||
computed: {
|
||
_canvasId() {
|
||
return this.canvasId ?? `l-clipper-${this._ ? this._.uid : this._uid}`
|
||
},
|
||
clipStyle() {
|
||
const {
|
||
clipWidth,
|
||
clipHeight,
|
||
clipY,
|
||
clipX,
|
||
animation
|
||
} = this
|
||
return `
|
||
width: ${clipWidth}px;
|
||
height:${clipHeight}px;
|
||
transition-property: ${animation ? '' : 'background'};
|
||
left: ${clipX}px;
|
||
top: ${clipY}px
|
||
`
|
||
},
|
||
imageStyle() {
|
||
const {
|
||
imageWidth,
|
||
imageHeight,
|
||
imageLeft,
|
||
imageTop,
|
||
animation,
|
||
scale,
|
||
angle
|
||
} = this
|
||
return `
|
||
width: ${imageWidth ? imageWidth + 'px' : 'auto'};
|
||
height: ${imageHeight ? imageHeight + 'px' : 'auto'};
|
||
transform: translate3d(${imageLeft - imageWidth / 2}px, ${imageTop - imageHeight / 2}px, 0) scale(${scale}) rotate(${angle}deg);
|
||
transition-duration: ${animation ? 0.35 : 0}s
|
||
`
|
||
},
|
||
clipSize() {
|
||
const {
|
||
clipWidth,
|
||
clipHeight
|
||
} = this;
|
||
return {
|
||
clipWidth,
|
||
clipHeight
|
||
};
|
||
},
|
||
clipPoint() {
|
||
const {
|
||
clipY,
|
||
clipX
|
||
} = this;
|
||
return {
|
||
clipY,
|
||
clipX
|
||
};
|
||
}
|
||
},
|
||
watch: {
|
||
value(val) {
|
||
if (!val) {
|
||
this.animation = 0
|
||
this.angle = 0
|
||
} else {
|
||
if (this.imageUrl) {
|
||
const {
|
||
imageWidth,
|
||
imageHeight,
|
||
imageLeft,
|
||
imageTop,
|
||
scale,
|
||
clipX,
|
||
clipY,
|
||
clipWidth,
|
||
clipHeight,
|
||
path
|
||
} = cache?.[this.imageUrl] || {}
|
||
if (path != this.image) {
|
||
this.image = this.imageUrl;
|
||
} else {
|
||
this.setDiffData({
|
||
imageWidth,
|
||
imageHeight,
|
||
imageLeft,
|
||
imageTop,
|
||
scale,
|
||
clipX,
|
||
clipY,
|
||
clipWidth,
|
||
clipHeight
|
||
})
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
},
|
||
imageUrl(url) {
|
||
this.image = url
|
||
},
|
||
image: {
|
||
handler: async function(url) {
|
||
this.getImageInfo(url)
|
||
},
|
||
// immediate: true,
|
||
},
|
||
clipSize({
|
||
widthVal,
|
||
heightVal
|
||
}) {
|
||
let {
|
||
minWidth,
|
||
minHeight
|
||
} = this;
|
||
minWidth = uni.upx2px(minWidth);
|
||
minHeight = uni.upx2px(minHeight);
|
||
if (widthVal < minWidth) {
|
||
this.setDiffData({
|
||
clipWidth: minWidth
|
||
})
|
||
}
|
||
if (heightVal < minHeight) {
|
||
this.setDiffData({
|
||
clipHeight: minHeight
|
||
})
|
||
}
|
||
this.calcClipSize();
|
||
},
|
||
angle(val) {
|
||
this.animation = this.imageInit;
|
||
this.moveStop();
|
||
const {
|
||
isLimitMove
|
||
} = this;
|
||
if (isLimitMove && val % 90) {
|
||
this.setDiffData({
|
||
angle: Math.round(val / 90) * 90
|
||
})
|
||
}
|
||
this.imgMarginDetectionScale();
|
||
},
|
||
animation(val) {
|
||
clearTimeout(this.animationTimer);
|
||
if (val) {
|
||
let animationTimer = setTimeout(() => {
|
||
this.setDiffData({
|
||
animation: false
|
||
})
|
||
}, 260);
|
||
this.setDiffData({
|
||
animationTimer
|
||
})
|
||
this.animationTimer = animationTimer;
|
||
}
|
||
},
|
||
isLimitMove(val) {
|
||
if (val) {
|
||
if (this.angle % 90) {
|
||
this.setDiffData({
|
||
angle: Math.round(this.angle / 90) * 90
|
||
})
|
||
}
|
||
this.imgMarginDetectionScale();
|
||
}
|
||
},
|
||
clipPoint() {
|
||
this.cutDetectionPosition();
|
||
},
|
||
width(width, oWidth) {
|
||
if (width !== oWidth) {
|
||
this.setDiffData({
|
||
clipWidth: uni.upx2px(width) //width / 2
|
||
})
|
||
}
|
||
},
|
||
height(height, oHeight) {
|
||
if (height !== oHeight) {
|
||
this.setDiffData({
|
||
clipHeight: uni.upx2px(height) //height / 2
|
||
})
|
||
}
|
||
}
|
||
},
|
||
mounted() {
|
||
const sysinfo = uni.getSystemInfoSync();
|
||
this.sysinfo = sysinfo;
|
||
this.setClipInfo();
|
||
this.image = this.imageUrl || this.image
|
||
this.setClipCenter();
|
||
this.calcClipSize();
|
||
this.cutDetectionPosition();
|
||
},
|
||
methods: {
|
||
setDiffData(data) {
|
||
Object.keys(data).forEach(key => {
|
||
if (this[key] !== data[key]) {
|
||
this[key] = data[key];
|
||
}
|
||
});
|
||
},
|
||
getImageInfo(url) {
|
||
if (!url) return;
|
||
if (this.value) {
|
||
uni.showLoading({
|
||
title: '请稍候...',
|
||
mask: true
|
||
});
|
||
}
|
||
this.imageInit = false
|
||
uni.getImageInfo({
|
||
src: url,
|
||
success: res => {
|
||
if (['right', 'left'].includes(res.orientation)) {
|
||
this.imgComputeSize(res.height, res.width);
|
||
} else {
|
||
this.imgComputeSize(res.width, res.height);
|
||
}
|
||
this.image = res.path;
|
||
if (this.isLimitMove) {
|
||
this.imgMarginDetectionScale();
|
||
this.$emit('ready', res);
|
||
}
|
||
const {
|
||
imageWidth,
|
||
imageHeight,
|
||
imageLeft,
|
||
imageTop,
|
||
scale,
|
||
clipX,
|
||
clipY,
|
||
clipWidth,
|
||
clipHeight
|
||
} = this
|
||
cache[url] = Object.assign(res, {
|
||
imageWidth,
|
||
imageHeight,
|
||
imageLeft,
|
||
imageTop,
|
||
scale,
|
||
clipX,
|
||
clipY,
|
||
clipWidth,
|
||
clipHeight
|
||
});
|
||
},
|
||
fail: (err) => {
|
||
this.imgComputeSize();
|
||
if (this.isLimitMove) {
|
||
this.imgMarginDetectionScale();
|
||
}
|
||
}
|
||
});
|
||
|
||
},
|
||
setClipInfo() {
|
||
const {
|
||
width,
|
||
height,
|
||
sysinfo,
|
||
_canvasId
|
||
} = this;
|
||
const clipWidth = width / 2;
|
||
const clipHeight = height / 2;
|
||
const clipY = (sysinfo.windowHeight - clipHeight) / 2;
|
||
const clipX = (sysinfo.windowWidth - clipWidth) / 2;
|
||
const imageLeft = sysinfo.windowWidth / 2;
|
||
const imageTop = sysinfo.windowHeight / 2;
|
||
this.ctx = uni.createCanvasContext(_canvasId, this);
|
||
this.clipWidth = clipWidth;
|
||
this.clipHeight = clipHeight;
|
||
this.clipX = clipX;
|
||
this.clipY = clipY;
|
||
this.canvasHeight = clipHeight;
|
||
this.canvasWidth = clipWidth;
|
||
this.imageLeft = imageLeft;
|
||
this.imageTop = imageTop;
|
||
},
|
||
setClipCenter() {
|
||
const {
|
||
sysInfo,
|
||
clipHeight,
|
||
clipWidth,
|
||
imageTop,
|
||
imageLeft
|
||
} = this;
|
||
let sys = sysInfo || uni.getSystemInfoSync();
|
||
let clipY = (sys.windowHeight - clipHeight) * 0.5;
|
||
let clipX = (sys.windowWidth - clipWidth) * 0.5;
|
||
this.imageTop = imageTop - this.clipY + clipY;
|
||
this.imageLeft = imageLeft - this.clipX + clipX;
|
||
this.clipY = clipY;
|
||
this.clipX = clipX;
|
||
},
|
||
calcClipSize() {
|
||
const {
|
||
clipHeight,
|
||
clipWidth,
|
||
sysinfo,
|
||
clipX,
|
||
clipY
|
||
} = this;
|
||
if (clipWidth > sysinfo.windowWidth) {
|
||
this.setDiffData({
|
||
clipWidth: sysinfo.windowWidth
|
||
})
|
||
} else if (clipWidth + clipX > sysinfo.windowWidth) {
|
||
this.setDiffData({
|
||
clipX: sysinfo.windowWidth - clipX
|
||
})
|
||
}
|
||
if (clipHeight > sysinfo.windowHeight) {
|
||
this.setDiffData({
|
||
clipHeight: sysinfo.windowHeight
|
||
})
|
||
} else if (clipHeight + clipY > sysinfo.windowHeight) {
|
||
this.clipY = sysinfo.windowHeight - clipY;
|
||
this.setDiffData({
|
||
clipY: sysinfo.windowHeight - clipY
|
||
})
|
||
}
|
||
},
|
||
cutDetectionPosition() {
|
||
const {
|
||
clipX,
|
||
clipY,
|
||
sysinfo,
|
||
clipHeight,
|
||
clipWidth
|
||
} = this;
|
||
let cutDetectionPositionTop = () => {
|
||
if (clipY < 0) {
|
||
this.setDiffData({
|
||
clipY: 0
|
||
})
|
||
}
|
||
if (clipY > sysinfo.windowHeight - clipHeight) {
|
||
this.setDiffData({
|
||
clipY: sysinfo.windowHeight - clipHeight
|
||
})
|
||
}
|
||
};
|
||
let cutDetectionPositionLeft = () => {
|
||
if (clipX < 0) {
|
||
this.setDiffData({
|
||
clipX: 0
|
||
})
|
||
}
|
||
if (clipX > sysinfo.windowWidth - clipWidth) {
|
||
this.setDiffData({
|
||
clipX: sysinfo.windowWidth - clipWidth
|
||
})
|
||
}
|
||
};
|
||
if (clipY === null && clipX === null) {
|
||
let newClipY = (sysinfo.windowHeight - clipHeight) * 0.5;
|
||
let newClipX = (sysinfo.windowWidth - clipWidth) * 0.5;
|
||
this.setDiffData({
|
||
clipX: newClipX,
|
||
clipY: newClipY
|
||
})
|
||
} else if (clipY !== null && clipX !== null) {
|
||
cutDetectionPositionTop();
|
||
cutDetectionPositionLeft();
|
||
} else if (clipY !== null && clipX === null) {
|
||
cutDetectionPositionTop();
|
||
this.setDiffData({
|
||
clipX: (sysinfo.windowWidth - clipWidth) / 2
|
||
})
|
||
} else if (clipY === null && clipX !== null) {
|
||
cutDetectionPositionLeft();
|
||
this.setDiffData({
|
||
clipY: (sysinfo.windowHeight - clipHeight) / 2
|
||
})
|
||
}
|
||
},
|
||
imgComputeSize(width, height) {
|
||
const {
|
||
imageWidth,
|
||
imageHeight
|
||
} = calcImageSize(width, height, this);
|
||
this.imageWidth = imageWidth;
|
||
this.imageHeight = imageHeight;
|
||
},
|
||
imgMarginDetectionScale(scale) {
|
||
if (!this.isLimitMove) return;
|
||
const currentScale = calcImageScale(this, scale);
|
||
this.imgMarginDetectionPosition(currentScale);
|
||
},
|
||
imgMarginDetectionPosition(scale) {
|
||
if (!this.isLimitMove) return;
|
||
const {
|
||
scale: currentScale,
|
||
left,
|
||
top
|
||
} = calcImageOffset(this, scale);
|
||
this.setDiffData({
|
||
imageLeft: left,
|
||
imageTop: top,
|
||
scale: currentScale
|
||
})
|
||
},
|
||
throttle() {
|
||
this.setDiffData({
|
||
throttleFlag: true
|
||
})
|
||
},
|
||
moveDuring() {
|
||
clearTimeout(this.timeClipCenter);
|
||
},
|
||
moveStop() {
|
||
clearTimeout(this.timeClipCenter);
|
||
const timeClipCenter = setTimeout(() => {
|
||
if (!this.animation) {
|
||
this.setDiffData({
|
||
imageInit: true,
|
||
animation: true,
|
||
})
|
||
}
|
||
this.setClipCenter();
|
||
}, 800);
|
||
this.setDiffData({
|
||
timeClipCenter
|
||
})
|
||
},
|
||
clipTouchStart(e) {
|
||
// #ifdef H5
|
||
e.preventDefault()
|
||
// #endif
|
||
if (!this.image) {
|
||
uni.showToast({
|
||
title: '请选择图片',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
const currentX = e.touches[0].clientX;
|
||
const currentY = e.touches[0].clientY;
|
||
const {
|
||
clipX,
|
||
clipY,
|
||
clipWidth,
|
||
clipHeight
|
||
} = this;
|
||
const corner = determineDirection(clipX, clipY, clipWidth, clipHeight, currentX, currentY);
|
||
this.moveDuring();
|
||
if (!corner) {
|
||
return
|
||
}
|
||
this.clipStart = {
|
||
width: clipWidth,
|
||
height: clipHeight,
|
||
x: currentX,
|
||
y: currentY,
|
||
clipY,
|
||
clipX,
|
||
corner
|
||
};
|
||
this.flagClipTouch = true;
|
||
this.flagEndTouch = true;
|
||
},
|
||
clipTouchMove(e) {
|
||
// #ifdef H5
|
||
e.stopPropagation()
|
||
e.preventDefault()
|
||
// #endif
|
||
if (!this.image) {
|
||
uni.showToast({
|
||
title: '请选择图片',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
// 只针对单指点击做处理
|
||
if (e.touches.length !== 1) {
|
||
return;
|
||
|
||
}
|
||
const {
|
||
flagClipTouch,
|
||
throttleFlag
|
||
} = this;
|
||
if (flagClipTouch && throttleFlag) {
|
||
const {
|
||
isLockRatio,
|
||
isLockHeight,
|
||
isLockWidth
|
||
} = this;
|
||
if (isLockRatio && (isLockWidth || isLockHeight)) return;
|
||
this.setDiffData({
|
||
throttleFlag: false
|
||
})
|
||
this.throttle();
|
||
const clipData = clipTouchMoveOfCalculate(this, e);
|
||
if (clipData) {
|
||
const {
|
||
width,
|
||
height,
|
||
clipX,
|
||
clipY
|
||
} = clipData;
|
||
if (!isLockWidth && !isLockHeight) {
|
||
this.setDiffData({
|
||
clipWidth: width,
|
||
clipHeight: height,
|
||
clipX,
|
||
clipY
|
||
})
|
||
} else if (!isLockWidth) {
|
||
this.setDiffData({
|
||
clipWidth: width,
|
||
clipX
|
||
})
|
||
} else if (!isLockHeight) {
|
||
this.setDiffData({
|
||
clipHeight: height,
|
||
clipY
|
||
})
|
||
}
|
||
this.imgMarginDetectionScale();
|
||
}
|
||
|
||
}
|
||
},
|
||
clipTouchEnd() {
|
||
this.moveStop();
|
||
this.flagClipTouch = false;
|
||
},
|
||
imageTouchStart(e) {
|
||
// #ifdef H5
|
||
e.preventDefault()
|
||
// #endif
|
||
this.flagEndTouch = false;
|
||
const {
|
||
imageLeft,
|
||
imageTop
|
||
} = this;
|
||
const clientXForLeft = e.touches[0].clientX;
|
||
const clientYForLeft = e.touches[0].clientY;
|
||
|
||
let touchRelative = [];
|
||
if (e.touches.length === 1) {
|
||
touchRelative[0] = {
|
||
x: clientXForLeft - imageLeft,
|
||
y: clientYForLeft - imageTop
|
||
};
|
||
this.touchRelative = touchRelative;
|
||
} else {
|
||
const clientXForRight = e.touches[1].clientX;
|
||
const clientYForRight = e.touches[1].clientY;
|
||
let width = Math.abs(clientXForLeft - clientXForRight);
|
||
let height = Math.abs(clientYForLeft - clientYForRight);
|
||
const hypotenuseLength = calcPythagoreanTheorem(width, height);
|
||
|
||
touchRelative = [{
|
||
x: clientXForLeft - imageLeft,
|
||
y: clientYForLeft - imageTop
|
||
},
|
||
{
|
||
x: clientXForRight - imageLeft,
|
||
y: clientYForRight - imageTop
|
||
}
|
||
];
|
||
this.touchRelative = touchRelative;
|
||
this.hypotenuseLength = hypotenuseLength;
|
||
}
|
||
},
|
||
imageTouchMove(e) {
|
||
// #ifdef H5
|
||
e.preventDefault()
|
||
// #endif
|
||
const {
|
||
flagEndTouch,
|
||
throttleFlag
|
||
} = this;
|
||
if (flagEndTouch || !throttleFlag) return;
|
||
const clientXForLeft = e.touches[0].clientX;
|
||
const clientYForLeft = e.touches[0].clientY;
|
||
this.setDiffData({
|
||
throttleFlag: false
|
||
})
|
||
this.throttle();
|
||
this.moveDuring();
|
||
if (e.touches.length === 1) {
|
||
const {
|
||
left: imageLeft,
|
||
top: imageTop
|
||
} = imageTouchMoveOfCalcOffset(this, clientXForLeft, clientYForLeft);
|
||
this.setDiffData({
|
||
imageLeft,
|
||
imageTop
|
||
})
|
||
this.imgMarginDetectionPosition();
|
||
} else {
|
||
const clientXForRight = e.touches[1].clientX;
|
||
const clientYForRight = e.touches[1].clientY;
|
||
let width = Math.abs(clientXForLeft - clientXForRight),
|
||
height = Math.abs(clientYForLeft - clientYForRight),
|
||
hypotenuse = calcPythagoreanTheorem(width, height),
|
||
scale = this.scale * (hypotenuse / this.hypotenuseLength);
|
||
if (this.isDisableScale) {
|
||
|
||
scale = 1;
|
||
} else {
|
||
scale = scale <= this.minRatio ? this.minRatio : scale;
|
||
scale = scale >= this.maxRatio ? this.maxRatio : scale;
|
||
this.$emit('change', {
|
||
width: this.imageWidth * scale,
|
||
height: this.imageHeight * scale
|
||
});
|
||
}
|
||
|
||
this.imgMarginDetectionScale(scale);
|
||
this.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||
this.scale = scale;
|
||
}
|
||
},
|
||
imageTouchEnd() {
|
||
this.setDiffData({
|
||
flagEndTouch: true
|
||
})
|
||
this.moveStop();
|
||
},
|
||
uploadImage() {
|
||
const itemList = Object.entries(this.source)
|
||
const sizeType = ['original', 'compressed']
|
||
const success = ({
|
||
tempFilePaths: a,
|
||
tempFiles: b
|
||
}) => {
|
||
this.image = a ? a[0] : b[0].path
|
||
};
|
||
const _uploadImage = (type) => {
|
||
if (type !== 'message') {
|
||
uni.chooseImage({
|
||
count: 1,
|
||
sizeType,
|
||
sourceType: [type],
|
||
success
|
||
});
|
||
}
|
||
// #ifdef MP-WEIXIN
|
||
if (type == 'message') {
|
||
wx.chooseMessageFile({
|
||
count: 1,
|
||
type: 'image',
|
||
success
|
||
})
|
||
}
|
||
// #endif
|
||
}
|
||
if (itemList.length > 1) {
|
||
uni.showActionSheet({
|
||
itemList: itemList.map(v => v[1]),
|
||
success: ({
|
||
tapIndex: i
|
||
}) => {
|
||
_uploadImage(itemList[i][0])
|
||
}
|
||
})
|
||
} else {
|
||
_uploadImage(itemList[0][0])
|
||
}
|
||
},
|
||
imageReset() {
|
||
const sys = this.sysinfo || uni.getSystemInfoSync();
|
||
this.moveStop()
|
||
this.scale = 1;
|
||
this.angle = 0;
|
||
|
||
this.imageTop = sys.windowHeight / 2;
|
||
this.imageLeft = sys.windowWidth / 2;
|
||
},
|
||
imageLoad(e) {
|
||
this.imageReset();
|
||
uni.hideLoading();
|
||
this.$emit('ready', e.detail);
|
||
},
|
||
rotate(event) {
|
||
if (this.isDisableRotate) return;
|
||
if (!this.image) {
|
||
uni.showToast({
|
||
title: '请选择图片',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
const {
|
||
rotateAngle
|
||
} = this;
|
||
const originAngle = this.angle
|
||
const type = event.currentTarget.dataset.type;
|
||
if (type === 'along') {
|
||
this.angle = originAngle + rotateAngle
|
||
} else {
|
||
this.angle = originAngle - rotateAngle
|
||
}
|
||
this.$emit('rotate', this.angle);
|
||
},
|
||
confirm() {
|
||
if (!this.image) {
|
||
uni.showToast({
|
||
title: '请选择图片',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
uni.showLoading({
|
||
title: '加载中'
|
||
});
|
||
const {
|
||
canvasHeight,
|
||
canvasWidth,
|
||
clipHeight,
|
||
clipWidth,
|
||
scale,
|
||
ctx,
|
||
imageLeft,
|
||
imageTop,
|
||
clipX,
|
||
clipY,
|
||
angle,
|
||
scaleRatio: dpr,
|
||
image,
|
||
quality,
|
||
fileType,
|
||
type: imageType,
|
||
_canvasId
|
||
} = this;
|
||
const draw = () => {
|
||
const imageWidth = this.imageWidth * scale * dpr;
|
||
const imageHeight = this.imageHeight * scale * dpr;
|
||
const xpos = imageLeft - clipX;
|
||
const ypos = imageTop - clipY;
|
||
// const ctx = uni.createCanvasContext(_canvasId, this);
|
||
ctx.translate(xpos * dpr, ypos * dpr);
|
||
ctx.rotate((angle * Math.PI) / 180);
|
||
ctx.drawImage(image, -imageWidth / 2, -imageHeight / 2, imageWidth, imageHeight);
|
||
ctx.draw(false, () => {
|
||
const width = clipWidth * dpr
|
||
const height = clipHeight * dpr
|
||
let params = {
|
||
x: 0,
|
||
y: 0,
|
||
width,
|
||
height,
|
||
destWidth: this.destWidth || width,
|
||
destHeight: this.destHeight || height,
|
||
canvasId: _canvasId,
|
||
fileType,
|
||
quality,
|
||
success: (res) => {
|
||
// 钉钉小程序
|
||
data.url = res.tempFilePath || res.filePath;
|
||
uni.hideLoading();
|
||
this.$emit('success', data);
|
||
this.$emit('input', false)
|
||
},
|
||
fail: (error) => {
|
||
console.error('error', error)
|
||
this.$emit('fail', error);
|
||
this.$emit('input', false)
|
||
}
|
||
};
|
||
|
||
let data = {
|
||
url: '',
|
||
width,
|
||
height
|
||
};
|
||
uni.canvasToTempFilePath(params, this)
|
||
});
|
||
|
||
};
|
||
|
||
if (canvasWidth !== clipWidth || canvasHeight !== clipHeight) {
|
||
this.canvasWidth = clipWidth;
|
||
this.canvasHeight = clipHeight;
|
||
ctx.draw();
|
||
this.$nextTick(() => {
|
||
setTimeout(() => {
|
||
draw();
|
||
}, 100);
|
||
})
|
||
} else {
|
||
draw();
|
||
}
|
||
},
|
||
cancel() {
|
||
this.$emit('cancel', false)
|
||
this.$emit('input', false)
|
||
},
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
@import './index';
|
||
</style> |