小程序利用canvas实现一键保存图片功能(合并图片,双指缩放,旋转,默认裁剪画布大小的尺寸)

时间:2024-04-13 07:36:13

**css **

.container {
  position: relative;
  width: 100%;
  height: 100%;
  background: #000;
}
.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: fixed;
  width: 100%;
  height: 110rpx;
  color: red;
  background: #000;
  bottom: 1;
  display: flex;
  align-items: center;
  justify-content: space-around;
  margin-top: 130%;
}
.footer view {
  width: 30%;
  text-align: center;
}
.background {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  z-index: -1;
}

HTML

<view class="container">
  <!--  剪裁框与初始图片,剪裁框监听用户手势,获取移动缩放旋转值,images通过css样式显示变化  -->
  <view class="img" style="width:{{ width }}px; height:{{height}}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>
  
  <!--  canvas长宽设为初始图片设置的长款的两倍,使剪裁得到的图片更清晰,也不至于过大  -->
  <canvas class='imgcrop' style="width:{{canvasWidth}}px; height: {{canvasHeight}}px" canvas-id='imgcrop'></canvas>
</view>

JS
注:77.png或者66.png更改成自己的图片URL,如果只需要一张图片去掉77.png或者66.png这段代码

const device = wx.getSystemInfoSync();
const app = getApp();
var mun = -1;
var initImgPaht='';
var widthRadio = '';
var heightRadio = '';
var twoPoint = {
  x1: 0,
  y1: 0,
  x2: 0,
  y2: 0
}

