微信小程序 canvas 图片裁剪 旋转 插件we-cropper

微信小程序 canvas 图片裁剪 旋转 插件we-cropper

比较推荐插件: https://github.com/wx-plugin/image-cropper

另一个插件
git:https://github.com/we-plugin/we-cropper
文档: https://we-plugin.github.io/we-cropper/#/

此插件只提供裁剪等功能,所以我在此插件上修改了一部分代码,添加了图片旋转功能,和图片放大功能

图片裁剪

旋转后的图片

下载解压:
文件目录


文件目录

如果在所需要裁剪的页面通过弹出窗口来使用这个功能,ios真机上面遇到一个问题:
第一次进入页面,页面无法上拉,原因查不出来,所以只好将裁剪的功能写在一个新页面中(而且本小程序需要裁剪的页面很多,所以将裁剪写成一个新的页面比较好)

pages里面新建目录cutFace
在index.wxml中代码如下:

// 引入裁剪组件
<import src="/commpents/we-cropper/we-cropper.wxml"/>  



<view class='cut-img-page'>
  <view class='cropper-wrapper-bg'>
    <view style='height: 1rpx;'></view>
    <view class="cropper-wrapper" style='margin-top: {{marTop}}px'>
      <template is="we-cropper" data="{{...cropperOpt}}"/>
      <view class="getCropperImage">
        <view bindtap='cancleCropper'>取消</view><view bindtap='rotateImg'><image src='/images/rotate.png' class='rotate'></image></view><view  bindtap="getCropperImage">确定</view>
      </view>
    </view>
  </view>
</view>

在index.wxss中代码如下:

page {
  min-height: auto;
  height: auto;
}

.cropper-wrapper {
  background: #fff;
}

.getCropperImage {
  text-align: center;
  font-size: 32rpx;
  display: flex;
  justify-content: space-around;
  position: absolute;
  bottom: 0;
  width: 750rpx;
  padding: 40 0rpx;
  background: #000;
  z-index: 9999;
  color: #fff;
  align-items: center;
}

.getCropperImage > view {
  padding: 0 40rpx;
  height: 60px;
  line-height: 60px;
}

.cropper-wrapper-bg {
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  background: rgb(0, 0, 0);
  z-index: 22;
}

.rotate {
  width: 44rpx;
  height: 44rpx;
}

在index.js中代码如下:

// pages/cutFace/index.js

// pages/cutFace/index.js

const WeCropper = require('../../we-cropper/we-cropper.js')

const device = wx.getSystemInfoSync() // 获取设备信息
const width = device.windowWidth // 示例为一个与屏幕等宽的正方形裁剪框
const system = device.system;
let height = device.windowHeight - 100




