| <template> | 
|     <view class="bt-container" :style="[containerStyle]"> | 
|         <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | 
|         <view class="mainContent" data-type="image" @touchstart="wxsModule.touchStart" @touchmove="wxsModule.touchMove" @touchend="wxsModule.touchEnd"> | 
|         <!-- #endif --> | 
|         <!-- #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | 
|         <view class="mainContent" data-type="image" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd"> | 
|         <!-- #endif --> | 
|             <template v-if="imageRect && cropperRect"> | 
|                 <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | 
|                 <image mode="aspectFit" :src="imageSrc" class="image" :rotateAngle="rotateAngle" :change:rotateAngle="wxsModule.changeRotateAngle" :change:imageRect="wxsModule.changeImageRect" :imageRect="imageRect" :class="{ anim }"> | 
|                 <!-- #endif --> | 
|                 <!-- #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | 
|                 <image mode="aspectFit" :src="imageSrc" class="image" :style="[imageStyle]" :class="{ anim }"> | 
|                 <!-- #endif --> | 
|                 </image> | 
|                 <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | 
|                 <view class="cropper" :class="{ anim }" :change:cropperRect="wxsModule.changeCropper" :cropperRect="cropperRect" :change:ratio="wxsModule.changeRatio" :ratio="ratio" > | 
|                 <!-- #endif --> | 
|                 <!-- #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | 
|                 <view class="cropper" :class="{ anim }"  :style="[cropperStyle]"> | 
|                 <!-- #endif --> | 
|                     <image class="mask" :src="mask"></image> | 
|                     <template v-if="showGrid"> | 
|                         <view class="line row row1"></view> | 
|                         <view class="line row row2"></view> | 
|                         <view class="line col col1"></view> | 
|                         <view class="line col col2"></view> | 
|                     </template> | 
|                     <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5  --> | 
|                     <view class="controller vertical left" @touchstart="wxsModule.touchStart" data-type="controller" /> | 
|                     <view class="controller vertical right" @touchstart="wxsModule.touchStart" data-type="controller" /> | 
|                     <view class="controller horizon top" @touchstart="wxsModule.touchStart" data-type="controller" /> | 
|                     <view class="controller horizon bottom" @touchstart="wxsModule.touchStart" data-type="controller" /> | 
|                     <view class="controller left top" @touchstart="wxsModule.touchStart" data-type="controller" /> | 
|                     <view class="controller left bottom" @touchstart="wxsModule.touchStart" data-type="controller" /> | 
|                     <view class="controller right top" @touchstart="wxsModule.touchStart" data-type="controller" /> | 
|                     <view class="controller right bottom" @touchstart="wxsModule.touchStart" data-type="controller" /> | 
|                     <!-- #endif --> | 
|                     <!-- #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5  --> | 
|                     <view class="controller vertical left" @touchstart.stop="touchStart(-1, 0, $event)" /> | 
|                     <view class="controller vertical right" @touchstart.stop="touchStart(1, 0, $event)" /> | 
|                     <view class="controller horizon top" @touchstart.stop="touchStart(0, -1, $event)" /> | 
|                     <view class="controller horizon bottom" @touchstart.stop="touchStart(0, 1, $event)" /> | 
|                     <view class="controller left top" @touchstart.stop="touchStart(-1, -1, $event)" /> | 
|                     <view class="controller left bottom" @touchstart.stop="touchStart(-1, 1, $event)" /> | 
|                     <view class="controller right top" @touchstart.stop="touchStart(1, -1, $event)" /> | 
|                     <view class="controller right bottom" @touchstart.stop="touchStart(1, 1, $event)" /> | 
|                     <!-- #endif --> | 
|                 </view> | 
|             </template> | 
|         </view> | 
|             <canvas v-if="type2d" type="2d" class="bt-canvas" :width="target.width" :height="target.height"></canvas> | 
|             <canvas | 
|                 v-else | 
|                 :canvas-id="canvasId" | 
|                 class="bt-canvas" | 
|                 :style="{ | 
|                     width: target.width + 'px', | 
|                     height: target.height + 'px' | 
|                 }" | 
|                 :width="target.width * pixel" | 
|                 :height="target.height * pixel" | 
|             ></canvas> | 
|     </view> | 
| </template> | 
|   | 
| <script> | 
| // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 | 
| import touches from './js/touchs.js'; | 
| // #endif | 
| import { parseUnit,sleep } from './utils/tools.js'; | 
| /** | 
|  * better-cropper 图片裁切插件 | 
|  */ | 
| export default { | 
|     name: 'bt-cropper', | 
|     props: { | 
|         // 图片路径,支持网络路径和本地路径 | 
|         imageSrc: { | 
|             type: String, | 
|             default: '', | 
|             required: true | 
|         }, | 
|         mask: { | 
|             type: String, | 
|             default: '' | 
|         }, | 
|         // 手动指定容器的大小 | 
|         containerSize: { | 
|             type: Object, | 
|             default: null | 
|         }, | 
|         // 输出图片的格式,默认jpg | 
|         fileType: { | 
|             type: String, | 
|             default: 'png' | 
|         }, | 
|         // 生成的图片的宽度,不传或者传0表示按照原始分辨率裁切 | 
|         dWidth: Number, | 
|         maxWidth: { | 
|             type: Number, | 
|             default: 2000 | 
|         }, | 
|         // 裁切比例,0表示自由 | 
|         ratio: { | 
|             type: Number, | 
|             default: 0, | 
|             validator(value) { | 
|                 if (typeof value === 'number') { | 
|                     if (value < 0) { | 
|                         return false; | 
|                     } | 
|                 } else { | 
|                     return false; | 
|                 } | 
|                 return true; | 
|             } | 
|         }, | 
|         // 旋转角度 | 
|         rotate: Number, | 
|         // 是否展示网格 | 
|         showGrid: { | 
|             type: Boolean, | 
|             default: false | 
|         }, | 
|         // 图片质量,0-1 越大质量越好 | 
|         quality: { | 
|             type: Number, | 
|             default: 1 | 
|         }, | 
|         canvas2d: { | 
|             type: Boolean, | 
|             default: false | 
|         }, | 
|         // 初始的图片位置 | 
|         initPosition: { | 
|             type: Object, | 
|             default() { | 
|                 return null; | 
|             } | 
|         }, | 
|         // 是否开启操作结束后自动放大 | 
|         autoZoom: { | 
|             type: Boolean, | 
|             default: true | 
|         } | 
|     }, | 
|     // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 | 
|     mixins: [touches], | 
|     // #endif | 
|     data() { | 
|         return { | 
|             canvasId: 'bt-cropper', | 
|             containerRect: null, | 
|             imageInfo: null, | 
|             operationHistory: [], | 
|             operationIndex: 0, | 
|             anim: false, | 
|             timer: null, | 
|             // 是否使用2D canvas | 
|             type2d: false, | 
|             pixel: 1, | 
|             imageRect: null, | 
|             cropperRect: null, | 
|             target:{ | 
|                 width:0, | 
|                 height:0 | 
|             } | 
|         }; | 
|     }, | 
|     watch: { | 
|         imageSrc: { | 
|             handler(src) { | 
|                 if (typeof src === 'string' && src !== '') { | 
|                     this.imageInit(src); | 
|                 } else { | 
|                     this.imageInfo = null; | 
|                 } | 
|             }, | 
|             immediate: true | 
|         }, | 
|         ratio() { | 
|             if (this.ratio != 0) { | 
|                 this.startAnim(); | 
|                 this.init(); | 
|             } | 
|         } | 
|     }, | 
|     computed: { | 
|         containerStyle() { | 
|             if (this.containerSize && this.containerRect) { | 
|                 return { | 
|                     width: this.containerRect.width + 'px', | 
|                     height: this.containerRect.height + 'px' | 
|                 }; | 
|             } | 
|             return {}; | 
|         }, | 
|         rotateAngle(){ | 
|             const angel = Number(this.rotate) | 
|             if(isNaN(angel)){ | 
|                 return 0 | 
|             }else{ | 
|                 return angel % 360 | 
|             } | 
|         } | 
|     }, | 
|     methods: { | 
|         startAnim() { | 
|             this.stopAnim(); | 
|             this.anim = true; | 
|             this.timer = setTimeout(() => { | 
|                 this.anim = false; | 
|             }, 200); | 
|         }, | 
|         stopAnim() { | 
|             this.anim = false; | 
|             clearTimeout(this.timer); | 
|         }, | 
|         imageInit(src) { | 
|             uni.showLoading({ | 
|                 title: '载入中...' | 
|             }); | 
|             uni.getImageInfo({ | 
|                 src, | 
|                 success: res => { | 
|                     this.imageInfo = res; | 
|                     this.$nextTick(() => { | 
|                         this.getContainer().then(rect => { | 
|                             this.containerRect = rect; | 
|                             this.init(); | 
|                         }); | 
|                     }); | 
|                 }, | 
|                 fail: (err) => { | 
|                     this.$emit('loadFail',err); | 
|                     uni.showToast({ | 
|                         title: '图片下载失败!', | 
|                         icon: 'none' | 
|                     }); | 
|                 }, | 
|                 complete(res) { | 
|                     uni.hideLoading(); | 
|                 } | 
|             }); | 
|         }, | 
|         initCropper() { | 
|             const imageRate = this.imageInfo.width / this.imageInfo.height; | 
|             const containerRate = this.containerRect.width / this.containerRect.height; | 
|             const imageRect = {}; | 
|             let cropperRate = this.ratio; | 
|             if (cropperRate == 0) { | 
|                 if (this.cropperRect) { | 
|                     cropperRate = this.cropperRect.width / this.cropperRect.height; | 
|                 } else { | 
|                     cropperRate = 1; | 
|                 } | 
|             } | 
|             const cropperRect = {}; | 
|             if (containerRate > cropperRate) { | 
|                 cropperRect.height = this.containerRect.height * 0.85; | 
|                 cropperRect.width = cropperRect.height * cropperRate; | 
|             } else { | 
|                 cropperRect.width = this.containerRect.width * 0.85; | 
|                 cropperRect.height = cropperRect.width / cropperRate; | 
|             } | 
|             if (cropperRate > imageRate) { | 
|                 imageRect.width = cropperRect.width; | 
|                 imageRect.height = imageRect.width / imageRate; | 
|             } else { | 
|                 imageRect.height = cropperRect.height; | 
|                 imageRect.width = imageRect.height * imageRate; | 
|             } | 
|             imageRect.left = (this.containerRect.width - imageRect.width) / 2; | 
|             imageRect.top = (this.containerRect.height - imageRect.height) / 2; | 
|             cropperRect.left = imageRect.left + (imageRect.width - cropperRect.width) / 2; | 
|             cropperRect.top = imageRect.top + (imageRect.height - cropperRect.height) / 2; | 
|             return { | 
|                 imageRect, | 
|                 cropperRect | 
|             }; | 
|         }, | 
|         init() { | 
|             const {imageRect,cropperRect} = this.initCropper() | 
|             if(this.initPosition){ | 
|                 const scale = this.imageInfo.width/imageRect.width; | 
|                 const {left,top,width,height} = this.initPosition; | 
|                 if(left!==undefined&&top!==undefined&&width!==undefined&&height!==undefined){ | 
|                     cropperRect.width = width/scale; | 
|                     cropperRect.height = height/scale; | 
|                     cropperRect.left = left/scale; | 
|                     cropperRect.top = top/scale; | 
|                     this.$nextTick(this.zoomToFill); | 
|                 } | 
|             } | 
|             this.imageRect = imageRect | 
|             this.cropperRect = cropperRect | 
|             this.operationHistory = [{imageRect,cropperRect}]; | 
|             this.operationIndex = 0; | 
|             this.setTarget(); | 
|             // #ifdef MP-WEIXIN | 
|             const systemInfo = uni.getSystemInfoSync(); | 
|             if (this.canvas2d === false || systemInfo.platform === 'windows' || systemInfo.platform === 'mac') { | 
|                 this.type2d = false; | 
|             } else { | 
|                 this.type2d = true; | 
|                 this.pixel = systemInfo.pixelRatio; | 
|             } | 
|             // #endif | 
|             //非微信小程序端强制关闭canvas2d模式 | 
|             // #ifndef MP-WEIXIN | 
|             this.type2d = false; | 
|             // #endif | 
|             // #ifdef  MP-TOUTIAO || MP-LARK || MP-ALIPAY | 
|             this.type2d = this.canvas2d; | 
|             // #endif | 
|         }, | 
|         // 设置目标图像的大小 | 
|         setTarget(){ | 
|             const ratio = this.cropperRect.width / this.cropperRect.height; | 
|             if (!!this.dWidth) { | 
|                 this.target = { | 
|                     width: this.dWidth, | 
|                     height: this.dWidth / (ratio || 1) | 
|                 } | 
|             } else { | 
|                 const width = Math.min(this.maxWidth, this.cropperRect.width * (this.imageInfo.width / this.imageRect.width)); | 
|                 this.target = { | 
|                     width, | 
|                     height: width / (ratio || 1) | 
|                 } | 
|             } | 
|         }, | 
|         addHistory({imageRect,cropperRect}){ | 
|             if(this.operationIndex!==this.operationHistory.length-1){ | 
|                 this.operationHistory = this.operationHistory.slice(0,this.operationIndex) | 
|             } | 
|             this.operationHistory.push({ | 
|                 imageRect, | 
|                 cropperRect | 
|             }); | 
|             if (this.operationHistory.length > 10) { | 
|                 this.operationHistory.shift(); | 
|             } | 
|             this.operationIndex = this.operationHistory.length - 1; | 
|         }, | 
|         updateData(data) { | 
|             this.imageRect = data.imageRect | 
|             this.cropperRect = data.cropperRect | 
|             this.addHistory(data); | 
|             this.setTarget(); | 
|             if (this.autoZoom) { | 
|                 this.timer = setTimeout(() => { | 
|                     this.zoomToFill(); | 
|                 }, 600); | 
|             } | 
|             const {imageRect,cropperRect} = data | 
|             const scale = imageRect.width/this.imageInfo.width | 
|             this.$emit('change', { | 
|                 left: (cropperRect.left - imageRect.left) /scale, | 
|                 top: (cropperRect.top - imageRect.top) / scale, | 
|                 width: cropperRect.width / scale, | 
|                 height: cropperRect.height / scale | 
|             }); | 
|         }, | 
|         getContainer() { | 
|             if (this.containerSize !== null && typeof this.containerSize == 'object') { | 
|                 const { width, height } = this.containerSize; | 
|                 return Promise.resolve({ | 
|                     width: parseUnit(width), | 
|                     height: parseUnit(height) | 
|                 }); | 
|             } else { | 
|                 return new Promise(resolve => { | 
|                     const query = uni.createSelectorQuery().in(this); | 
|                     query | 
|                         .select('.mainContent') | 
|                         .boundingClientRect(rect => { | 
|                             resolve(rect); | 
|                         }) | 
|                         .exec(); | 
|                 }); | 
|             } | 
|         }, | 
|         zoomToFill() { | 
|             this.startAnim(); | 
|             const beforeCropper = { | 
|                 ...this.cropperRect | 
|             }; | 
|             const operation = { | 
|                 imageRect:this.imageRect, | 
|                 cropperRect:this.cropperRect | 
|             }; | 
|             this.cropperRect = this.initCropper().cropperRect; | 
|             const scale = this.cropperRect.width / beforeCropper.width; | 
|             const ox = beforeCropper.left - this.imageRect.left; | 
|             const oy = beforeCropper.top - this.imageRect.top; | 
|             this.imageRect = { | 
|                 width: this.imageRect.width * scale, | 
|                 height: this.imageRect.height * scale, | 
|                 left: this.imageRect.left + (this.cropperRect.left - beforeCropper.left) - (scale - 1) * ox, | 
|                 top: this.imageRect.top + (this.cropperRect.top - beforeCropper.top) - (scale - 1) * oy | 
|             }; | 
|         }, | 
|         onTouchStart() { | 
|             this.stopAnim(); | 
|         }, | 
|         // 撤销 | 
|         undo() { | 
|             if (this.operationIndex > 0) { | 
|                 this.operationIndex--; | 
|                 this.imageRect = this.operationHistory[this.operationIndex].imageRect; | 
|                 this.cropperRect = this.operationHistory[this.operationIndex].cropperRect; | 
|                 return true; | 
|             } | 
|             return false; | 
|         }, | 
|         // 重做 | 
|         resume() { | 
|             if (this.operationIndex < this.operationHistory.length - 1) { | 
|                 this.operationIndex++; | 
|                 this.imageRect = this.operationHistory[this.operationIndex].imageRect; | 
|                 this.cropperRect = this.operationHistory[this.operationIndex].cropperRect; | 
|                 return true; | 
|             } | 
|             return false; | 
|         }, | 
|         async drawImage(ctx,image,x,y,w,h){ | 
|             if (this.type2d) { | 
|                 await new Promise(resolve => (image.onload = resolve)); | 
|                 ctx.drawImage(image, x * this.pixel, y * this.pixel, w * this.pixel, h * this.pixel); | 
|             } else { | 
|                 const path = await new Promise((resolve)=>{ | 
|                     uni.getImageInfo({ | 
|                         src:image, | 
|                         success({path}){ | 
|                             resolve(path) | 
|                         } | 
|                     }) | 
|                 }) | 
|                 ctx.drawImage(path, x * this.pixel, y * this.pixel, w * this.pixel, h * this.pixel); | 
|                 await new Promise((resolve) => ctx.draw(false,resolve)); | 
|             } | 
|         }, | 
|         async crop() { | 
|             let ctx; | 
|             let canvas; | 
|             this.$emit('cropStart') | 
|             this.setTarget() | 
|             if (this.type2d) { | 
|                 const query = uni.createSelectorQuery().in(this); | 
|                 canvas = await new Promise(resolve => | 
|                     query | 
|                         .select('.bt-canvas') | 
|                         .node(({ node }) => resolve(node)) | 
|                         .exec() | 
|                 ); | 
|                 canvas.width = this.target.width * this.pixel; | 
|                 canvas.height = this.target.height * this.pixel; | 
|                 ctx = canvas.getContext('2d'); | 
|                 // #ifdef MP-TOUTIAO | 
|                 if(this.type2d){ | 
|                     console.warn("请注意:目前头条系小程序暂时无法使用2d canvas保存图片,建议换成V1版本") | 
|                 } | 
|                 // #endif | 
|             } else { | 
|                 ctx = uni.createCanvasContext(this.canvasId,this); | 
|             } | 
|             const scale = this.cropperRect.width / this.target.width; | 
|             const dx = (this.cropperRect.left - this.imageRect.left) / scale; | 
|             const dy = (this.cropperRect.top - this.imageRect.top) / scale; | 
|             let image; | 
|             if(this.type2d){ | 
|                 image = canvas.createImage() | 
|                 image.src = this.imageSrc; | 
|             }else{ | 
|                 image = this.imageSrc; | 
|             } | 
|             const x = -dx, | 
|                 y = -dy, | 
|                 w = this.imageRect.width / scale, | 
|                 h = this.imageRect.height / scale; | 
|             ctx.save(); | 
|             ctx.translate(x + w / 2, y + h / 2); | 
|             ctx.rotate((this.rotateAngle * Math.PI) / 180); | 
|             ctx.translate(-(x + w / 2), -(y + h / 2)); | 
|             await this.drawImage(ctx,image, x, y, w, h); | 
|             ctx.restore(); | 
|             if (this.mask !== '') { | 
|                 let imageData; | 
|                 if (this.type2d) { | 
|                     imageData = ctx.getImageData(0, 0, this.target.width, this.target.height); | 
|                 } else { | 
|                     imageData = await new Promise(resolve => { | 
|                         uni.canvasGetImageData({ | 
|                             canvasId: this.canvasId, | 
|                             x: 0, | 
|                             y: 0, | 
|                             width: this.target.width, | 
|                             height: this.target.height, | 
|                             success(res) { | 
|                                 resolve(res); | 
|                             } | 
|                         },this); | 
|                     }); | 
|                 } | 
|                 ctx.clearRect(0, 0, this.target.width, this.target.height); | 
|                 if(this.type2d){ | 
|                     image.src = this.mask; | 
|                 }else{ | 
|                     image = this.mask; | 
|                 } | 
|                 await this.drawImage(ctx,image, 0, 0, this.target.width, this.target.height); | 
|                 let maskData; | 
|                 if (this.type2d) { | 
|                     maskData = ctx.getImageData(0, 0, this.target.width, this.target.height); | 
|                 } else { | 
|                     maskData = await new Promise((resolve,reject) => { | 
|                         uni.canvasGetImageData({ | 
|                             canvasId: this.canvasId, | 
|                             x: 0, | 
|                             y: 0, | 
|                             width: this.target.width, | 
|                             height: this.target.height, | 
|                             success(res) { | 
|                                 resolve(res); | 
|                             } | 
|                         },this); | 
|                     }); | 
|                 } | 
|                 ctx.clearRect(0, 0, this.target.width, this.target.height); | 
|                 for (let index = 3; index < maskData.data.length; index += 4) { | 
|                     const alpha = maskData.data[index]; | 
|                     if (alpha !== 0) { | 
|                         imageData.data[index] = 0; | 
|                     } | 
|                 } | 
|                 if (this.type2d) { | 
|                     ctx.putImageData(imageData, 0, 0); | 
|                 } else { | 
|                     await new Promise(resolve => { | 
|                         uni.canvasPutImageData({ | 
|                             canvasId: this.canvasId, | 
|                             x: 0, | 
|                             y: 0, | 
|                             width: imageData.width, | 
|                             height: imageData.height, | 
|                             data: imageData.data, | 
|                             complete: res => { | 
|                                 resolve(res); | 
|                             } | 
|                         },this); | 
|                     }); | 
|                 } | 
|             } | 
|             return new Promise((resolve,reject) => { | 
|                 const params = {}; | 
|                 if (this.type2d) { | 
|                     params.canvas = canvas; | 
|                 } else { | 
|                     params.canvasId = this.canvasId; | 
|                 } | 
|                  | 
|                 uni.canvasToTempFilePath({ | 
|                     ...params, | 
|                     destWidth: this.target.width, | 
|                     destHeight: this.target.height, | 
|                     quality: Number(this.quality) || 1, | 
|                     fileType: this.fileType, | 
|                     success: ({ tempFilePath }) => { | 
|                         // #ifdef H5 | 
|                         var arr = tempFilePath.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; | 
|                         resolve( | 
|                             url.createObjectURL( | 
|                                 new Blob([u8arr], { | 
|                                     type: mime | 
|                                 }) | 
|                             ) | 
|                         ); | 
|                         // #endif | 
|                         resolve(tempFilePath); | 
|                     }, | 
|                     fail(err) { | 
|                         console.log('保存失败,错误信息:',err); | 
|                         reject(err); | 
|                     } | 
|                 },this); | 
|             }); | 
|         } | 
|     } | 
| }; | 
| </script> | 
| <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | 
| <script module="wxsModule" lang="wxs"> | 
| var startTouchs = []; | 
| var startDistance = 0; | 
| var touchCenter = []; | 
| var ratio = 0; | 
| var imageInstance = null; | 
| var cropperInstance = null; | 
| var touchType = ""; | 
| var touchInstance = null; | 
| var cropperRect = null; | 
| var imageRect = null; | 
| // 旋转角度 | 
| var rotateAngle = 0; | 
| // 操作时改变的对象 | 
| var changes = { | 
|     imageRect: null, | 
|     cropperRect: null | 
| } | 
|   | 
| function updateImageStyle() { | 
|     var imageRect = changes.imageRect | 
|     imageInstance.setStyle({ | 
|         left: imageRect.left + 'px', | 
|         top: imageRect.top + 'px', | 
|         width: imageRect.width + 'px', | 
|         height: imageRect.height + 'px', | 
|         transform: 'rotate(' + rotateAngle + 'deg)' | 
|     }) | 
| } | 
|   | 
| function updateCopperStyle() { | 
|     var cropperRect = changes.cropperRect | 
|     cropperInstance.setStyle({ | 
|         left: cropperRect.left + "px", | 
|         top: cropperRect.top + "px", | 
|         width: cropperRect.width + "px", | 
|         height: cropperRect.height + "px" | 
|     }) | 
| } | 
|   | 
| function imageScale(scaleRate) { | 
|     var cw = imageRect.width * (scaleRate - 1) | 
|     var ch = imageRect.height * (scaleRate - 1) | 
|     changes.imageRect = { | 
|         width: imageRect.width + cw, | 
|         height: imageRect.height + ch, | 
|         left: imageRect.left - cw * (touchCenter[0]), | 
|         top: imageRect.top - ch * (touchCenter[1]) | 
|     } | 
| } | 
| function getImageRotateSizeChange(w,h){ | 
|     var cw = h*Math.sin(rotateAngle/180 * Math.PI) | 
|     var ch = w*Math.sin(rotateAngle/180 * Math.PI) | 
|     return {cw,ch} | 
| } | 
| // 角度转弧度 | 
| function ang2deg(ang){ | 
|     return ang/180*Math.PI | 
| } | 
| // 计算旋转后真实的图片大小 | 
| function getRealSize(){ | 
|     var w = changes.imageRect.width | 
|     var h = changes.imageRect.height | 
|     var l =  changes.imageRect.left | 
|     var t =  changes.imageRect.top | 
|     // 内斜边 | 
|     var R = Math.sqrt(w*w+h*h) | 
|     var angle = Math.atan(h/w) / Math.PI * 180 | 
|     var rorate = rotateAngle%90 | 
|     var direct = Math.floor(rotateAngle/90) | 
|     var width = R*Math.cos(ang2deg(angle-rorate)) | 
|     var height = R*Math.sin(ang2deg(angle+rorate)) | 
|     if(direct % 2 === 1){ | 
|         var temp = width | 
|         width = height | 
|         height = temp | 
|     } | 
|     return { | 
|         width: width, | 
|         height: height, | 
|         left: l - (width - w)/2, | 
|         top: t - (height - h)/2, | 
|         dw: width - w, | 
|         dh: height - h | 
|     } | 
| } | 
| module.exports = { | 
|     touchStart: function (ev, oi) { | 
|         // #ifdef APP-PLUS || H5 | 
|         ev.preventDefault(); | 
|         ev.stopPropagation(); | 
|         // #endif | 
|         touchInstance = ev.instance; | 
|         var dataSet = ev.instance.getDataset() | 
|         touchType = dataSet.type; | 
|         startTouchs = ev.touches; | 
|         oi.callMethod('onTouchStart') | 
|         if (startTouchs.length == 2) { | 
|             touchType = "image" | 
|             var x1 = startTouchs[0].clientX | 
|             var y1 = startTouchs[0].clientY | 
|             var x2 = startTouchs[1].clientX | 
|             var y2 = startTouchs[1].clientY | 
|             var distance = Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2) | 
|             startDistance = Math.sqrt(distance) | 
|             var leftPercent = ((x1 + x2) / 2 - imageRect.left) / imageRect.width | 
|             var topPercent = ((y1 + y2) / 2 - imageRect.top) / imageRect.height | 
|             touchCenter = [leftPercent, topPercent] | 
|         } | 
|         return false; | 
|     }, | 
|     touchMove: function (ev, io) { | 
|         if (touchType == "") return false | 
|         // #ifdef H5 | 
|         ev.preventDefault(); | 
|         ev.stopPropagation(); | 
|         // #endif | 
|         var touches = ev.touches; | 
|         var changeX1 = touches[0].clientX - startTouchs[0].clientX; | 
|         var changeY1 = touches[0].clientY - startTouchs[0].clientY; | 
|         if (startTouchs.length == 1) { | 
|             if (touchType === 'image') { | 
|                 changes.imageRect.left = imageRect.left + changeX1; | 
|                 changes.imageRect.top = imageRect.top + changeY1; | 
|                 updateImageStyle() | 
|             } else if (touchType === 'controller') { | 
|                 var directionX = 0; | 
|                 if (touchInstance.hasClass('left')) { | 
|                     directionX = -1; | 
|                 } | 
|                 if (touchInstance.hasClass('right')) { | 
|                     directionX = 1; | 
|                 } | 
|                 var directionY = 0; | 
|                 if (touchInstance.hasClass('top')) { | 
|                     directionY = -1 | 
|                 } | 
|                 if (touchInstance.hasClass('bottom')) { | 
|                     directionY = 1 | 
|                 } | 
|                 var changeX = changeX1 * directionX; | 
|                 var changeY = changeY1 * directionY; | 
|                 // 比例缩放控制 | 
|                 if (ratio !== 0) { | 
|                     if (directionX * directionY !== 0) { | 
|                         if (changeX / ratio > changeY) { | 
|                             changeY = changeX / ratio | 
|                             changeX = changeY * ratio | 
|                         } else { | 
|                             changeX = changeY * ratio | 
|                             changeY = changeX / ratio | 
|                         } | 
|                     } else { | 
|                         if (directionX == 0) { | 
|                             changeX = changeY * ratio | 
|                         } else { | 
|                             changeY = changeX / ratio | 
|                         } | 
|                     } | 
|                 } | 
|                 var realSize = getRealSize() | 
|                 var width = cropperRect.width + changeX | 
|                 var height = cropperRect.height + changeY | 
|                 // var imageRight = imageRect.left + imageRect.width | 
|                 // var imageBottom = imageRect.top + imageRect.height | 
|                 var imageRight = realSize.left+realSize.width | 
|                 var imageBottom = realSize.top+realSize.height | 
|                 if (directionX != -1) { | 
|                     if (cropperRect.left + width > imageRight) { | 
|                         width = imageRight - cropperRect.left | 
|                         if (ratio !== 0) { | 
|                             height = width / ratio | 
|                         } | 
|                     } | 
|                 } else { | 
|                     var cLeft = cropperRect.left - changeX | 
|                     if (cLeft < realSize.left) { | 
|                         width = cropperRect.left + cropperRect.width - realSize.left | 
|                         if (ratio !== 0) { | 
|                             height = width / ratio | 
|                         } | 
|                     } | 
|                 } | 
|                 // 判断是否触底 | 
|                 if (directionY != -1) { | 
|                     if (cropperRect.top + height > imageBottom) { | 
|                         height = imageBottom - cropperRect.top | 
|                         if (ratio !== 0) { | 
|                             width = height * ratio | 
|                         } | 
|                     } | 
|                 } else { | 
|                     var cTop = cropperRect.top - changeY | 
|                     if (cTop < realSize.top) { | 
|                         height = cropperRect.top + cropperRect.height - realSize.top | 
|                         if (ratio !== 0) { | 
|                             width = height * ratio | 
|                         } | 
|                     } | 
|                 } | 
|                 if (directionX == -1) { | 
|                     changes.cropperRect.left = cropperRect.left + cropperRect.width - width | 
|                 } | 
|                 if (directionY == -1) { | 
|                     changes.cropperRect.top = cropperRect.top + cropperRect.height - height | 
|                 } | 
|                 // 边界控制 | 
|                 changes.cropperRect.width = width | 
|                 changes.cropperRect.height = height | 
|                 updateCopperStyle() | 
|             } | 
|         } else if (touches.length == 2 && startTouchs.length == 2) { | 
|             var changeX2 = touches[0].clientX - touches[1].clientX; | 
|             var changeY2 = touches[0].clientY - touches[1].clientY; | 
|             var distance = Math.pow(changeX2, 2) + Math.pow(changeY2, 2) | 
|             distance = Math.sqrt(distance) | 
|             // 放大比例 | 
|             var scaleRate = distance / startDistance | 
|             imageScale(scaleRate) | 
|             updateImageStyle() | 
|         } | 
|         return false; | 
|     }, | 
|     touchEnd: function (ev, oi) { | 
|         if (touchType === "image") { | 
|             var cropperLeft = cropperRect.left | 
|             var cropperRight = cropperRect.left + cropperRect.width | 
|             var cropperTop = cropperRect.top | 
|             var cropperBottom = cropperTop + cropperRect.height | 
|             var cropperRate = cropperRect.width / cropperRect.height | 
|             var realSize = getRealSize() | 
|             var rate = realSize.width / realSize.height | 
|             if (realSize.width < cropperRect.width || realSize.height < cropperRect.height) { | 
|                 var scale = 1 | 
|                 if (rate < cropperRate) { | 
|                     scale = cropperRect.width / realSize.width | 
|                 } else { | 
|                     scale = cropperRect.height / realSize.height | 
|                 } | 
|                 imageRect.width = changes.imageRect.width | 
|                 imageRect.height = changes.imageRect.height | 
|                 imageScale(scale) | 
|             } | 
|             // 边界控制start | 
|             if (cropperLeft < realSize.left) { | 
|                 changes.imageRect.left = cropperLeft + realSize.dw/2 | 
|             } | 
|             if (cropperRight > realSize.left + realSize.width) { | 
|                 changes.imageRect.left = cropperRight - realSize.width + realSize.dw/2 | 
|             } | 
|             if (cropperTop < realSize.top) { | 
|                 changes.imageRect.top = cropperTop + realSize.dh/2 | 
|             } | 
|             if (cropperBottom > realSize.top + realSize.height) { | 
|                 changes.imageRect.top = cropperBottom - realSize.height + realSize.dh/2 | 
|             } | 
|             // 边界控制end | 
|             updateImageStyle() | 
|         } | 
|         oi.callMethod('updateData', { | 
|             cropperRect: changes.cropperRect, | 
|             imageRect: changes.imageRect, | 
|         }) | 
|         touchType = "" | 
|         startTouchs = [] | 
|         return false; | 
|     }, | 
|     // 将逻辑层的图像变换同步过来 | 
|     // 裁剪比例变化 | 
|     changeRatio: function (value) { | 
|         ratio = value | 
|     }, | 
|     changeRotateAngle:function (value){ | 
|         rotateAngle = value; | 
|         if(imageInstance){ | 
|             updateImageStyle() | 
|         } | 
|         var realSize = getRealSize() | 
|     }, | 
|     changeImageRect: function (value, oldValue, oi) { | 
|         if (value) { | 
|             imageRect = value; | 
|             changes.imageRect = { | 
|                 left: value.left, | 
|                 top: value.top, | 
|                 width: value.width, | 
|                 height: value.height | 
|             }; | 
|             // #ifndef MP-WEIXIN || MP-QQ | 
|             setTimeout(function() { | 
|                 imageInstance = oi.selectComponent('.mainContent > .image') | 
|                 updateImageStyle(); | 
|             }); | 
|             // #endif | 
|             // #ifdef MP-WEIXIN || MP-QQ | 
|             imageInstance = oi.selectComponent('.mainContent > .image') | 
|             updateImageStyle(); | 
|             // #endif | 
|         } | 
|     }, | 
|     changeCropper: function (value, oldValue, oi) { | 
|         if (value) { | 
|             cropperRect = value | 
|             changes.cropperRect = { | 
|                 left: value.left, | 
|                 top: value.top, | 
|                 width: value.width, | 
|                 height: value.height | 
|             } | 
|             // #ifdef H5 || APP-VUE | 
|             setTimeout(function() { | 
|             // #endif | 
|                 cropperInstance = oi.selectComponent('.mainContent > .cropper') | 
|                 updateCopperStyle() | 
|             // #ifdef H5 || APP-VUE | 
|             }); | 
|             // #endif | 
|         } | 
|     } | 
| } | 
| </script> | 
| <!-- #endif --> | 
| <style lang="scss" scoped> | 
| .bt-container { | 
|     // display: flex; | 
|     // flex-direction: column; | 
|     // justify-content: space-between; | 
|     height: 100%; | 
|     box-sizing: border-box; | 
|     // background-color: #0e1319; | 
|     position: relative; | 
|     overflow: hidden; | 
|   | 
|     .bt-canvas { | 
|         position: fixed; | 
|         left: 100%; | 
|         top: 0; | 
|     } | 
|   | 
|     .mainContent { | 
|         // flex: 1; | 
|         // flex-shrink:0; | 
|         // margin: 60rpx; | 
|         width: 100%; | 
|         height: 100%; | 
|         position: relative; | 
|         display: flex; | 
|         justify-content: center; | 
|         align-items: center; | 
|         // touch-action: none; | 
|   | 
|         .image { | 
|             position: absolute; | 
|             // will-change: transform; | 
|             // transform-origin: center center; | 
|             width: 85%; | 
|             height: 85%; | 
|             will-change: left, top, width, height; | 
|         } | 
|   | 
|         .controller { | 
|             position: absolute; | 
|             z-index: 99; | 
|             padding: 10rpx; | 
|             $offset: -20rpx; | 
|   | 
|             &::after { | 
|                 display: block; | 
|                 content: ''; | 
|                 filter: drop-shadow(0 0px 10rpx rgba(0, 0, 0, 0.3)); | 
|             } | 
|   | 
|             &.vertical { | 
|                 top: calc(50% - 30rpx); | 
|             } | 
|   | 
|             &.horizon { | 
|                 left: calc(50% - 30rpx); | 
|             } | 
|   | 
|             &.left { | 
|                 &::after { | 
|                     height: 40rpx; | 
|                     border-left: 10rpx solid #fff; | 
|                 } | 
|   | 
|                 left: $offset; | 
|             } | 
|   | 
|             &.right { | 
|                 &::after { | 
|                     height: 40rpx; | 
|                     border-right: 10rpx solid #fff; | 
|                 } | 
|   | 
|                 right: $offset; | 
|             } | 
|   | 
|             &.top { | 
|                 top: $offset; | 
|   | 
|                 &::after { | 
|                     width: 40rpx; | 
|                     border-top: 10rpx solid #fff; | 
|                 } | 
|             } | 
|   | 
|             &.bottom { | 
|                 bottom: $offset; | 
|   | 
|                 &::after { | 
|                     width: 40rpx; | 
|                     border-bottom: 10rpx solid #fff; | 
|                 } | 
|             } | 
|   | 
|             &.left.bottom, | 
|             &.right.bottom, | 
|             &.left.top, | 
|             &.left.bottom { | 
|                 &::after { | 
|                     width: 30rpx; | 
|                     height: 30rpx; | 
|                     background-color: transparent; | 
|                 } | 
|             } | 
|         } | 
|   | 
|         .cropper { | 
|             position: absolute; | 
|             border: 1px solid #eee; | 
|             box-sizing: content-box; | 
|             // transform-origin: center center; | 
|             outline: 999px solid rgba(0, 0, 0, 0.5); | 
|             will-change: left, top, width, height; | 
|   | 
|             // display: contain; | 
|             // pointer-events: none; | 
|             .mask { | 
|                 position: absolute; | 
|                 left: 0; | 
|                 top: 0; | 
|                 width: 100%; | 
|                 height: 100%; | 
|                 opacity: 0.5; | 
|             } | 
|   | 
|             .line { | 
|                 position: absolute; | 
|                 // background-color: #eee; | 
|             } | 
|   | 
|             .row { | 
|                 width: 100%; | 
|                 height: 0px; | 
|                 left: 0; | 
|                 border-top: 1px dashed #007aff; | 
|             } | 
|   | 
|             .col { | 
|                 height: 100%; | 
|                 width: 0px; | 
|                 border-left: 1px dashed #007aff; | 
|             } | 
|   | 
|             .row1 { | 
|                 top: 33%; | 
|             } | 
|   | 
|             .row2 { | 
|                 top: 66%; | 
|             } | 
|   | 
|             .col1 { | 
|                 left: 33%; | 
|             } | 
|   | 
|             .col2 { | 
|                 left: 66%; | 
|             } | 
|         } | 
|     } | 
|   | 
|     // .slot { | 
|     //     position: relative; | 
|     //     padding-top: 20rpx; | 
|     // } | 
| } | 
|   | 
| .anim { | 
|     transition: 0.2s; | 
| } | 
| </style> |