瀏覽代碼

feature:增加壁纸裁剪组件

zeng.chen 8 月之前
父節點
當前提交
07d1bf07f7

+ 725 - 0
pages/components/imageCrop/index.js

@@ -0,0 +1,725 @@
+// cropper/cropper.js
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    /**
+     * @type         number
+     * @description  组件裁剪显示区域的最大比例,如果裁剪的图片过长,则做限制,默认最大宽高比例为 宽640 / 高960 (宽高比例)
+     * @example 1    如果CROPPER_WIDTH宽度是720px,那么裁剪区域的高度也就是 CROPPER_WIDTH / cropperRatio 为 720px;
+     */
+    cropperRatio: {
+      type: Number,
+      value: 0.7
+    },
+
+    /**
+     * @type         number
+     * @description  初始化的裁剪比例
+     * @example 0    默认初始化的裁剪区域宽高为图片的宽高,且裁剪比例不固定
+     * @example 0.5  宽高比例固定,且宽和高的比例为 1 : 2 的比例
+     * @example 2    宽高比例固定,且宽和高的比例为 2 : 1 的比例
+     */
+    cutRatio: {
+      type: Number,
+      value: 1
+    },
+
+    /**
+     * @type         string
+     * @description  需要裁剪的图片地址
+     */
+    imageSrc: {
+      type: String,
+      value: ''
+    },
+
+    /**
+     * @type         number
+     * @description  裁剪区域的宽度
+     */
+    cropperWidth: {
+      type: Number,
+      value: 720
+    },
+
+    /**
+     * @type          number
+     * @description   最小裁剪的范围
+     */
+    minCropperW: {
+      type: Number,
+      value: 100
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    /**
+     * @type         boolean
+     * @description  图片在进行网络请求完成之后显示,showImg用于控制图片显示时机
+     */
+    showImg: false,
+
+    /**
+     * @
+     */
+    // 动态的宽高
+    cropperW: null,
+    cropperH: null,
+
+    // 图片缩放值
+    scaleP: 0,
+    // 裁剪框 宽高
+    cutL: 0,
+    cutT: 0,
+    cutB: 0,
+    cutR: 0,
+
+    qualityWidth: null,
+    innerAspectRadio: null,
+
+    filePath: null
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    close() {
+      wx.hideLoading()
+      this.triggerEvent('close')
+    },
+
+    /**
+     * 初始化变量信息
+     */
+    initStaticData() {
+      this.drag = {
+        CUT_L: null,  // 初始化拖拽元素的left值
+        CUT_T: null,  // ...top值
+        CUT_R: null,  // ...right值
+        CUT_B: null,  // ...bottom值
+
+        CUT_W: null,  // 初始化拖拽元素的宽度
+        CUT_H: null,  // 初始化拖拽元素的高度
+
+        IS_TOUCH_CONTENT: false,  // 是否是可拖动的状态(拖拽裁剪框)
+        IS_TOUCH_SIDE: false,  // 是否可以拖拽边框
+        IS_NO_DRAG: false, 
+
+        // 拖拽区域的时候设置
+        TOUCH_OFFSET_X: null, // 手按下相对于裁剪框左边的距离
+        TOUCH_OFFSET_Y: null, // 手按下相对于裁剪框上边的距离
+
+        TOUCH_MAX_MOVE_SECTION_X: null, // 移动区域的时候移动的x方向最大区间
+        TOUCH_MAX_MOVE_SECTION_Y: null, // 移动区域的时候移动的y方向最大区间
+
+        MOVE_PAGE_X: null,  // 手移动的时候x的位置
+        MOVE_PAGE_Y: null,  // 手移动的时候Y的位置
+
+        SPACE_TOP_POSITION: null,
+        SPACE_LEFT_POSITION: null,
+        SPACE_RIGHT_POSITION: null,
+        SPACE_BOTTOM_POSITION: null
+      }
+
+      this.conf = {
+        // 图片比例
+        IMG_RATIO: null,
+
+        // 图片实际宽高
+        IMG_REAL_W: null,   // 图片实际的宽度
+        IMG_REAL_H: null,   // 图片实际的高度
+
+        // 裁剪除黑色区域以内的高度
+        CROPPER_HEIGHT: null,   // 图片背景区域宽度
+        CROPPER_WIDTH: null,    // 图标背景区域高度
+
+        // 设置最小裁剪宽度高度
+        CUT_MIN_W: null,  // 最小限制多宽
+        CUT_MIN_H: null,  // 最小限制多高
+
+        // 裁剪图片区域的信息
+        // CROPPER_IMG_W: null,    // 也就是 data.cropperW
+        // CROPPER_IMG_H: null,    // 也就是 data.cropperH
+
+        // 移动的比例
+        DRAG_MOVE_RATIO: 750 / wx.getSystemInfoSync().windowWidth,  //移动时候的比例,
+
+        INIT_DRAG_POSITION: 0,    // 初始化屏幕宽度和裁剪区域的宽度之差,用于设置初始化裁剪的宽度
+        DRAW_IMAGE_W: null,       // 设置生成的图片宽度
+
+        // 最大可显示得图片宽度,需要设定最大值,否则安卓部分机器会闪退, 控制qualityWidth的最大值
+        MAX_QW: 2550,
+
+        /**
+         * 最小裁剪宽度  由于设置了裁剪的UI样式,裁剪的宽高必须要有最小宽度,这个宽度是裁剪长或者宽的最短一方的宽度
+         * 如 400 200
+         * 那么如果只能设置为100的时候
+         * 那么最小缩放到200 100的效果,之后只能放大不能缩小
+         */
+        MIN_CROPPER_DIS: 100
+      }
+    },
+
+    /**
+     * 选择本地图片
+     * 基于底部中间的按钮的点击事件
+     */
+    getImage() {
+      const _this = this
+      wx.chooseImage({
+        success: function (res) {
+          _this.setData({
+            isShowImg: false,
+            filePath: res.tempFilePaths[0],
+          })
+          _this.loadImage(_this.data.filePath)
+        },
+      })
+    },
+
+    /**
+     * 初始化加载图片
+     */
+    loadImage(src) {
+      const _this = this
+
+      wx.showLoading({
+        title: '图片加载中...',
+      })
+      // console.log(this.properties.imageSrc)
+      wx.getImageInfo({
+        src: src ? src : this.properties.imageSrc,
+        success: function (res) {
+          /**
+           * 获取图片真实宽高
+           * 设置DRAW_IMAGE_W
+           */
+          _this.conf.DRAW_IMAGE_W = _this.conf.IMG_REAL_W = res.width
+          _this.conf.IMG_REAL_H = res.height
+          _this.conf.IMG_RATIO = Number((_this.conf.IMG_REAL_W / _this.conf.IMG_REAL_H).toFixed(5))
+          _this.conf.CROPPER_HEIGHT = Math.ceil(_this.properties.cropperWidth / _this.conf.IMG_RATIO)
+
+          const scaleP = Number((_this.conf.IMG_REAL_W / _this.properties.cropperWidth).toFixed(5))
+          const qualityWidth = _this.conf.DRAW_IMAGE_W > _this.conf.MAX_QW ? _this.conf.MAX_QW : _this.conf.DRAW_IMAGE_W
+          // const MIN_RANG
+          const p = _this.initPosition()
+
+          // 根据图片的宽高显示不同的效果 保证图片可以正常显示 (横屏)
+          // console.log(_this.conf.IMG_RATIO)
+          // console.log(_this.conf)
+          // console.log(_this.drag)
+          // console.log(_this.data)
+          // console.log(p)
+          if (_this.conf.IMG_RATIO >= 1) {
+            _this.conf.CROPPER_WIDTH = _this.properties.cropperWidth
+            _this.setData ({
+              cropperW: _this.properties.cropperWidth,
+              cropperH: _this.conf.CROPPER_HEIGHT,
+
+              // 初始化left right
+              cutL: p.left,
+              cutT: p.top,
+              cutR: p.right,
+              cutB: p.bottom,
+
+              // 图片缩放值
+              scaleP,
+              qualityWidth,
+              innerAspectRadio: _this.conf.IMG_RATIO,
+              filePath: res.path
+            })
+          } else {
+            // 竖屏初始化
+            _this.setData ({
+              cropperW: _this.conf.CROPPER_WIDTH,
+              cropperH: _this.conf.CROPPER_HEIGHT,
+
+              // 初始化left right
+              cutL: p.left,
+              cutT: p.top,
+              cutR: p.right,
+              cutB: p.bottom,
+
+              // 图片缩放值
+              scaleP,
+              qualityWidth,
+              innerAspectRadio: _this.conf.IMG_RATIO,
+              filePath: res.path
+            })
+          }
+
+          // 设置裁剪最小限制
+          _this.setMinCutInfo()
+
+          _this.setData({
+            showImg: true
+          })
+
+          wx.hideLoading()
+        } 
+      })
+    },
+
+    /**
+     * 点击完成裁剪图片并返回图片信息
+     * width 宽度
+     * height  高度
+     * url  图片的临时存储地址
+     */
+    getImageInfo() {
+      const _this = this
+      wx.showLoading({
+        title: '图片生成中...',
+      })
+      this.drag.IS_NO_DRAG = true
+      // 将图片写入画布
+      const ctx = wx.createCanvasContext('wxCropperCanvas', _this)
+      const w = this.data.qualityWidth
+      const h = Math.ceil(this.data.qualityWidth / this.data.innerAspectRadio)
+      ctx.drawImage(_this.data.filePath, 0, 0, w, h)
+      ctx.draw(true, () => {
+        // 获取画布要裁剪的位置和宽度   均为百分比 * 画布中图片的宽度    保证了在微信小程序中裁剪的图片模糊  位置不对的问题
+        const canvasW = Math.ceil(((_this.data.cropperW - _this.data.cutL - _this.data.cutR) / _this.data.cropperW) * w)
+        const canvasH = Math.ceil(((_this.data.cropperH - _this.data.cutT - _this.data.cutB) / _this.data.cropperH) * h)
+        const canvasL = Math.ceil((_this.data.cutL / _this.data.cropperW) * w)
+        const canvasT = Math.ceil((_this.data.cutT / _this.data.cropperH) * h)
+        // console.log(canvasW, canvasH, canvasL, canvasT)
+        // 生成图片
+        wx.canvasToTempFilePath({
+          x: canvasL,
+          y: canvasT,
+          width: canvasW,
+          height: canvasH,
+          destWidth: canvasW,
+          destHeight: canvasH,
+          quality: 0.9,
+          canvasId: 'wxCropperCanvas',
+          success: function (res) {
+            // console.log(res.tempFilePath)
+            const img = {
+              path: res.tempFilePath,
+              width: canvasW,
+              height: canvasH
+            }
+            _this.triggerEvent('close', img)
+          },
+          complete: function () {
+            // 结束之后可拖拽放大缩小
+            // 关闭loading
+            wx.hideLoading()
+            _this.drag.IS_NO_DRAG = false
+          }
+        }, _this)
+      })
+    },
+
+    /**
+     * 设置最小裁剪宽度高度限制
+     */
+    setMinCutInfo() {
+      this.conf.CUT_MIN_W = this.properties.minCropperW
+      if (this.properties.cutRatio) {
+        this.conf.CUT_MIN_H = this.conf.CUT_MIN_W / this.properties.cutRatio
+        // console.log('this.conf.CUT_MIN_H', this.conf.CUT_MIN_H)
+        return
+      }
+      this.conf.CUT_MIN_H = this.conf.CUT_MIN_W
+      // console.log('this.conf.CUT_MIN_H', this.conf.CUT_MIN_H)
+    },
+
+    /**
+     * 初始化裁剪位置
+     * 需要 cutRatio 来判断
+     * @return 返回裁剪的left, right, top bottom的值
+     */
+    initPosition() {
+      // 定义返回的对象
+      const left = 0,
+        right = 0,
+        top = 0,
+        bottom = 0
+      // cutRatio为0 且为横行  则为不等比裁剪
+      if (this.properties.cutRatio === 0 && this.conf.IMG_RATIO >= 1) {
+        return { left, right, top, bottom }
+      }
+
+      // 如果图片宽度大于等于高度(横向)
+      if (this.conf.IMG_RATIO >= 1) {
+        // 获取基本宽度
+        // 图片显示区域比裁剪比例大的时候
+        if (this.conf.IMG_RATIO >= this.properties.cutRatio) {
+          // left的值
+          let leftRight = Math.ceil((this.properties.cropperWidth - (this.conf.CROPPER_HEIGHT * this.properties.cutRatio)) / 2)
+          return {
+            left: leftRight,
+            right: leftRight,
+            top: 0,
+            bottom: 0
+          }
+        }
+        // 否则
+        const bottomTop = Math.ceil((this.conf.CROPPER_HEIGHT  - (this.properties.cropperWidth / this.properties.cutRatio)) / 2)
+        return {
+          left: 0,
+          right: 0,
+          top: bottomTop,
+          bottom: bottomTop
+        }
+      }
+
+      // 如果图片宽度小于高度 (竖向)
+      // const r = _this.properties.cropperRatio > _this.conf.IMG_RATIO ? _this.properties.cropperRatio : _this.conf.IMG_RATIO
+      if (this.properties.cropperRatio > this.conf.IMG_RATIO) {
+        this.conf.CROPPER_WIDTH = this.properties.cropperWidth / this.properties.cropperRatio * this.conf.IMG_RATIO
+        this.conf.CROPPER_HEIGHT = this.properties.cropperWidth / this.properties.cropperRatio
+      } else {
+        this.conf.CROPPER_WIDTH = this.properties.cropperWidth
+        this.conf.CROPPER_HEIGHT = this.properties.cropperWidth / this.conf.IMG_RATIO
+      }
+      // 定义四个位置  如果不比例裁剪
+      if (this.properties.cutRatio === 0) return { left, right, top, bottom }
+      // 否则
+
+      if (this.conf.IMG_RATIO >= this.properties.cutRatio) {
+        const leftRight = Math.ceil((this.conf.CROPPER_WIDTH - (this.conf.CROPPER_HEIGHT * this.properties.cutRatio)) / 2)
+        return {
+          left: leftRight,
+          right: leftRight,
+          top: 0,
+          bottom: 0
+        }
+      }
+      const bottomTop = Math.ceil((this.conf.CROPPER_HEIGHT  - (this.conf.CROPPER_WIDTH / this.properties.cutRatio)) / 2)
+      return {
+        left: 0,
+        right: 0,
+        top: bottomTop,
+        bottom: bottomTop
+      }
+    },
+
+    /**
+     * 裁剪框的拖动事件
+     */
+    contentDragStart(e) {
+      if (this.drag.IS_NO_DRAG) return
+      this.drag.IS_TOUCH_CONTENT = true
+
+      this.drag.TOUCH_OFFSET_X = (e.touches[0].pageX * this.conf.DRAG_MOVE_RATIO - this.data.cutL)
+      this.drag.TOUCH_OFFSET_Y = (e.touches[0].pageY * this.conf.DRAG_MOVE_RATIO - this.data.cutT)
+
+      /**
+       * 获取可移动的最大值 xy方向
+       */
+      const cc = this.cropperCurrentInfo()
+      this.drag.TOUCH_MAX_MOVE_SECTION_X = cc.x
+      this.drag.TOUCH_MAX_MOVE_SECTION_Y = cc.y
+    },
+
+    /**
+     * 获取裁剪区域信息
+     */
+    cropperCurrentInfo() {
+      const x = this.data.cutL + this.data.cutR
+      const y = this.data.cutT + this.data.cutB
+
+      // 获取拖拽元素的宽高
+      this.drag.CUT_W = this.data.cropperW - x
+      this.drag.CUT_H = this.data.cropperH - y
+
+      // 返回x, y
+      return {
+        x,
+        y
+      }
+    },
+
+    /**
+     * 裁剪框拖动
+     */
+    contentDragMove(e) {
+      if (this.drag.IS_NO_DRAG) return
+      if (!this.drag.IS_TOUCH_CONTENT) return
+      const MOVE_X = e.touches[0].pageX * this.conf.DRAG_MOVE_RATIO - this.drag.TOUCH_OFFSET_X
+      const MOVE_Y = e.touches[0].pageY * this.conf.DRAG_MOVE_RATIO - this.drag.TOUCH_OFFSET_Y
+
+      const drag_x = Math.min(this.drag.TOUCH_MAX_MOVE_SECTION_X, Math.max(0, MOVE_X))
+      const drag_y = Math.min(this.drag.TOUCH_MAX_MOVE_SECTION_Y, Math.max(0, MOVE_Y))
+
+      this.setData({
+        cutL: Math.ceil(drag_x),
+        cutR: Math.ceil(this.data.cropperW - this.drag.CUT_W - drag_x),
+        cutT: Math.ceil(drag_y),
+        cutB: Math.ceil((this.data.cropperH - this.drag.CUT_H - drag_y))
+      })
+
+      // 需要初始化
+      this.drag.TOUCH_OFFSET_X = (e.touches[0].pageX * this.conf.DRAG_MOVE_RATIO - this.data.cutL)
+      this.drag.TOUCH_OFFSET_Y = (e.touches[0].pageY * this.conf.DRAG_MOVE_RATIO - this.data.cutT)
+    },
+
+    /**
+     * 裁剪框拖动结束
+     */
+    contentTouchEnd() {
+      this.drag.IS_TOUCH_CONTENT = false
+    },
+
+    /**
+     * 裁剪框4个方向的拖拽
+     */
+    sideDragStart(e) {
+      if (this.drag.IS_NO_DRAG) return
+      this.drag.IS_TOUCH_SIDE = true
+      this.drag.MOVE_PAGE_X = e.touches[0].pageX
+      this.drag.MOVE_PAGE_Y = e.touches[0].pageY
+
+      // 初始化设置
+      this.conf.CUT_T = this.data.cutT
+      this.conf.CUT_L = this.data.cutL
+      this.conf.CUT_R = this.data.cutR
+      this.conf.CUT_B = this.data.cutB
+
+      // 初始化最大移动区域
+      this.drag.SPACE_TOP_POSITION = this.conf.CROPPER_HEIGHT - this.conf.CUT_B - this.conf.CUT_MIN_H
+      this.drag.SPACE_BOTTOM_POSITION = this.conf.CROPPER_HEIGHT - this.conf.CUT_T - this.conf.CUT_MIN_H
+      this.drag.SPACE_RIGHT_POSITION = this.conf.CROPPER_WIDTH - this.conf.CUT_L - this.conf.CUT_MIN_W
+      this.drag.SPACE_LEFT_POSITION = this.conf.CROPPER_WIDTH - this.conf.CUT_R - this.conf.CUT_MIN_W
+    },
+    
+    /**
+     *  拖拽中
+     */
+    sideDragMove(e) {
+      if (this.drag.IS_NO_DRAG) return
+      if (!this.drag.IS_TOUCH_SIDE) return
+      const type = e.target.dataset.drag
+      if (this.properties.cutRatio === 0) {
+        this.sideDragMoveDefault(e, type)
+      } else {
+        this.sideDragMoveConst(e, type)
+      }
+    },
+
+    /**
+     * 拖拽结束
+     */
+    sideDragEnd() {
+      this.drag.IS_TOUCH_SIDE = false
+      // console.log('sideDragEnd')
+    },
+
+    /**
+     * 开始拖拽
+     * 等比例的拖拽方式
+     */
+    sideDragMoveConst(e, type) {
+      const xLength = (e.touches[0].pageX - this.drag.MOVE_PAGE_X) * this.conf.DRAG_MOVE_RATIO
+      const yLength = (e.touches[0].pageY - this.drag.MOVE_PAGE_Y) * this.conf.DRAG_MOVE_RATIO
+      switch (type) {
+        case 'top':
+          let top = this.conf.CUT_T + yLength
+          top = Math.ceil(top >= this.drag.SPACE_TOP_POSITION ? this.drag.SPACE_TOP_POSITION : top)
+
+          let topL = this.conf.CUT_L + yLength * this.properties.cutRatio
+          topL = Math.ceil(topL >= this.drag.SPACE_LEFT_POSITION ? this.drag.SPACE_LEFT_POSITION : topL)
+
+          if (topL < 0) {
+            if (this.data.cutT <= 0) return
+            if (this.data.cutL >= 0) return
+            this.setData({
+              cutL: 0
+            })
+            return
+          }
+
+          if (top <= 0) {
+            this.setData({
+              cutT: 0
+            })
+            return
+          }
+
+          this.setData({
+            cutT: top,
+            cutL: topL
+          })
+          break
+        case 'left':
+          let left = this.conf.CUT_L + xLength
+          left = Math.ceil(left >= this.drag.SPACE_LEFT_POSITION ? this.drag.SPACE_LEFT_POSITION : left)
+
+          let leftB = this.conf.CUT_B + xLength / this.properties.cutRatio
+          leftB = Math.ceil(leftB >= this.drag.SPACE_BOTTOM_POSITION ? this.drag.SPACE_BOTTOM_POSITION : leftB)
+
+          // console.log(leftB)
+          // console.log(left)
+          if (leftB < 0) {
+            if (this.data.cutL <= 0) return
+            if (this.data.cutB >= 0) return
+            this.setData({
+              cutB: 0
+            })
+            return
+          }
+
+          if (left <= 0) {
+            this.setData({
+              cutL: 0
+            })
+            return
+          }
+
+          this.setData({
+            cutL: left,
+            cutB: leftB
+          })
+          break
+        case 'bottom':
+          let bottom = this.conf.CUT_B - yLength
+          bottom = Math.ceil(bottom >= this.drag.SPACE_BOTTOM_POSITION ? this.drag.SPACE_BOTTOM_POSITION : bottom)
+
+          let bottomR = this.conf.CUT_R - yLength * this.properties.cutRatio
+          bottomR = Math.ceil(bottomR >= this.drag.SPACE_RIGHT_POSITION ? this.drag.SPACE_RIGHT_POSITION : bottomR)
+
+          if (bottomR < 0) {
+            if (this.data.cutB <= 0) return
+            if (this.data.cutR >= 0) return
+            this.setData({
+              cutR: 0
+            })
+            return
+          }
+
+          if (bottom <= 0) {
+            this.setData({
+              cutB: 0
+            })
+            return
+          }
+
+          this.setData({
+            cutR: bottomR,
+            cutB: bottom
+          })
+          break
+        case 'right':
+          let right = this.conf.CUT_R - xLength
+          right = Math.ceil(right >= this.drag.SPACE_RIGHT_POSITION ? this.drag.SPACE_RIGHT_POSITION : right)
+
+          let rightT = this.conf.CUT_T - xLength / this.properties.cutRatio
+          rightT = Math.ceil(rightT >= this.drag.SPACE_TOP_POSITION ? this.drag.SPACE_TOP_POSITION : rightT)
+
+          if (rightT < 0) {
+            if (this.data.cutR <= 0) return
+            if (this.data.cutT >= 0) return
+            this.setData({
+              cutT: 0
+            })
+            return
+          }
+
+          if (right <= 0) {
+            this.setData({
+              cutR: 0
+            })
+            return
+          }
+
+          this.setData({
+            cutR: right,
+            cutT: rightT
+          })
+          break
+      }
+    },
+
+    /**
+     * 非等比例拖拽的操作
+     */
+    sideDragMoveDefault(e, type) {
+      const xLength = (e.touches[0].pageX - this.drag.MOVE_PAGE_X) * this.conf.DRAG_MOVE_RATIO
+      const yLength = (e.touches[0].pageY - this.drag.MOVE_PAGE_Y) * this.conf.DRAG_MOVE_RATIO
+      switch (type) {
+        case 'top':
+          let top = this.conf.CUT_T + yLength
+          top = top <= 0 ? 0 : top
+          top = Math.ceil(top >= this.drag.SPACE_TOP_POSITION ? this.drag.SPACE_TOP_POSITION : top)
+          this.setData({
+            cutT: top
+          })
+          break
+        case 'bottom':
+          let bottom = this.conf.CUT_B - yLength
+          bottom = bottom <= 0 ? 0 : bottom
+          bottom = Math.ceil(bottom >= this.drag.SPACE_BOTTOM_POSITION ? this.drag.SPACE_BOTTOM_POSITION : bottom)
+          this.setData({
+            cutB: bottom
+          })
+          break
+        case 'right':
+          let right = this.conf.CUT_R - xLength
+          right = right <= 0 ? 0 : right
+          right = Math.ceil(right >= this.drag.SPACE_RIGHT_POSITION ? this.drag.SPACE_RIGHT_POSITION : right)
+          this.setData({
+            cutR: right
+          })
+          break
+        case 'left':
+          let left = this.conf.CUT_L + xLength
+          left = left <= 0 ? 0 : left
+          left = Math.ceil(left >= this.drag.SPACE_LEFT_POSITION ? this.drag.SPACE_LEFT_POSITION : left)
+          this.setData({
+            cutL: left
+          })
+          break
+        case 'rightBottom':
+          let rightBottomR = this.conf.CUT_R - xLength
+          rightBottomR = rightBottomR <= 0 ? 0 : rightBottomR
+          rightBottomR = Math.ceil(rightBottomR >= this.drag.SPACE_RIGHT_POSITION ? this.drag.SPACE_RIGHT_POSITION : rightBottomR)
+
+          let rightBottomB = this.conf.CUT_B - yLength
+          rightBottomB = rightBottomB <= 0 ? 0 : rightBottomB
+          rightBottomB = Math.ceil(rightBottomB >= this.drag.SPACE_BOTTOM_POSITION ? this.drag.SPACE_BOTTOM_POSITION : rightBottomB)
+          this.setData({
+            cutB: rightBottomB,
+            cutR: rightBottomR
+          })
+          break
+        default:
+          break
+      }
+    },
+  },
+  
+  created: function () {
+    this.initStaticData()
+    // console.log(this.drag)
+    // console.log(this.conf)
+    // console.log(this.data)
+    // console.log(this.conf.DRAG_MOVE_RATIO)
+  },
+
+  attached: function () {
+    // console.log('attached')
+    this.loadImage()
+  },
+
+  ready: function () {
+    // console.log('ready')
+  },
+
+  moved: function () {
+    // console.log('moved')
+  },
+
+  detached: function () {
+    // console.log('detached')
+  }
+})

