This is the 8th day of my participation in Gwen Challenge

Micro channel small program development upload profile picture needs to be cut, otherwise the profile picture is too big, record their own use of picture cutting components package. Components are based on WE-Cropper.

Picture rotation and sizing cropping

It is mainly the upload operation of the background picture of the homepage

Component structure

wxml

<view class="container"> <! <view class="img" style="width:{{width}}px; height:{{height}}px; top:{{topHeight+120}}px" catchtouchstart="touchstartCallback" catchtouchmove="touchmoveCallback" catchtouchend="touchendCallback" > <image style="transform: translate({{stv.offsetX}}px, {{stv.offsetY}}px) scale({{stv.scale}}) rotate({{ stv.rotate }}deg); width:{{originImg.width}}px; height: {{originImg.height}}px" src="{{ originImg.url }}"></image> </view> <view class='footer'> <view Bindtap ='uploadTap'> </view> <view bindtap='rotate'> </view> <view bindtap='cropperImg'> </view> </view> </view> <! <canvas class='imgcrop' style="width:{{width * 2}}px; height:{{ height * 2}}px;" canvas-id='imgcrop'></canvas> </view>Copy the code

wxss

.container {
  width: 100%;
  height: 100%;
  background: #000;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 99;
}
.img {
   position: absolute;
   top: 5%;
   left: 50%;
   transform: translateX(-50%);
   overflow: hidden;
   background: #eee;
}
.img image {
  height:400px;
}
.imgcrop {
   position: absolute;
  left: -50000rpx;
  top: -500000rpx; 
}
.footer {
  position: absolute;
  width: 100%;
  height: 110rpx;
  color: #fff;
  background: #000;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-around;
}
.footer view {
  width: 30%;
  text-align: center;
}
.background {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  z-index: -1;
}
Copy the code

js

// 上传自己的头像以及个人页面的背景图裁剪组件
const app = getApp();
let topHeight = app.globalData.sysInfo.statusBarHeight + app.globalData.sysInfo.navHeight;
const device = wx.getSystemInfoSync();
var twoPoint = {
  x1: 0,
  y1: 0,
  x2: 0,
  y2: 0
};

Component({
  properties: {
    ratio: {
      type: Number,
      observer: function(newVal, oldVal) {
        this.setData({
          width: device.windowWidth,
          height: device.windowWidth * 0.47
        });
      }
    },
    url: {
      type: String,
      observer(newVal, oldVal) {
        this.initImg(newVal);
      }
    }
  },
  data: {
    topHeight: topHeight,
    width: device.windowWidth, //剪裁框的宽度
    height: device.windowWidth * 0.47, //剪裁框的长度
    originImg: null, //存放原图信息
    stv: {
      offsetX: 0, //剪裁图片左上角坐标x
      offsetY: 0, //剪裁图片左上角坐标y
      zoom: false, //是否缩放状态
      distance: 0, //两指距离
      scale: 1, //缩放倍数
      rotate: 0 //旋转角度
    }
  },
  methods: {
    uploadTap() {
      let _this = this;
      wx.chooseImage({
        count: 1, // 默认9
        sourceType: ["album", "camera"], // 可以指定来源是相册还是相机,默认二者都有
        success(res) {
          _this.initImg(res.tempFilePaths[0]);
        }
      });
    },
    rotate() {
      let _this = this;
      _this.setData({
        "stv.rotate":
          _this.data.stv.rotate % 90 == 0
            ? (_this.data.stv.rotate = _this.data.stv.rotate + 90)
            : (_this.data.stv.rotate = 0)
      });
    },
    // canvas剪裁图片
    cropperImg() {
      wx.showLoading({
        title: "loading",
        mask: true
      });
      let _this = this;
      let ctx = wx.createCanvasContext("imgcrop", this);
      let cropData = _this.data.stv;
      ctx.save();
      // 缩放偏移值
      let x =
        (_this.data.originImg.width -
          _this.data.originImg.width * cropData.scale) /
        2;
      let y =
        (_this.data.originImg.height -
          _this.data.originImg.height * cropData.scale) /
        2;

      //画布中点坐标转移到图片中心
      let movex =
        (cropData.offsetX + x) * 2 +
        _this.data.originImg.width * cropData.scale;
      let movey =
        (cropData.offsetY + y) * 2 +
        _this.data.originImg.height * cropData.scale;
      ctx.translate(movex, movey);
      ctx.rotate((cropData.rotate * Math.PI) / 180);
      ctx.translate(-movex, -movey);

      ctx.drawImage(
        _this.data.originImg.url,
        (cropData.offsetX + x) * 2,
        (cropData.offsetY + y) * 2,
        _this.data.originImg.width * 2 * cropData.scale,
        _this.data.originImg.height * 2 * cropData.scale
      );
      ctx.restore();
      ctx.draw(false, () => {
        wx.canvasToTempFilePath(
          {
            canvasId: "imgcrop",
            success(response) {
              _this.triggerEvent("getCropperImg", {
                url: response.tempFilePath
              });
              wx.hideLoading();
            },
            fail(e) {
              wx.hideLoading();
              wx.showToast({
                title: "生成图片失败",
                icon: "none"
              });
            }
          },
          this
        );
      });
    },

    initImg(url) {
      let _this = this;
      wx.getImageInfo({
        src: url,
        success(resopne) {
          let innerAspectRadio = resopne.width / resopne.height;

          if (innerAspectRadio < _this.data.width / _this.data.height) {
            _this.setData({
              originImg: {
                url: url,
                width: _this.data.width,
                height: _this.data.width / innerAspectRadio
              },
              stv: {
                offsetX: 0,
                offsetY:
                  0 -
                  Math.abs(
                    (_this.data.height - _this.data.width / innerAspectRadio) /
                      2
                  ),
                zoom: false, //是否缩放状态
                distance: 0, //两指距离
                scale: 1, //缩放倍数
                rotate: 0
              }
            });
          } else {
            _this.setData({
              originImg: {
                url: url,
                height: _this.data.height,
                width: _this.data.height * innerAspectRadio
              },
              stv: {
                offsetX:
                  0 -
                  Math.abs(
                    (_this.data.width - _this.data.height * innerAspectRadio) /
                      2
                  ),
                offsetY: 0,
                zoom: false, //是否缩放状态
                distance: 0, //两指距离
                scale: 1, //缩放倍数
                rotate: 0
              }
            });
          }
        }
      });
    },
    //事件处理函数
    touchstartCallback: function(e) {
      if (e.touches.length === 1) {
        let { clientX, clientY } = e.touches[0];
        this.startX = clientX;
        this.startY = clientY;
        this.touchStartEvent = e.touches;
      } else {
        let xMove = e.touches[1].clientX - e.touches[0].clientX;
        let yMove = e.touches[1].clientY - e.touches[0].clientY;
        let distance = Math.sqrt(xMove * xMove + yMove * yMove);
        twoPoint.x1 = e.touches[0].pageX * 2;
        twoPoint.y1 = e.touches[0].pageY * 2;
        twoPoint.x2 = e.touches[1].pageX * 2;
        twoPoint.y2 = e.touches[1].pageY * 2;
        this.setData({
          "stv.distance": distance,
          "stv.zoom": true //缩放状态
        });
      }
    },
    //图片手势动态缩放
    touchmoveCallback: function(e) {
      let _this = this;
      fn(_this, e);
    },
    touchendCallback: function(e) {
      //触摸结束
      if (e.touches.length === 0) {
        this.setData({
          "stv.zoom": false //重置缩放状态
        });
      }
    }
  }
});