Component({
  /**
   * 组件的属性列表
   */
  properties: {
    ratio: {
      type: Number,
      observer: function (newVal, oldVal) {
        this.setData({
          width: device.windowWidth * 0.8,
          height: device.windowWidth * 0.8 / newVal
        })
      }
    },
    url: {
      type: String,
      observer ( newVal, oldVal ) {
        this.initImg( newVal )
      }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    width: device.windowWidth * 0.8,                //剪裁框的宽度
    height: device.windowWidth * 0.8 / (102 / 152), //剪裁框的长度
    originImg: null,                                //存放原图信息
    canvasWidth: device.windowWidth,
    canvasHeight: device.windowWidth,
    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
        sizeType: ['original'], // 可以指定是原图还是压缩图,默认二者都有
        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
      })
      if (_this.data.stv.rotate == 360) {
        _this.data.stv.rotate = 0;
      }
    },
    // canvas剪裁图片
    cropperImg() {
      wx.showLoading({
        title: 'loading',
        mask: true
      })
      let _this = this;
      let ctx = wx.createCanvasContext('imgcrop',this);//创建画布
      //原始图片
      var imgWidth 
      var imgHeight
      //放大缩小后的图片
      var zoomWidth
      var zoomHeight
      //等比例图片
      var originalWidth
      var originalHeight
      var proportion//放大缩小倍数
      var str//临时值
      var zoomJudge = true;

      if (_this.data.originImg.judge) {//宽度大于高度图片
        imgWidth = _this.data.height * heightRadio;
        imgHeight = _this.data.width * widthRadio;
      } else {//高度大于宽度图片
        imgWidth = _this.data.width * widthRadio;
        imgHeight = _this.data.height * heightRadio;
      }
      //保存六寸图片默认1440分辨率,进行等比例缩放,默认刚刚大于1440分辨率
      if (imgHeight > 1440) {//缩小
        for (var i = 1; i < 10; i = i + 0.1) {
          str = imgHeight / i;
          if (str < 1440) {
            proportion = i + 0.1;
            break;
          }
        }
      } else {//放大
        for (var i = 1; i < 10; i = i + 0.1) {
          str = imgHeight * i;
          if (str > 1440) {
            proportion = i;
            zoomJudge = false;
            break;
          }
        }
      }

      //等比例缩放高度宽度(任务需求只有六寸图片),宽度图片长宽相互替换避免画布大小变化(裁剪画布大小图片)
      if (zoomJudge){
        originalWidth = imgWidth / proportion;
        originalHeight = imgHeight / proportion;
      }else {
        originalWidth = imgWidth * proportion;
        originalHeight = imgHeight * proportion;
      }
      //画布高宽赋值
      _this.setData({
        canvasWidth: originalWidth,
        canvasHeight: originalHeight
      })

      //获取图片赋值信息
      let cropData = _this.data.stv;
      ctx.save();// 保存当前的绘图状态

      //放大缩小后的图片(双指缩放)
      zoomWidth = originalWidth * cropData.scale;
      zoomHeight = originalHeight * cropData.scale;
      //等待处理
      console.info(cropData.rotate);
      
      //以画布原点为中心
      if (_this.data.originImg.judge) {//由于旋转高宽位置替换
        if (cropData.rotate == 90){//默认旋转90
          ctx.translate(originalWidth + (zoomWidth - originalWidth) / 2, -(zoomHeight - originalHeight) / 2);
          ctx.rotate(cropData.rotate * Math.PI / 180);
          ctx.drawImage(_this.data.originImg.url, 0, 0, zoomHeight, zoomWidth);
          ctx.drawImage('66.png', (zoomHeight - originalHeight) / 2, (zoomWidth - originalWidth) / 2, originalHeight, originalWidth);
        } else if (cropData.rotate == 180) {
          ctx.translate(zoomHeight + (originalWidth - zoomHeight) / 2, zoomWidth + (originalHeight - zoomWidth) / 2);
          ctx.rotate(cropData.rotate * Math.PI / 180);
          ctx.drawImage(_this.data.originImg.url, 0, 0, zoomHeight, zoomWidth);
          ctx.drawImage('66.png', -(originalWidth - zoomHeight) / 2, -(originalHeight - zoomWidth) / 2, originalWidth, originalHeight);
        } else if (cropData.rotate == 270) {
          ctx.translate(-(zoomWidth - originalWidth) / 2, originalHeight + (zoomHeight - originalHeight) / 2);
          ctx.rotate(cropData.rotate * Math.PI / 180);
          ctx.drawImage(_this.data.originImg.url, 0, 0, zoomHeight, zoomWidth);
          ctx.drawImage('66.png', (zoomHeight - originalHeight) / 2, (zoomWidth - originalWidth) / 2, originalHeight, originalWidth);
        } else {//没有选择的图片
          ctx.translate((originalWidth - zoomHeight) / 2, (originalHeight - zoomWidth) / 2);
          ctx.rotate(cropData.rotate * Math.PI / 180);
          ctx.drawImage(_this.data.originImg.url, 0, 0, zoomHeight, zoomWidth);
          ctx.drawImage('66.png', (originalWidth + zoomHeight) / 2 - originalWidth, (zoomWidth + originalHeight) / 2 - originalHeight, originalWidth, originalHeight);
        }
      } else {//正常图片
        if (cropData.rotate == 0) {
          ctx.translate((originalWidth - zoomWidth) / 2, (originalHeight - zoomHeight) /2);
          ctx.rotate(cropData.rotate * Math.PI / 180);
          ctx.drawImage(_this.data.originImg.url, 0, 0, zoomWidth, zoomHeight);
          ctx.drawImage('77.png', (zoomWidth - originalWidth) / 2, (zoomHeight -originalHeight) / 2, originalWidth, originalHeight);
        } else if (cropData.rotate == 90){
          ctx.translate((originalWidth + zoomHeight)/2, (originalHeight - zoomWidth )/2);
          ctx.rotate(cropData.rotate * Math.PI / 180);
          ctx.drawImage(_this.data.originImg.url, 0, 0, zoomWidth, zoomHeight);
          ctx.drawImage('77.png', -(originalHeight - zoomWidth) / 2, -(originalWidth - zoomHeight) / 2, originalHeight, originalWidth);
        } else if (cropData.rotate == 180){
          ctx.translate(originalWidth - (originalWidth - zoomWidth) / 2, originalHeight - (originalHeight - zoomHeight) / 2);
          ctx.rotate(cropData.rotate * Math.PI / 180);
          ctx.drawImage(_this.data.originImg.url, 0, 0, zoomWidth, zoomHeight);
          ctx.drawImage('77.png', -(originalWidth - zoomWidth) / 2, -(originalHeight - zoomHeight) / 2, originalWidth, originalHeight);
        } else if(cropData.rotate == 270) {
          ctx.translate((originalWidth - zoomHeight) / 2, (originalHeight + zoomWidth) / 2);
          ctx.rotate(cropData.rotate * Math.PI / 180);
          ctx.drawImage(_this.data.originImg.url, 0, 0, zoomWidth, zoomHeight);
          ctx.drawImage('77.png', -(originalHeight - zoomWidth) / 2, -(originalWidth - zoomHeight) / 2, originalHeight, originalWidth);
        }
      }
     
      ctx.restore();//恢复之前保存的绘图状态。
      wx.showToast({
        title: '图片正在生成中.....',
        icon: 'loading',
        duration: 2200
      });
      ctx.draw(false, () => {//延迟2.2秒,防止画布加载过快无法正常裁剪。异步加载
        setTimeout(() => {
          wx.canvasToTempFilePath({
            canvasId: 'imgcrop',
            success(response) {
              mun++;
              console.log(response.tempFilePath);
              var list = {
                url: response.tempFilePath,
                id: mun
              }
              _this.triggerEvent("getCropperImg", { list: list })
              wx.hideLoading();

              wx.saveImageToPhotosAlbum({
                filePath: response.tempFilePath,
                success(res) {
                  wx.showModal({//保存相册,不需要保存相册的上传服务器自己写
                    content: '图片已保存到相册了',
                    showCancel: false,
                    confirmText: '知道啦',
                    confirmColor: '#72B9C3',
                    success: function (res) {
                      if (res.confirm) {
                        console.log('用户点击确定');
                      }
                    }
                  })
                }
              })
            },
            fail(e) {
              console.log(e);
              wx.hideLoading();
              wx.showToast({
                title: '生成图片失败',
                icon: 'none'
              })
            }
          }, this)
        }, 2200);
      });
    },
    //选择相片后初始化图片信息
    initImg(url) {
      let _this = this;
      wx.getSystemInfo({//获取手机信息
        success(res) {
          _this.setData({//默认设置手机屏幕百分之八十
            width: res.windowWidth * 0.8,
            height: res.windowHeight * 0.8
          })
        }
      })
      wx.getImageInfo({//图片信息
        src: url,
        success(resopne) {
          var imgWidth = resopne.width;
          var imgHeight = resopne.height;
          //根据屏幕缩小图片
          widthRadio = imgWidth / _this.data.width;
          heightRadio = imgHeight / _this.data.height;
          if (imgWidth > imgHeight) {//宽大于高 图片处理
            _this.setData({//初始化测试图片信息
              originImg: {
                url: url,
                width: imgHeight / heightRadio,
                height: imgWidth / widthRadio ,
                judge:true//判断图片类型,
              },
              stv: {//初始化画布信息,旋转90度后 X,Y位置发生变化
                offsetX: -70 - widthRadio,
                offsetY: 70 + widthRadio,
                zoom: false, //是否缩放状态
                distance: 0,  //两指距离
                scale: 1,  //缩放倍数
                rotate: 90 //旋转角度
              }
            })
          } else {
            _this.setData({
              originImg: {
                url: url,
                width: imgWidth / widthRadio,
                height: imgHeight / heightRadio,
                judge:false
              },
              stv: {
                offsetX: 0,
                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;
    }
  } else if (e.touches.length === 2) {
      //双指缩放
      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);

效果图
小程序利用canvas实现一键保存图片功能(合并图片,双指缩放,旋转,默认裁剪画布大小的尺寸)