+ 4 - 0
pages/components/imageCrop/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 42 - 0
pages/components/imageCrop/index.wxml

@@ -0,0 +1,42 @@
+<!--cropper/cropper.wxml-->
+<view class="wx-content-info">
+  <view class='cropper-content'>
+    <view wx:if="{{showImg}}" class="wx-corpper" style="background:#000;">
+      <view class="wx-corpper-content" style="width:{{cropperW}}rpx;height:{{cropperH}}rpx;">
+        <view class="wx-corpper-content-bg">
+          <!-- <view class="mask" hover-class="none" hover-stop-propagation="false"></view> -->
+          <image src="{{filePath}}" style="width:{{cropperW}}rpx;height:{{cropperH}}rpx"></image>
+        </view>
+        <view class="wx-corpper-crop-box" bind:touchstart="contentDragStart" bind:touchmove="contentDragMove" bind:touchend="contentTouchEnd" style="left:{{cutL}}rpx;top:{{cutT}}rpx;right:{{cutR}}rpx;bottom:{{cutB}}rpx">
+        <!-- <view class="wx-corpper-crop-box" bind:touchstart="contentDragStart" bind:touchmove="contentDragMove" bind:touchend="contentTouchEnd" style="width:{{cropperW - cutL - cutR}}rpx;height:{{cropperH - cutT- cutB}}rpx; transform: translateX({{cutL}}rpx) translateY({{cutT}}rpx);"> -->
+          <view class="wx-cropper-view-box">
+            <!-- <view class="wx-cropper-view-box-img" style="width:100%;height:100%" hover-class="none" hover-stop-propagation="false">
+              <image src="{{imageSrc}}" style="width:{{cropperW}}rpx;height:{{cropperH}}rpx; transform: translateX({{-cutL}}rpx) translateY({{-cutT}}rpx);"></image>
+            </view> -->
+            <view class="wx-cropper-dashed-h"></view>
+            <view class="wx-cropper-dashed-v"></view>
+            <view class="wx-cropper-line-t" data-drag="top" catch:touchstart="sideDragStart" catch:touchmove="sideDragMove" catch:touchend="sideDragEnd"></view>
+            <view class="wx-cropper-line-r" data-drag="right" catch:touchstart="sideDragStart" catch:touchmove="sideDragMove" catch:touchend="sideDragEnd"></view>
+            <view class="wx-cropper-line-b" data-drag="bottom" catch:touchstart="sideDragStart" catch:touchmove="sideDragMove" catch:touchend="sideDragEnd"></view>
+            <view class="wx-cropper-line-l" data-drag="left" catch:touchstart="sideDragStart" catch:touchmove="sideDragMove" catch:touchend="sideDragEnd"></view>
+            <view class="wx-cropper-point point-t" data-drag="top" catch:touchstart="sideDragStart" catch:touchmove="sideDragMove" catch:touchend="sideDragEnd"></view>
+            <view class="wx-cropper-point point-tr" data-drag="topTight"></view>
+            <view class="wx-cropper-point point-r" data-drag="right" catch:touchstart="sideDragStart" catch:touchmove="sideDragMove" catch:touchend="sideDragEnd"></view>
+            <view class="wx-cropper-point point-rb" data-drag="rightBottom" catch:touchstart="sideDragStart" catch:touchmove="sideDragMove" catch:touchend="sideDragEnd"></view>
+            <view class="wx-cropper-point point-b" data-drag="bottom" catch:touchstart="sideDragStart" catch:touchmove="sideDragMove" catch:touchend="sideDragEnd"></view>
+            <view class="wx-cropper-point point-bl" data-drag="bottomLeft"></view>
+            <view class="wx-cropper-point point-l" data-drag="left" catch:touchstart="sideDragStart" catch:touchmove="sideDragMove" catch:touchend="sideDragEnd"></view>
+            <view class="wx-cropper-point point-lt" data-drag="leftTop"></view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+  <view class='cropper-config'>
+    <text class="cropper-cancle" bindtap="close">取消</text>
+    <text class="cropper-cancle" bindtap="getImage">选择图片</text>
+    <text class="cropper-save" bindtap="getImageInfo">完成</text>
+  </view>
+  <canvas canvas-id="wxCropperCanvas"
+          style="position:absolute; width:{{qualityWidth}}px;height:{{qualityWidth / innerAspectRadio }}px;top:-9999px;left:-9999px;"></canvas>
+</view>

