|
|
@@ -0,0 +1,444 @@
|
|
|
+<template>
|
|
|
+ <view class="ct" style='position: fixed;left: 0; top: 0;z-index:999;'>
|
|
|
+ <view :id="id" style="height: 100%;display: flex;flex-flow: column nowrap;" @touchstart="touchstart" @touchmove.stop.prevent="touchmove" @touchend="touchend">
|
|
|
+ <view class="ct cbt"></view>
|
|
|
+ <view class="bc tb"> 单指滑动 双指缩放 </view>
|
|
|
+ <view class="cs">
|
|
|
+ <view class="bc l"></view>
|
|
|
+ <view class="cvbox" :style="boxStyle">
|
|
|
+ <view :id="id + '_cmd'" style="display: none;" :draw="drawInfo" :change:draw="clipjs.doDraw"></view>
|
|
|
+ <view class="imgbox" :style="imageBoxStyle" >
|
|
|
+ <image :src='image_info.path' style="width: 100%;height: 100%;" ></image>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="bc r"></view>
|
|
|
+ </view>
|
|
|
+ <view class="bc b"></view>
|
|
|
+ </view>
|
|
|
+ <view style="width: 100%;line-height: 40rpx;display: flex;flex-flow: row nowrap;align-items: center;justify-content: space-between;background-color: rgba(0, 0, 0, 0.5);">
|
|
|
+ <view @click="doCancel" >
|
|
|
+ <slot name='cancel'>
|
|
|
+ <view style="color: #fff; margin: 30rpx;">取消</view>
|
|
|
+ </slot>
|
|
|
+ </view>
|
|
|
+ <view @click="doRotate" >
|
|
|
+ <slot name='rotate'>
|
|
|
+ <view style="color: #fff; margin: 30rpx;">旋转</view>
|
|
|
+ </slot>
|
|
|
+ </view>
|
|
|
+ <view @click="ok" >
|
|
|
+ <slot name='ok'>
|
|
|
+ <view style="color: #fff; margin: 30rpx;">确定</view>
|
|
|
+ </slot>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+<script module="clipjs" lang="renderjs">
|
|
|
+export default{
|
|
|
+ methods:{
|
|
|
+ doDraw(nv,ov,rinstance,instance){
|
|
|
+ let {info,scale,imgSize,position,osize,bscale,rotate}=nv;
|
|
|
+ let imageDom = new Image();
|
|
|
+ imageDom.crossOrigin = '';
|
|
|
+ imageDom.onload=()=>{
|
|
|
+ let {width,height} = nv.osize;
|
|
|
+ let canvas = document.createElement('canvas');
|
|
|
+ canvas.width = width;
|
|
|
+ canvas.height = height;
|
|
|
+ let ctx = canvas.getContext('2d');
|
|
|
+ ctx.save();
|
|
|
+ ctx.scale(bscale,bscale);
|
|
|
+ ctx.rotate(rotate*Math.PI/180);
|
|
|
+ switch(rotate){
|
|
|
+ case 0 :
|
|
|
+ ctx.drawImage(imageDom,0,0,info.width,info.height,position.x,position.y,imgSize.width,imgSize.height);
|
|
|
+ break;
|
|
|
+ case 90:
|
|
|
+ ctx.drawImage(imageDom,0,0,info.width,info.height,position.y,-position.x,imgSize.width,imgSize.height);
|
|
|
+ break;
|
|
|
+ case 180:
|
|
|
+ ctx.drawImage(imageDom,0,0,info.width,info.height,-position.x,-position.y,imgSize.width,imgSize.height);
|
|
|
+ break;
|
|
|
+ case 270:
|
|
|
+ ctx.drawImage(imageDom,0,0,info.width,info.height,-position.y,position.x,imgSize.width,imgSize.height);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ ctx.restore()
|
|
|
+ let base64 = '';
|
|
|
+ try{
|
|
|
+ base64 = canvas.toDataURL();
|
|
|
+ }catch(e){
|
|
|
+ console.log(e);
|
|
|
+ }
|
|
|
+ rinstance.callMethod('updateImage',base64);
|
|
|
+ }
|
|
|
+ imageDom.src = info.path;
|
|
|
+ },
|
|
|
+ },
|
|
|
+}
|
|
|
+</script>
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ //注意: 当事件 ok | cancel 发生时,本组件应该销毁,此组件销毁请使用 v-if ,使用v-show会出问题,因为v-show只是隐藏了本组件,并未销毁
|
|
|
+ name: 'reaee-image-clip',
|
|
|
+ props: {
|
|
|
+ //图片文件路径或url,
|
|
|
+ src: {
|
|
|
+ type: String,
|
|
|
+ required: true
|
|
|
+ },
|
|
|
+ //想要裁剪的规格大小,单位px,裁剪后输出图片的长宽就在这里设置
|
|
|
+ size: {
|
|
|
+ type: Object,
|
|
|
+ default: () => {
|
|
|
+ return {
|
|
|
+ width: 200,
|
|
|
+ height: 200
|
|
|
+ };
|
|
|
+ }
|
|
|
+ },
|
|
|
+ //裁剪框在屏幕上的占比,会根据 此值 & 裁剪规格 & 手机屏幕规格 进行适配
|
|
|
+ rectScale:{
|
|
|
+ type:Number,
|
|
|
+ default:0.6
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ //是否已挂载
|
|
|
+ mountedStatus: false,
|
|
|
+ //图片真实数据
|
|
|
+ image_info: {},
|
|
|
+ //图片定位
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ //移动偏移量
|
|
|
+ tTop: 0,
|
|
|
+ tLeft: 0,
|
|
|
+ //图片放大比例
|
|
|
+ scale: 1,
|
|
|
+ tScale:1,
|
|
|
+ imgScale:1,
|
|
|
+ boxScsle: 0,
|
|
|
+ rotate:0,
|
|
|
+ id: "reaee_image_clip",
|
|
|
+ toucheStartValue: {},
|
|
|
+ drawInfo: {}
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ imageBoxStyle() {
|
|
|
+ let s = {};
|
|
|
+ s.top=this.cTop + 'px';
|
|
|
+ s.left=this.cLeft + 'px';
|
|
|
+ s.width =this.cWidth + 'px';
|
|
|
+ s.height=this.cHeight + 'px';
|
|
|
+ s.transform = `rotate(${this.rotate}deg)`;
|
|
|
+ s.transformOrigin = `0 0`;
|
|
|
+ return s ;
|
|
|
+ },
|
|
|
+ boxStyle() {
|
|
|
+ return {
|
|
|
+ width: this.bWidth + 'px',
|
|
|
+ height: this.bHeight + 'px'
|
|
|
+ };
|
|
|
+ },
|
|
|
+ cTop() {
|
|
|
+ return this.top + this.tTop;
|
|
|
+ },
|
|
|
+ cLeft() {
|
|
|
+ return this.left + this.tLeft;
|
|
|
+ },
|
|
|
+ cWidth() {
|
|
|
+ return this.image_info.width / (this.scale*this.tScale);
|
|
|
+ },
|
|
|
+ cHeight() {
|
|
|
+ return this.image_info.height / (this.scale*this.tScale);
|
|
|
+ },
|
|
|
+ bWidth() {
|
|
|
+ return this.size.width / this.boxScsle;
|
|
|
+ },
|
|
|
+ bHeight() {
|
|
|
+ return this.size.height / this.boxScsle;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ image_info:{
|
|
|
+ handler(){
|
|
|
+ this.$nextTick(this.autoBoxScale);
|
|
|
+ },
|
|
|
+ deep:true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ initValue(){
|
|
|
+ uni.getImageInfo({
|
|
|
+ src: this.src
|
|
|
+ }).then(res => {
|
|
|
+ this.image_info = res[1];
|
|
|
+ }).catch(e=>{
|
|
|
+ this.doCancel();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ doCancel(){
|
|
|
+ this.$emit('cancel');
|
|
|
+ },
|
|
|
+ updateImage(e) {
|
|
|
+ this.$emit('ok', e);
|
|
|
+ },
|
|
|
+ doRotate(){
|
|
|
+ let rotate = this.rotate;
|
|
|
+ rotate += 90;
|
|
|
+ rotate %= 360;
|
|
|
+ this.$set(this,'rotate',rotate);
|
|
|
+ this.$nextTick(this.autoBoxScale)
|
|
|
+ },
|
|
|
+ autoBoxScale() {
|
|
|
+ if (!this.mountedStatus) return;
|
|
|
+ let { width, height } = this.size;
|
|
|
+ let id = '#' + this.id;
|
|
|
+ let select = uni.createSelectorQuery(this).in(this).select(id);
|
|
|
+ select.boundingClientRect(data => {
|
|
|
+ let sw = width / (data.width)/this.rectScale;
|
|
|
+ let sh = height / (data.height)/this.rectScale;
|
|
|
+ this.boxScsle = sw > sh ? sw : sh;
|
|
|
+ this.$nextTick(this.autoScale)
|
|
|
+ }).exec();
|
|
|
+ if(this.boxScsle === 0){
|
|
|
+ setTimeout(this.autoBoxScale,100);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ autoScale() {
|
|
|
+ if (!this.mountedStatus) return;
|
|
|
+ if(this.rotate == 90 || this.rotate == 270){
|
|
|
+ let { width, height } = this.image_info;
|
|
|
+ let sw = height / this.bWidth;
|
|
|
+ let sh = width / this.bHeight;
|
|
|
+ this.scale = sw < sh ? sw : sh;
|
|
|
+ this.imgScale = this.scale;
|
|
|
+ }else{
|
|
|
+ let { width, height } = this.image_info;
|
|
|
+ let sw = width / this.bWidth;
|
|
|
+ let sh = height / this.bHeight;
|
|
|
+ this.scale = sw < sh ? sw : sh;
|
|
|
+ this.imgScale = this.scale;
|
|
|
+ }
|
|
|
+ this.$nextTick(this.correctionLocationFinish)
|
|
|
+ },
|
|
|
+ ok() {
|
|
|
+ let info = this.image_info;
|
|
|
+ let osize = this.size;
|
|
|
+ let scale = (this.scale*this.tScale);
|
|
|
+ let bscale = this.boxScsle;
|
|
|
+ let imgSize={width:this.cWidth,height:this.cHeight}
|
|
|
+ let position={x:this.cLeft,y:this.cTop}
|
|
|
+ let rotate = this.rotate;
|
|
|
+ let clipInfo = { info, osize, scale,bscale,imgSize,position,rotate };
|
|
|
+ this.drawInfo = clipInfo;
|
|
|
+ },
|
|
|
+ touchstart(e) {
|
|
|
+ e.time=new Date().getTime();
|
|
|
+ this.toucheStartValue = e;
|
|
|
+ },
|
|
|
+ distanceF(p1, p2) {
|
|
|
+ let dx = Math.abs(p1.clientX - p2.clientX);
|
|
|
+ let dy = Math.abs(p1.clientY - p2.clientY);
|
|
|
+ return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
|
|
|
+ },
|
|
|
+ toucheScale(e){
|
|
|
+ let nowTouches = e.touches;
|
|
|
+ let nowLong = this.distanceF(nowTouches[0],nowTouches[1]);
|
|
|
+ let startTouches = this.toucheStartValue.touches;
|
|
|
+ let startLong = this.distanceF(startTouches[0],startTouches[1]);
|
|
|
+ let tScale = startLong/nowLong;
|
|
|
+ this.tScale = tScale;
|
|
|
+ this.tTop = 1;
|
|
|
+ this.tLeft = 1;
|
|
|
+ },
|
|
|
+ correctionLocationFinish(){
|
|
|
+ this.correctionLocation();
|
|
|
+ this.top += this.tTop;
|
|
|
+ this.left += this.tLeft;
|
|
|
+ this.scale *= this.tScale;
|
|
|
+ this.tScale = 1;
|
|
|
+ this.tTop = 0;
|
|
|
+ this.tLeft = 0;
|
|
|
+ },
|
|
|
+ correctionLocation(){
|
|
|
+ //当前初始X
|
|
|
+ let left = this.left;
|
|
|
+ //当前初始Y
|
|
|
+ let top = this.top;
|
|
|
+ //X轴移动量
|
|
|
+ let tLeft = this.tLeft;
|
|
|
+ //Y轴移动量
|
|
|
+ let tTop = this.tTop;
|
|
|
+ //当前角度
|
|
|
+ let rotate = this.rotate;
|
|
|
+ //当前选框宽度
|
|
|
+ let bWidth = this.bWidth;
|
|
|
+ //当前选框高度
|
|
|
+ let bHeight = this.bHeight;
|
|
|
+ //当前图片宽度
|
|
|
+ let iWidth = this.cWidth;
|
|
|
+ //当前图片高度
|
|
|
+ let iHeight = this.cHeight;
|
|
|
+
|
|
|
+ if(this.imgScale < this.scale * this.tScale){
|
|
|
+ this.tScale = this.imgScale/this.scale;
|
|
|
+ }
|
|
|
+ //如果旋转
|
|
|
+ switch(rotate){
|
|
|
+ case 90:
|
|
|
+ if(left + tLeft - iHeight > 0){
|
|
|
+ this.tLeft = iHeight - left ;
|
|
|
+ }
|
|
|
+ if(left + tLeft < bWidth ){
|
|
|
+ this.tLeft = bWidth - left;
|
|
|
+ }
|
|
|
+ if(top + tTop + iWidth < bHeight){
|
|
|
+ this.tTop = bHeight - top - iWidth ;
|
|
|
+ }
|
|
|
+ if(top + tTop > 0 ){
|
|
|
+ this.tTop = - top;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 180:
|
|
|
+ if(left+tLeft > iWidth){
|
|
|
+ this.tLeft = iWidth - left;
|
|
|
+ }
|
|
|
+ if(left+tLeft < bWidth){
|
|
|
+ this.tLeft = bWidth-left
|
|
|
+ }
|
|
|
+ if(top + tTop > iHeight){
|
|
|
+ this.tTop = iHeight - top;
|
|
|
+ }
|
|
|
+ if(top + tTop < bHeight){
|
|
|
+ this.tTop = bHeight - top;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 270:
|
|
|
+ if(left + tLeft > 0 ){
|
|
|
+ this.tLeft = - left;
|
|
|
+ }
|
|
|
+ if(left + tLeft + iHeight < bWidth ){
|
|
|
+ this.tLeft = bWidth - left - iHeight;
|
|
|
+ }
|
|
|
+ if(top + tTop < bHeight){
|
|
|
+ this.tTop = bHeight - top;
|
|
|
+ }
|
|
|
+ if(top + tTop - iWidth > 0 ){
|
|
|
+ this.tTop = iWidth- top;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ if(left + tLeft + iWidth < bWidth){
|
|
|
+ this.tLeft = bWidth - left - iWidth ;
|
|
|
+ }
|
|
|
+ if(left + tLeft > 0 ){
|
|
|
+ this.tLeft = - left;
|
|
|
+ }
|
|
|
+ if(top + tTop + iHeight < bHeight){
|
|
|
+ this.tTop = bHeight - top - iHeight ;
|
|
|
+ }
|
|
|
+ if(top + tTop > 0 ){
|
|
|
+ this.tTop = - top;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ toucheM(e){
|
|
|
+ //当前鼠标位置
|
|
|
+ let { clientX, clientY } = e.changedTouches[0];
|
|
|
+ //起始鼠标位置
|
|
|
+ let { clientX: clientXs, clientY: clientYs } = this.toucheStartValue.changedTouches[0];
|
|
|
+ //X轴移动量
|
|
|
+ this.tLeft = clientX - clientXs;
|
|
|
+ //Y轴移动量
|
|
|
+ this.tTop = clientY - clientYs;
|
|
|
+ },
|
|
|
+ touchmove(e) {
|
|
|
+ let time = new Date().getTime();
|
|
|
+ let toucheLen = e.touches.length;
|
|
|
+ if(toucheLen>1){
|
|
|
+ this.toucheScale(e);
|
|
|
+ this.correctionLocation();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if(toucheLen == 1){
|
|
|
+ this.toucheM(e);
|
|
|
+ this.correctionLocation();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ touchend(e) {
|
|
|
+ this.correctionLocationFinish();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.mountedStatus = true;
|
|
|
+ this.initValue()
|
|
|
+ },
|
|
|
+ destroyed(){
|
|
|
+ this.mountedStatus = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style>
|
|
|
+.imgbox {
|
|
|
+ width: 80vw;
|
|
|
+ height: 80vw;
|
|
|
+ top: -10vw;
|
|
|
+ left: -10vw;
|
|
|
+ z-index: -1;
|
|
|
+ position: relative;
|
|
|
+ /* background: #007AFF; */
|
|
|
+}
|
|
|
+.cvbox {
|
|
|
+ background-color: rgba(255, 255, 255, 0);
|
|
|
+ width: 50vw;
|
|
|
+ height: 50vw;
|
|
|
+ border: 1px dashed #fff;
|
|
|
+}
|
|
|
+.bc {
|
|
|
+ background-color: rgba(0, 0, 0, 0.5);
|
|
|
+ color: rgba(255,255,255,0.3);
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ flex: 1 100 auto;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.ctrl {
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ /* background-color: #4CD964; */
|
|
|
+ position: fixed;
|
|
|
+}
|
|
|
+.ab {
|
|
|
+ position: absolute;
|
|
|
+ /* background-color: #2c405a; */
|
|
|
+}
|
|
|
+.cs {
|
|
|
+ flex: none;
|
|
|
+ display: flex;
|
|
|
+ flex-flow: row nowrap;
|
|
|
+ /* border: 1px solid #4CD964; */
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.ct {
|
|
|
+ width: 100vw;
|
|
|
+ height: 100vh;
|
|
|
+ display: flex;
|
|
|
+ flex-flow: column nowrap;
|
|
|
+}
|
|
|
+.cbt {
|
|
|
+ position: absolute;
|
|
|
+ background-color: #000;
|
|
|
+ z-index: -5;
|
|
|
+}
|
|
|
+</style>
|