Page({

  /**
   * 页面的初始数据
   */
  data: {
    rotateI: 0, //旋转默认角度
    cropperOpt: {
      id: 'cropper',
      rotateI: 0,//旋转默认角度
      tranlateX: width / 2,    //定义canvas 的原点
      tranlateY: height / 2,  //定义canvas 的原点
      width,  // 画布宽度
      height, // 画布高度
      scale: 2.5, // 最大缩放倍数
      zoom: 8, // 缩放系数,
      cut: {
        x: -width / 2,  // 裁剪框的坐标
        y: -(height - (width / 1.4)) / 2, // 裁剪框的坐标
        width: width, //裁剪框的大小
        height: width / 1.4
      }
    },
    chooseImg: false,
    imgSrc: '',
    marTop: 40
  },


  onLoad: function (options) {
    const self = this;
    //兼容可不写
    const system = device.system;
    if (system.indexOf('iOS') != -1) {
      this.setData({
        ios: true
      })
    };
    if (system.indexOf('iOS') != -1) {
      this.setData({
        marTop: 45
      })
    } else {
      this.setData({
        marTop: 45
      })
    };
    if (device.model.indexOf("iPhone X") != -1) {
      this.setData({
        height: wx.getStorageSync('height') * 2 + 50,
        marTop: 80
      })
    };
    // 判断来自哪个图片的裁剪  身份证、荣誉证书、营业证书等
    this.setData({
      cuttype: options.cuttype
    })
    //裁剪 插件配置
    const { cropperOpt } = this.data;
    new WeCropper(cropperOpt)
      .on('ready', (ctx) => {
        self.wecropper.updateCanvas(this.data.rotateI)
      })
      .on('beforeImageLoad', (ctx) => {
        wx.showToast({
          title: '上传中',
          icon: 'loading',
          duration: 20000
        })
      })
      .on('imageLoad', (ctx) => {
        wx.hideToast()
      })
    this.chooseImg()

  },

  chooseImg() {
    const self = this;
    wx.chooseImage({
      count: 1, // 默认9
      sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
      sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
      success(res) {
        const src = res.tempFilePaths[0]
        if (src) {
        // 将图片参数传递给插件
          self.wecropper.pushOrign(src)
          self.setData({
            chooseImg: true,
            imgSrc: src,
            rotateI: 0
          })
        };
        wx.hideToast()
      },
      fail(res) {
        wx.hideToast();
        wx.navigateBack()
      }
    })
  },

  touchStart(e) {
    this.wecropper.touchStart(e)
  },
  touchMove(e) {
    this.wecropper.touchMove(e)
  },
  touchEnd(e) {
    this.wecropper.touchEnd(e)
  },
  // 获取裁剪后的图片
  getCropperImage() {
    let that = this;
    if (this.data.chooseImg) {
      this.wecropper.getCropperImage((src) => {
        //获取上个页面的参数
        let pages = getCurrentPages();
        //prevPage 相当于上个页面的this,可以通过setData修改上个页面参数执行上个页面的方法等
        let prevPage = pages[pages.length - 2]
        if (src) {
          // 身份证
          if (this.data.cuttype == 1) {
            prevPage.setData({
              IDCard: src,
              cuttype: this.data.cuttype
            })
          //荣誉证书
          } else if (this.data.cuttype == 2) {
            prevPage.setData({
              hornerCard: src,
              cuttype: this.data.cuttype
            })
          } else if (this.data.cuttype == 3) {
            prevPage.setData({
              licenceCard: src,
              cuttype: this.data.cuttype
            })
          } else if (this.data.cuttype == 4) {
            prevPage.setData({
              certificationCard: src,
              cuttype: this.data.cuttype
            })
          };
          wx.navigateBack()
        } else {
          wx.hideToast()
          wx.showToast({
            title: '获取图片地址失败,请稍后再试!',
          })
        }
      })
    } else {
      wx.showToast({
        title: '您还没选择图片!',
        icon: 'none'
      })
    }
  },
  cancleCropper() {
    wx.hideToast()
    wx.navigateBack()
  },

  // 图片旋转
  rotateImg() {
    const self = this;
    let rotateI = this.data.rotateI + 1;
    this.setData({
      rotateI: rotateI
    })
    // 将旋转的角度传递给插件
    self.wecropper.updateCanvas(rotateI)
  }
})

上个页面(进入裁剪的那个页面)处理


image.png

在上个页面的数据处理
在data里面声明:

data: {
    IDCard: '', //身份证
    cuttype: null, // 图片类型
    licenceCard: '',// 机构证书
    certificationCard: ''//资质证书
}

点击跳转到裁剪页面时所传递的参数

//证书
getLicence() {
    wx.navigateTo({
      url: '/pages/cutFace/index?cuttype=3',
    })
  },
  //证书
  getCertification() {
    wx.navigateTo({
      url: '/pages/cutFace/index?cuttype=4',
    })
  },
  // 身份证
  IDCardFont() {
    wx.navigateTo({
      url: '/pages/cutFace/index?cuttype=1',
    })
  },

从裁剪页面退回时的判断