+ 375 - 0
pages/components/imageCrop/index.wxss

@@ -0,0 +1,375 @@
+/* pages/cropper/cropper.wxss */
+Page {
+  /* 点的颜色 */
+  --primary-color: rgb(255, 255, 255);
+  /* 边框颜色 */
+  --primary-color-outline: rgba(255, 255, 255, 0.75);
+  /* 虚线颜色 */
+  --primary-color-dashed: rgba(255, 255, 255, 0.46);
+  /* 裁剪区域背景色 */
+  --box-bg: transparent;
+  /* 裁剪所有的背景色 */
+  --cropper-bg: #000;
+}
+.wx-content-info {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: var(--cropper-bg);
+}
+.wx-content-info .cropper-content {
+  min-height: calc(20%);
+  width: 720rpx;
+  margin: 0 auto;
+  margin-top: 8rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 2;
+  bottom: 88rpx;
+}
+.wx-content-info .cropper-content .wx-corpper {
+  position: relative;
+  overflow: visible;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  -webkit-tap-highlight-color: transparent;
+  -webkit-touch-callout: none;
+  box-sizing: border-box;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content {
+  position: relative;
+  /* 内部的信息 */
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-content-bg {
+  display: block;
+  width: 100%;
+  min-width: 0 !important;
+  max-width: none !important;
+  height: 100%;
+  min-height: 0 !important;
+  max-height: none !important;
+  image-orientation: 0deg !important;
+  margin: 0 auto;
+  position: relative;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-content-bg .mask{
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.7);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: var(--box-bg);
+  z-index: 2;
+  outline: 2500rpx solid rgba(0, 0, 0, 0.7);
+}
+
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box {
+  position: relative;
+  display: block;
+  width: 100%;
+  height: 100%;
+  overflow: visible;
+  outline: 1px solid var(--primary-color-outline);
+  /* 横向虚线 */
+  /* 纵向虚线 */
+  /* 四个方向的线  为了之后的拖动事件*/
+  /* 右下 */
+  /* 右上 */
+  /* 左下 */
+  /* 左上 */
+}
+
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-cropper-view-box-img {
+  position: absolute;
+  overflow: hidden;
+}
+
+
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-dashed-h {
+  position: absolute;
+  top: 33.33333333%;
+  left: 0;
+  width: 100%;
+  height: 33.33333333%;
+  border-top: 1px dashed var(--primary-color-dashed);
+  border-bottom: 1px dashed var(--primary-color-dashed);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-dashed-v {
+  position: absolute;
+  left: 33.33333333%;
+  top: 0;
+  width: 33.33333333%;
+  height: 100%;
+  border-left: 1px dashed var(--primary-color-dashed);
+  border-right: 1px dashed var(--primary-color-dashed);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-line-t {
+  position: absolute;
+  display: block;
+  width: 100%;
+  background-color: var(--primary-color);
+  top: 0;
+  left: 0;
+  height: 1px;
+  opacity: 0.1;
+  cursor: n-resize;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-line-t::before {
+  content: '';
+  position: absolute;
+  top: 50%;
+  right: 0rpx;
+  width: 100%;
+  -webkit-transform: translate3d(0, -50%, 0);
+  transform: translate3d(0, -50%, 0);
+  bottom: 0;
+  height: 41rpx;
+  background: transparent;
+  z-index: 11;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-line-r {
+  position: absolute;
+  display: block;
+  background-color: var(--primary-color);
+  top: 0;
+  right: 0px;
+  width: 1px;
+  opacity: 0.1;
+  height: 100%;
+  cursor: e-resize;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-line-r::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 50%;
+  width: 41rpx;
+  -webkit-transform: translate3d(-50%, 0, 0);
+  transform: translate3d(-50%, 0, 0);
+  bottom: 0;
+  height: 100%;
+  background: transparent;
+  z-index: 11;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-line-b {
+  position: absolute;
+  display: block;
+  width: 100%;
+  background-color: var(--primary-color);
+  bottom: 0;
+  left: 0;
+  height: 1px;
+  opacity: 0.1;
+  cursor: s-resize;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-line-b::before {
+  content: '';
+  position: absolute;
+  top: 50%;
+  right: 0rpx;
+  width: 100%;
+  -webkit-transform: translate3d(0, -50%, 0);
+  transform: translate3d(0, -50%, 0);
+  bottom: 0;
+  height: 41rpx;
+  background: transparent;
+  z-index: 11;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-line-l {
+  position: absolute;
+  display: block;
+  background-color: var(--primary-color);
+  top: 0;
+  left: 0;
+  width: 1px;
+  opacity: 0.1;
+  height: 100%;
+  cursor: w-resize;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-line-l::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 50%;
+  width: 41rpx;
+  -webkit-transform: translate3d(-50%, 0, 0);
+  transform: translate3d(-50%, 0, 0);
+  bottom: 0;
+  height: 100%;
+  background: transparent;
+  z-index: 11;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .wx-cropper-point {
+  width: 5px;
+  height: 5px;
+  /* background-color: var(--primary-color); */
+  opacity: .75;
+  position: absolute;
+  z-index: 3;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-t {
+  top: -3px;
+  left: 50%;
+  margin-left: -3px;
+  cursor: n-resize;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-r {
+  top: 50%;
+  left: 100%;
+  margin-left: -3px;
+  margin-top: -3px;
+  cursor: n-resize;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-tr,
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-rb,
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-bl,
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-lt {
+  cursor: n-resize;
+  width: 29rpx;
+  height: 29rpx;
+  position: absolute;
+  z-index: 1112;
+  opacity: 1;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-rb {
+  right: 0;
+  bottom: 0;
+  -webkit-transform: translate3d(50%, 50%, 0);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-tr {
+  right: 0;
+  top: 0;
+  -webkit-transform: translate3d(50%, -50%, 0);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-bl {
+  left: 0;
+  bottom: 0;
+  -webkit-transform: translate3d(-50%, 50%, 0);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-lt {
+  top: 0;
+  left: 0;
+  -webkit-transform: translate3d(-50%, -50%, 0);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-rb::before,
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-rb::after,
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-tr::before,
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-tr::after,
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-bl::before,
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-bl::after,
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-lt::before,
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-lt::after {
+  content: '';
+  position: absolute;
+  background-color: var(--primary-color);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-rb::before {
+  width: 6rpx;
+  height: 30rpx;
+  right: calc(44%);
+  bottom: calc(44%);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-rb::after {
+  height: 6rpx;
+  width: 30rpx;
+  right: calc(44%);
+  bottom: calc(44%);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-tr::before {
+  width: 6rpx;
+  height: 30rpx;
+  right: calc(44%);
+  top: calc(44%);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-tr::after {
+  height: 6rpx;
+  width: 30rpx;
+  right: calc(44%);
+  top: calc(44%);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-bl::before {
+  width: 6rpx;
+  height: 30rpx;
+  left: calc(44%);
+  bottom: calc(44%);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-bl::after {
+  height: 6rpx;
+  width: 30rpx;
+  left: calc(44%);
+  bottom: calc(44%);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-lt::before {
+  width: 6rpx;
+  height: 30rpx;
+  left: calc(44%);
+  top: calc(44%);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-lt::after {
+  height: 6rpx;
+  width: 30rpx;
+  left: calc(44%);
+  top: calc(44%);
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-b {
+  left: 50%;
+  top: 100%;
+  margin-left: -3px;
+  margin-top: -3px;
+  cursor: n-resize;
+}
+.wx-content-info .cropper-content .wx-corpper .wx-corpper-content .wx-corpper-crop-box .wx-cropper-view-box .point-l {
+  left: 0%;
+  top: 50%;
+  margin-left: -3px;
+  margin-top: -3px;
+  cursor: n-resize;
+}
+.wx-content-info .cropper-config {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 80rpx;
+  z-index: 3;
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  border-top: 1px solid rgba(255, 255, 255, 0.12);
+}
+.wx-content-info .cropper-config .cropper-cancle,
+.wx-content-info .cropper-config .cropper-save {
+  color: #fff;
+  font-size: 26rpx;
+  padding: 15rpx 25px;
+  display: block;
+}
+/* 裁剪框预览内容 */
+.wx-cropper-viewer {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+.wx-cropper-viewer image {
+  position: absolute;
+  z-index: 2;
+}