/**
 * fn:延时调用函数
 * delay:延迟多长时间
 * mustRun:至少多长时间触发一次
 */
var throttle = function(fn, delay, mustRun) {
  var timer = null,
    previous = null;

  return function() {
    var now = +new Date(),
      context = this,
      args = arguments;
    if (!previous) previous = now;
    var remaining = now - previous;
    if (mustRun && remaining >= mustRun) {
      fn.apply(context, args);
      previous = now;
    } else {
      clearTimeout(timer);
      timer = setTimeout(function() {
        fn.apply(context, args);
      }, delay);
    }
  };
};

var touchMove = function(_this, e) {
  //触摸移动中
  if (e.touches.length === 1) {
    //单指移动
    if (_this.data.stv.zoom) {
      //缩放状态,不处理单指
      return;
    }
    let { clientX, clientY } = e.touches[0];
    let offsetX = clientX - _this.startX;
    let offsetY = clientY - _this.startY;
    _this.startX = clientX;
    _this.startY = clientY;
    let { stv } = _this.data;
    stv.offsetX += offsetX;
    stv.offsetY += offsetY;
    stv.offsetLeftX = -stv.offsetX;
    stv.offsetLeftY = -stv.offsetLeftY;
    _this.setData({
      stv: stv
    });
  } else if (e.touches.length === 2) {
    //计算旋转
    let preTwoPoint = JSON.parse(JSON.stringify(twoPoint));
    twoPoint.x1 = e.touches[0].pageX * 2;
    twoPoint.y1 = e.touches[0].pageY * 2;
    twoPoint.x2 = e.touches[1].pageX * 2;

    function vector(x1, y1, x2, y2) {
      this.x = x2 - x1;
      this.y = y2 - y1;
    }

    //计算点乘
    function calculateVM(vector1, vector2) {
      return (
        (vector1.x * vector2.x + vector1.y * vector2.y) /
        (Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) *
          Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y))
      );
    }
    //计算叉乘
    function calculateVC(vector1, vector2) {
      return vector1.x * vector2.y - vector2.x * vector1.y > 0 ? 1 : -1;
    }

    let vector1 = new vector(
      preTwoPoint.x1,
      preTwoPoint.y1,
      preTwoPoint.x2,
      preTwoPoint.y2
    );
    let vector2 = new vector(
      twoPoint.x1,
      twoPoint.y1,
      twoPoint.x2,
      twoPoint.y2
    );
    let cos = calculateVM(vector1, vector2);
    let angle = (Math.acos(cos) * 180) / Math.PI;

    let direction = calculateVC(vector1, vector2);
    let _allDeg = direction * angle;

    // 双指缩放
    let xMove = e.touches[1].clientX - e.touches[0].clientX;
    let yMove = e.touches[1].clientY - e.touches[0].clientY;
    let distance = Math.sqrt(xMove * xMove + yMove * yMove);

    let distanceDiff = distance - _this.data.stv.distance;
    let newScale = _this.data.stv.scale + 0.005 * distanceDiff;

    if (Math.abs(_allDeg) > 1) {
      _this.setData({
        "stv.rotate": _this.data.stv.rotate + _allDeg
      });
    } else {
      //双指缩放
      let xMove = e.touches[1].clientX - e.touches[0].clientX;
      let yMove = e.touches[1].clientY - e.touches[0].clientY;
      let distance = Math.sqrt(xMove * xMove + yMove * yMove);

      let distanceDiff = distance - _this.data.stv.distance;
      let newScale = _this.data.stv.scale + 0.005 * distanceDiff;
      if (newScale < 0.2 || newScale > 2.5) {
        return;
      }
      _this.setData({
        "stv.distance": distance,
        "stv.scale": newScale
      });
    }
  } else {
    return;
  }
};