onShow() {
    if (this.data.cuttype == 1) {
      this.upLoadImg(this.data.IDCard)
    };
    if (this.data.cuttype == 3) {
      this.upLoadImg(this.data.licenceCard)
    };
    if (this.data.cuttype == 4) {
      this.upLoadImg(this.data.certificationCard)
    };
  },
//图片上传
upLoadImg(path) {
    let that = this;
    wx.uploadFile({
      url: wx.$.host + 'api/uploadImage',
      filePath: path,
      name: 'files',
      success: function (res) {
        if (res.data) {
          const lecturerInfo = that.data.lecturerInfo
          let data = JSON.parse(res.data);
          if (that.data.cuttype == 1) {
            lecturerInfo.hand_card_one = data.path
          };
          if (that.data.cuttype == 3) {
            lecturerInfo.licence_pic = data.path
          };
          if (that.data.cuttype == 4) {
            lecturerInfo.certification_pic = data.path
          };
          app.data.lecturerInfo.data.lecturer = lecturerInfo
          that.setData({
            lecturerInfo: lecturerInfo,
            cuttype: null//上传成功后设置为空
          })
        }
      }
    })
  },

下面是插件源码的修改(能力有限,修改之后勉强能用吧,反正自己是不怎么满意)
在插件的method函数中修改

function methods() {
    var self = this;

    var id = self.id;
    var deviceRadio = self.deviceRadio;
    var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
    var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度
    var ref = self.cut;
    var x = ref.x; if (x === void 0) x = 0;
    var y = ref.y; if (y === void 0) y = 0;
    var width = ref.width; if (width === void 0) width = boundWidth;
    var height = ref.height; if (height === void 0) height = boundHeight;

    self.updateCanvas = function (rotateI) {
      self.rotateI = rotateI ? rotateI : self.rotateI
      if (self.croperTarget) {
        //  画布绘制图片
        self.ctx.save()
        self.ctx.translate(self.tranlateX, self.tranlateY)
        self.ctx.rotate(self.rotateI * 90 * Math.PI / 180)
        self.ctx.drawImage(self.croperTarget, self.imgLeft, self.imgTop, self.scaleWidth, self.scaleHeight);
      }
      isFunction(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self);
      self.ctx.restore()
      self.setBoundStyle(); //  设置边界样式
      self.ctx.draw();
      return self
    };

    self.pushOrign = function (src) {
      self.rotateI = 0
      self.src = src;

      isFunction(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self);

      wx.getImageInfo({
        src: src,
        success: function success(res) {
          var innerAspectRadio = res.width / res.height;

          self.croperTarget = res.path;

          if (innerAspectRadio < width / height) {
            self.rectX = x;
            self.baseWidth = width;
            self.baseHeight = width / innerAspectRadio;
            self.rectY = y - Math.abs((height - self.baseHeight) / 2);
          } else {
            self.rectY = y;
            self.baseWidth = height * innerAspectRadio;
            self.baseHeight = height;
            self.rectX = x - Math.abs((width - self.baseWidth) / 2);
          }

          self.imgLeft = self.rectX;
          self.imgTop = self.rectY;
          self.scaleWidth = self.baseWidth;
          self.scaleHeight = self.baseHeight;

          self.updateCanvas();

          isFunction(self.onImageLoad) && self.onImageLoad(self.ctx, self);
        }
      });

      self.update();
      return self
    };



    self.getCropperImage = function () {
      var args = [], len = arguments.length;
      while (len--) args[len] = arguments[len];

      var ARG_TYPE = toString.call(args[0]);
      var fn = args[args.length - 1];

      switch (ARG_TYPE) {
        case '[object Object]':
          var ref = args[0];
          var quality = ref.quality; if (quality === void 0) quality = 10;

          if (typeof (quality) !== 'number') {
            console.error(("quality:" + quality + " is invalid"));
          } else if (quality < 0 || quality > 10) {
            console.error("quality should be ranged in 0 ~ 10");
          }
          wx.canvasToTempFilePath({
            canvasId: id,
            x: x,
            y:  -y,
            width: width,
            height: height,
            destWidth: width * quality / (deviceRadio * 10),
            destHeight: height * quality / (deviceRadio * 10),
            success: function success(res) {
              isFunction(fn) && fn.call(self, res.tempFilePath);
            },
            fail: function fail(res) {
              isFunction(fn) && fn.call(self, null);
            }
          }); break
        case '[object Function]':
          wx.canvasToTempFilePath({
            canvasId: id,
            x: x,
            y: -y,
            width: width,
            height: height,
            destWidth: width / deviceRadio,
            destHeight: height / deviceRadio,
            success: function success(res) {
              isFunction(fn) && fn.call(self, res.tempFilePath);
            },
            fail: function fail(res) {
              isFunction(fn) && fn.call(self, null);
            }
          }); break
      }

      return self
    };
  }

