1 Star 0 Fork 10

ruoshuisixue/lime-cllipper

forked from liangei/lime-clipper 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
index.vue 17.25 KB
一键复制 编辑 原始数据 按行查看 历史
liangei 提交于 2020-12-04 19:17 . first commit
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
<template>
<view class="l-clipper" disable-scroll :style="'z-index: ' + zIndex">
<view
class="l-clipper-mask"
@touchstart.stop.prevent="clipTouchStart"
@touchmove.stop.prevent="clipTouchMove"
@touchend.stop.prevent="clipTouchEnd">
<view class="l-clipper__content" :style="clipStyle">
<view class="l-clipper__edge" v-for="(item, index) in [0,0,0,0]" :key="index"></view>
</view>
</view>
<image
class="l-clipper-image"
@error="imageLoad"
@load="imageLoad"
@touchstart="imageTouchStart"
@touchmove="imageTouchMove"
@touchend="imageTouchEnd"
:src="image"
mode="widthFix"
v-if="image"
:style="imageStyle"/>
<canvas
canvas-id="l-clipper"
id="l-clipper"
disable-scroll
:style="'width: ' + (canvasWidth * scaleRatio) + 'px; height:' + (canvasHeight * scaleRatio) + 'px;'"
class="l-clipper-canvas"
></canvas>
<view class="l-clipper-tools">
<view class="l-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="require('./images/photo.svg')" />
</view>
<view v-if="isShowRotateBtn" @tap="rotate">
<slot name="rotate" v-if="$slots.rotate" />
<image v-else :src="require('./images/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>
<slot></slot>
</view>
</view>
</template>
<script>
import { pathToBase64, determineDirection, calcImageOffset, calcImageScale, calcImageSize, calcPythagoreanTheorem, clipTouchMoveOfCalculate, imageTouchMoveOfCalcOffset } from './utils';
export default {
// version: '0.1.0',
name: 'l-clipper',
props: {
zIndex: {
type: Number,
default: 99
},
imageUrl: {
type: String
},
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
},
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
}
},
data() {
return {
canvasWidth: 0,
canvasHeight: 0,
cutX: 0,
cutY: 0,
aWidth: this.width,
aHeight: this.height,
clipWidth: 0,
clipHeight: 0,
cutAnimation: false,
imageWidth: 0,
imageHeight: 0,
imageTop: 0,
imageLeft: 0,
scale: 1,
angle: 0,
image: this.imageUrl,
sysinfo: {},
moveThrottleTimer: null,
moveThrottleFlag: true,
timeCutcenter: null,
flagCutTouch: false,
flagEndTouch: false,
cutstart: {},
cutAnimationTime: null,
touchRelative: [{x: 0,y: 0}],
hypotenuseLength: 0,
ctx: null
};
},
computed: {
clipStyle() {
const {clipWidth, clipHeight, cutY, cutX, cutAnimation} = this
return `
width: ${clipWidth}px;
height:${clipHeight}px;
transition-property: ${cutAnimation ? '' : 'background'};
left: ${cutX}px;
top: ${cutY}px
`
},
imageStyle() {
const {imageWidth, imageHeight, imageLeft, imageTop, cutAnimation, 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: ${cutAnimation ? 0.35 : 0}s
`
},
clipSize() {
const { clipWidth, clipHeight } = this;
return { clipWidth, clipHeight };
},
cutPoint() {
const { cutY, cutX } = this;
return { cutY, cutX };
}
},
watch: {
// #ifdef H5
imageUrl: {
handler: async function(url) {
const res = await pathToBase64(url)
if(res) {
this.image = res
}
},
immediate: true,
},
// #endif
image:{
handler: function (url) {
this.getImageInfo(url)
},
immediate: true,
},
clipSize({ widthVal, heightVal }) {
let { minWidth, minHeight } = this;
minWidth = minWidth / 2;
minHeight = minHeight / 2;
if (widthVal < minWidth) {
this.clipWidth = minWidth;
}
if (heightVal < minHeight) {
this.clipHeight = minHeight;
}
this.computeCutSize();
},
angle(val) {
this.cutAnimation = true;
this.moveStop();
const { isLimitMove } = this;
if (isLimitMove && val % 90) {
this.angle = Math.round(val / 90) * 90;
}
this.imgMarginDetectionScale();
},
cutAnimation(val) {
clearTimeout(this.cutAnimationTime);
if (val) {
let cutAnimationTime = setTimeout(() => {
this.cutAnimation = false;
}, 260);
this.cutAnimationTime = cutAnimationTime;
}
},
isLimitMove(val) {
if (val) {
if (this.angle % 90) {
this.angle = Math.round(this.angle / 90) * 90;
}
this.imgMarginDetectionScale();
}
},
cutPoint() {
this.cutDetectionPosition();
},
aWidth(width, oWidth) {
if (width !== oWidth) {
this.clipWidth = width / 2
}
},
aHeight(height, oHeight) {
if (height !== oHeight) {
this.clipHeight = height / 2
}
}
},
mounted() {
const sysinfo = uni.getSystemInfoSync();
this.sysinfo = sysinfo;
this.setCutInfo();
this.setCutCenter();
this.computeCutSize();
this.cutDetectionPosition();
},
methods: {
getImageInfo(url) {
if (!url) return;
uni.showLoading({
title: '请稍候...',
mask: true
});
uni.getImageInfo({
src: url,
success: res => {
this.imgComputeSize(res.width, res.height);
if (this.isLimitMove) {
this.imgMarginDetectionScale();
this.$emit('ready', res);
}
},
fail: () => {
this.imgComputeSize();
if (this.isLimitMove) {
this.imgMarginDetectionScale();
}
}
});
},
setCutInfo() {
const { aWidth, aHeight, sysinfo } = this;
const clipWidth = aWidth / 2;
const clipHeight = aHeight / 2;
const cutY = (sysinfo.windowHeight - clipHeight) / 2;
const cutX = (sysinfo.windowWidth - clipWidth) / 2;
const imageLeft = sysinfo.windowWidth / 2;
const imageTop = sysinfo.windowHeight / 2;
this.ctx = uni.createCanvasContext('l-clipper', this);
this.clipWidth = clipWidth;
this.clipHeight = clipHeight;
this.cutX = cutX;
this.cutY = cutY;
this.canvasHeight = clipHeight;
this.canvasWidth = clipWidth;
this.imageLeft = imageLeft;
this.imageTop = imageTop;
},
setCutCenter() {
const { sysInfo, clipHeight, clipWidth, imageTop, imageLeft } = this;
let sys = sysInfo || uni.getSystemInfoSync();
let cutY = (sys.windowHeight - clipHeight) * 0.5;
let cutX = (sys.windowWidth - clipWidth) * 0.5;
this.imageTop = imageTop - this.cutY + cutY;
this.imageLeft = imageLeft - this.cutX + cutX;
this.cutY = cutY;
this.cutX = cutX;
},
computeCutSize() {
const { clipHeight, clipWidth, sysinfo, cutX, cutY } = this;
if (clipWidth > sysinfo.windowWidth) {
this.clipWidth = sysinfo.windowWidth;
} else if (clipWidth + cutX > sysinfo.windowWidth) {
this.cutX = sysinfo.windowWidth - cutX;
}
if (clipHeight > sysinfo.windowHeight) {
this.clipHeight = sysinfo.windowHeight;
} else if (clipHeight + cutY > sysinfo.windowHeight) {
this.cutY = sysinfo.windowHeight - cutY;
}
},
cutDetectionPosition() {
const { cutX, cutY, sysinfo, clipHeight, clipWidth } = this;
let cutDetectionPositionTop = () => {
if (cutY < 0) {
this.cutY = 0;
}
if (cutY > sysinfo.windowHeight - clipHeight) {
this.cutY = sysinfo.windowHeight - clipHeight;
}
},
cutDetectionPositionLeft = () => {
if (cutX < 0) {
this.cutX = 0;
}
if (cutX > sysinfo.windowWidth - clipWidth) {
this.cutX = sysinfo.windowWidth - clipWidth;
}
};
if (cutY === null && cutX === null) {
let newCutY = (sysinfo.windowHeight - clipHeight) * 0.5;
let newCutX = (sysinfo.windowWidth - clipWidth) * 0.5;
this.cutX = newCutX;
this.cutY = newCutY;
} else if (cutY !== null && cutX !== null) {
cutDetectionPositionTop();
cutDetectionPositionLeft();
} else if (cutY !== null && cutX === null) {
cutDetectionPositionTop();
this.cutX = (sysinfo.windowWidth - clipWidth) / 2;
} else if (cutY === null && cutX !== null) {
cutDetectionPositionLeft();
this.cutY = (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.imageLeft = left;
this.imageTop = top;
this.scale = currentScale;
},
moveThrottle() {
if( this.moveThrottleFlag !== true) {
this.moveThrottleFlag = true
}
},
moveDuring() {
clearTimeout(this.timeCutcenter);
},
moveStop() {
clearTimeout(this.timeCutcenter);
const timeCutcenter = setTimeout(() => {
if (!this.cutAnimation) {
this.cutAnimation = true;
}
this.setCutCenter();
}, 800);
this.timeCutcenter = timeCutcenter;
},
clipTouchStart(event) {
// #ifdef H5
event.preventDefault()
// #endif
if (!this.image) {
uni.showToast({
title: '请选择图片',
icon: 'none'
});
return;
}
const currentX = event.touches[0].clientX;
const currentY = event.touches[0].clientY;
const { cutX, cutY, clipWidth, clipHeight } = this;
const corner = determineDirection(cutX, cutY, clipWidth, clipHeight, currentX, currentY);
this.moveDuring();
if(!corner) {
return
}
this.cutstart = {
width: clipWidth,
height: clipHeight,
x: currentX,
y: currentY,
cutY,
cutX,
corner
};
this.flagCutTouch = true;
this.flagEndTouch = true;
},
clipTouchMove(event) {
// #ifdef H5
event.stopPropagation()
event.preventDefault()
// #endif
if (!this.image) {
uni.showToast({
title: '请选择图片',
icon: 'none'
});
return;
}
const { flagCutTouch, moveThrottleFlag } = this;
if (flagCutTouch && moveThrottleFlag) {
const { isLockRatio, isLockHeight, isLockWidth } = this;
if (isLockRatio && (isLockWidth || isLockHeight)) return;
this.moveThrottleFlag = false;
if(this.moveThrottleFlag !== false) {
this.moveThrottleFlag = false;
}
this.moveThrottle();
const { width, height, cutX, cutY } = clipTouchMoveOfCalculate(this, event);
if (!isLockWidth && !isLockHeight) {
this.clipWidth = width;
this.clipHeight = height;
this.cutX = cutX;
this.cutY = cutY;
} else if (!isLockWidth) {
this.clipWidth = width// clipWidth;
this.cutX = cutX;
} else if (!isLockHeight) {
this.clipHeight = height//clipHeight;
this.cutY = cutY;
}
this.imgMarginDetectionScale();
}
},
clipTouchEnd() {
this.moveStop();
this.flagCutTouch = false;
},
imageTouchStart(e) {
// #ifdef H5
event.preventDefault()
// #endif
this.flagEndTouch = false;
this.cutAnimation = false;
const { imageLeft, imageTop } = this;
const clientXForLeft = Math.round(e.touches[0].clientX);
const clientYForLeft = Math.round(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 = Math.round(e.touches[1].clientX);
const clientYForRight = Math.round(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
event.preventDefault()
// #endif
const { flagEndTouch, moveThrottleFlag } = this;
if (flagEndTouch || !moveThrottleFlag) return;
const clientXForLeft = Math.round(e.touches[0].clientX);
const clientYForLeft = Math.round(e.touches[0].clientY);
this.moveThrottleFlag = false;
this.moveThrottle();
this.moveDuring();
if (e.touches.length === 1) {
const { left, top } = imageTouchMoveOfCalcOffset(this, clientXForLeft, clientYForLeft);
this.imageLeft = left;
this.imageTop = top;
this.imgMarginDetectionPosition();
} else {
const clientXForRight = Math.round(e.touches[1].clientX);
const clientYForRight = Math.round(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.flagEndTouch = true;
this.moveStop();
},
uploadImage() {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: res => {
const tempFilePaths = res.tempFilePaths;
this.image = tempFilePaths[0];
}
});
},
imageReset() {
const sys = this.sysinfo || uni.getSystemInfoSync();
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 { clipHeight, clipWidth, ctx, scale, imageLeft, imageTop, cutX, cutY, angle, scaleRatio, image, quality, type: imageType } = this;
let { canvasHeight, canvasWidth } = this;
const draw = () => {
const imageWidth = this.imageWidth * scale * scaleRatio;
const imageHeight = this.imageHeight * scale * scaleRatio;
const xpos = imageLeft - cutX;
const ypos = imageTop - cutY;
ctx.translate(xpos * scaleRatio, ypos * scaleRatio);
ctx.rotate((angle * Math.PI) / 180);
ctx.drawImage(image, -imageWidth / 2, -imageHeight / 2, imageWidth, imageHeight);
ctx.draw(false, () => {
const width = clipWidth * scaleRatio
const height = clipHeight * scaleRatio
let params = {
x: 0,
y: 0,
width,
height,
destWidth: width,
destHeight: height,
canvasId: 'l-clipper',
fileType: 'png',
quality,
success: (res) => {
data.url = res.tempFilePath;
uni.hideLoading();
this.$emit('success', data);
},
fail: (error) => {
console.error('error', error)
this.$emit('fail', error);
}
};
let data = {
url: '',
width,
height
};
uni.canvasToTempFilePath(params, this)
});
};
if (canvasWidth !== clipWidth || canvasHeight !== clipHeight) {
canvasWidth = clipWidth;
canvasHeight = clipHeight;
ctx.draw();
setTimeout(() => {
draw();
}, 100);
} else {
draw();
}
},
cancel() {
this.$emit('cancel', false)
},
}
};
</script>
<style lang="stylus" scoped>
@import './index'
</style>
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/ruoshuisixue/lime-cllipper.git
[email protected]:ruoshuisixue/lime-cllipper.git
ruoshuisixue
lime-cllipper
lime-cllipper
master

搜索帮助