//为touchMove函数节流
const fn = throttle(touchMove, 10, 10);
Copy the code

Components use

<cropper bind:getCropperImg="_getCropperImg" url="{{ originUrl }}" ratio="{{ratio}}"></cropper>
Copy the code

Upload cropped images

_getCropperImg(e) { var coverPath = e.detail.url; // Upload function}Copy the code

User profile picture cropping

wxml

<view class="cropper-wrapper" catchtap='clickReturn'> <canvas class="cropper" disable-scroll="true" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd" style="width:{{cropperOpt.width}}px; height:{{cropperOpt.height}}px; background-color: rgba(0, 0, 0, 0.8)" canvas-id="{{cropperopt. id}}"> </canvas> <view class="cropper-buttons"> <view class="left" </view> </view> catchtap=" catchtap "</view> </view>Copy the code

wxss

.cropper-buttons view {
	line-height: 50px;
	display: inline-block;
	width: 50%;
	background-color: #000;
	color: #fff;
	font-size: 18px;
}

.left {
	text-align: left;
	padding-left: 40rpx;
}

.right {
	text-align: right;
	padding-right: 40rpx;
}
.cropper-buttons{
  display: flex;
}
Copy the code

js

import WeCropper from '.. /.. /utils/we-cropper.js'; const device = wx.getSystemInfoSync(); const width = device.windowWidth; const height = device.windowHeight - 50; var app = getApp(); var cropper = null; Page({ data: { cropperOpt: { id: 'cropper', width, height, scale: 1, zoom: 5, cut: { x: 15, y: (height - 300) / 2, width: (width - 30), height: (width - 30) }, option: null }, w: 0, h: 0, showCanvas: false }, touchStart(e) { this.wecropper.touchStart(e) }, touchMove(e) { this.wecropper.touchMove(e) }, touchEnd(e) { this.wecropper.touchEnd(e) }, cancel: function() { wx.navigateBack({ delta: 1 }) }, getCropperImage() { var option = this.data.option; this.wecropper.getCropperImage((src) => { var pages = getCurrentPages(); var prePage = pages[pages.length - 2]; if (option.avatar) { prePage.setAvatar(src); } else if (option.cover) { prePage.setCover(src); } setTimeout(() => { wx.navigateBack({ delta: 1 }) }, 200) }) }, onLoad: function(options) { var self = this; self.setData({ showCanvas: true }) self.setData({ option: options }); const { cropperOpt } = this.data; if (options.cover) { cropperOpt.cut.height = cropperOpt.cut.width } else if (options.avatar || options.isSetRole) { cropperOpt.cut.height = cropperOpt.cut.width; } else if (options.bgImg) { cropperOpt.cut.y = height * 1 / 10; cropperOpt.cut.x = 15; cropperOpt.cut.width = 345; cropperOpt.cut.height = 614; } else { cropperOpt.cut.y = 50; } var src = options.src; cropper = new WeCropper(cropperOpt) .on('ready', (ctx) => {}) .on('beforeImageLoad', (ctx) => { wx.showToast({ title: 'loading', icon: 'loading', duration: 20000 }) }) .on('imageLoad', (ctx) => { wx.hideToast() }) .on('beforeDraw', (ctx, instance) => {}) .updateCanvas(); self.wecropper.pushOrign(src); }, upload: function() { var self = this; wx.chooseImage({ count: 1, sizeType: ['original'], sourceType: ['album', 'camera'], success: function(res) { var tempFilePath = self.checkPhoto(res.tempFilePaths[0]); var option = self.data.option; option.src = tempFilePath; self.onLoad(option); } }) }, checkPhoto(tempFilePath) { var type = ""; if (tempFilePath.length) { type = tempFilePath.match(/^(.*)(\.) (. {8} 1) $/) [3]. type = type.toUpperCase(); } if (type ! = "JPEG" && type ! = "PNG" && type ! = "JPG" && type ! = "GIF") {wx.showtoast ({title: 'filter special image format ', icon: "none", duration: 2000}) return false; } else { return tempFilePath; }}})Copy the code