对照图


image.png

image.png

update 函数


function update() {
    var self = this;

    if (!self.src) { return }

    self.__oneTouchStart = function (touch) {
      self.touchX0 = Math.round(touch.x);
      self.touchY0 = Math.round(touch.y);
    };

    self.__oneTouchMove = function (touch) {
      var xMove, yMove;
      // 计算单指移动的距离
      if (self.touchended) {
        return self.updateCanvas()
      }
      xMove = Math.round(touch.x - self.touchX0);
      yMove = Math.round(touch.y - self.touchY0);
      if (self.rotateI % 4 == 0) {
        var imgLeft = Math.round(self.rectX + xMove);
        var imgTop = Math.round(self.rectY + yMove);
      } else if (self.rotateI % 4 == 1) {
        var imgLeft = Math.round(self.rectX + yMove);
        var imgTop = Math.round(self.rectY - xMove);
      } else if (self.rotateI % 4 == 2) {
        var imgLeft = Math.round(self.rectX - xMove);
        var imgTop = Math.round(self.rectY - yMove);
      } else if (self.rotateI % 4 == 3) {
        var imgLeft = Math.round(self.rectX - yMove);
        var imgTop = Math.round(self.rectY + xMove);
      }
      self.outsideBound(imgLeft, imgTop);
      self.updateCanvas();
    };

对照图


image.png

outsideBound函数

self.outsideBound = function (imgLeft, imgTop) {

      self.imgLeft = imgLeft
      self.imgTop = imgTop



      // self.imgLeft = imgLeft >= x
      //   ? x
      //   : self.scaleWidth + imgLeft - x <= width
      //     ? x + width - self.scaleWidth
      //     :  imgLeft;

      // self.imgTop = imgTop >= y
      //   ? y
      //   : self.scaleHeight + imgTop - y <= height
      //     ? y + height - self.scaleHeight
      //     : imgTop;
      // console.log(imgLeft)
    };

对照图


image.png

__twoTouchMove函数,原插件只提供了图片放大,没有图片缩小功能,这里小改了一下

self.__twoTouchMove = function (touch0, touch1) {
      var oldScale = self.oldScale;
      var oldDistance = self.oldDistance;
      var scale = self.scale;
      var zoom = self.zoom;

      self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1);

      //  设定缩放范围
      self.newScale <= 0.2 && (self.newScale = oldScale - 0.001 * zoom * (newDistance - oldDistance));
      self.newScale >= scale && (self.newScale = scale);

      self.scaleWidth = Math.round(self.newScale * self.baseWidth);
      self.scaleHeight = Math.round(self.newScale * self.baseHeight);
      var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2);
      var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2);

      self.outsideBound(imgLeft, imgTop);

      self.updateCanvas();
    };

对照图


image.png

选择图片不居中时需要微调,微调地方


image.png

最后附上demo的git地址: https://github.com/UprightCedar/we-cropper-demo-cut-img

有更好的方法请告诉我

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容