欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

微信小程序实现图片拖动、放大、缩小、旋转、滤镜和切图功能

程序员文章站 2024-02-11 17:03:22
...

目录

 

前言

实现过程


前言

本文介绍使用微信小程序API+canvas来实现图片的可拖动、放大、缩小和旋转,并可对选中的图片进行不同效果的滤镜和不同形状的切图,且可对最终效果进行保存到本地。

最终效果:

微信小程序实现图片拖动、放大、缩小、旋转、滤镜和切图功能

实现过程

1. 文件index.wxml和index.wxss代码如下,这一块比较简单,可自行查看,不做过多分析:

<view class='contentWarp'>
  <block wx:for="{{itemList}}" wx:key="id">
    <view class='touchWrap' style='transform: scale({{item.scale}});top:{{item.top}}px;left:{{item.left}}px; z-index:{{item.active?100:1}}'>
      <view class='imgWrap {{item.active? "touchActive":""}}' style="transform: rotate({{item.angle}}deg); border: {{item.active?4*item.oScale:0}}rpx #fff dashed;">
        <image src='{{item.image}}' data-id='{{item.id}}' style='width:{{item.width}}px;height:{{item.height}}px;' bindtouchstart='WraptouchStart' bindtouchmove='WraptouchMove' bindtouchend='WraptouchEnd'  mode="widthFix"></image>
        <image class='x' src='../../images/del.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtap='deleteItem'></image>
        <image class='o' src='../../images/scale.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtouchstart='oTouchStart' bindtouchmove='oTouchMove' bindtouchend='WraptouchEnd'></image>
      </view>
    </view>
  </block>
</view>
<!-- 右下角操作按钮 -->
<view class="operation-buttons">
  <image src="../../images/upload.png" bindtap="uploadImg"></image>
  <image src="../../images/fliters.png" bindtap="toggleFliters"></image>
  <image src="../../images/shapes.png" bindtap="toggleShapes"></image>
  <image src="../../images/synthesis.png" bindtap="openMask"></image>
</view>
<!-- 各种过滤效果 -->
<view class="fliters" hidden="{{!showFliters}}">
  <block wx:for="{{fliters}}" wx:key="id">
    <image data-fliter="{{item.fliter}}" src="{{item.src}}" bindtap="imgEffects"></image>
  </block>
</view>
<!-- 各种形状 -->
<view class="shapes" hidden="{{!showShapes}}">
  <block wx:for="{{shapes}}" wx:key="id">
    <image data-shape="{{item.shape}}" src="{{item.src}}" bindtap="imgEffects"></image>
  </block>
</view>
<!-- 保存显示效果图 -->
<view class='canvasWrap' hidden="{{!showCanvas}}">
  <image class="resImg" bindlongtap="saveImg" src="{{canvasTemImg}}" mode="widthFix"></image>
  <view class='btn_view'>
    <button bindtap='saveImg'>保存到手机</button>
    <button bindtap='disappearCanvas'>关闭</button>
  </view>
</view>
<!-- 画布 -->
<canvas class='maskCanvas' canvas-id="maskCanvas" style='width:{{canvasWidth}}px; height:{{canvasHeight}}px;'></canvas>
/**index.wxss**/
.contentWarp {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  margin: auto;
}

.touchWrap {
  transform-origin: center;
  position: absolute;
  z-index: 100;
}

.imgWrap {
  box-sizing: border-box;
  width: 100%;
  transform-origin: center;
  float: left;
  border: 5rpx transparent dashed;
}

.imgWrap image {
  float: left;
}

.touchActive .x {
  display: block;
}

.touchActive .o {
  display: block;
}

.x {
  position: absolute;
  top: -25rpx;
  left: -25rpx;
  z-index: 500;
  display: none;
  width: 50rpx;
  height: 50rpx;
  overflow: hidden;
  font-weight: bold;
  color: #d1e3f1;
}

.o {
  position: absolute;
  bottom: -25rpx;
  right: -25rpx;
  width: 50rpx;
  height: 50rpx;
  text-align: center;
  display: none;
  overflow: hidden;
  font-weight: bold;
  color: #d1e3f1;
}

.active {
  background-color: rgb(78, 114, 151);
}

.active view {
  border: none;
}

.touchActive {
  z-index: 400;
}

.operation-buttons {
  position: absolute;
  bottom: 100rpx;
  right: 20rpx;
  display: flex;
  flex-direction: column;
  z-index: 101;
}

.operation-buttons image {
  width: 100rpx;
  height: 100rpx;
  margin-top: 40rpx;
}

.canvasWrap {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.6);
  z-index: 999;
  text-align: center;
}

.maskCanvas {
  position: absolute;
  left: -200%;
  top: 0;
}

.btnView view {
  padding-bottom: 20rpx;
}

.hand {
  position: absolute;
  left: 100rpx;
  right: 0;
  margin: auto;
  z-index: 100;
}

.getUserInfoBtn {
  position: initial;
  border: none;
  background-color: none;
}

.getUserInfoBtn::after {
  border: none;
}

.btn_view {
  display: flex;
  padding: 20rpx;
}

.btn_view button {
  width: 210rpx;
  font-size: 28rpx;
  background-color: #eb4985;
  color: #fff;
  line-height: 80rpx;
}

.resImg {
  width: 75%;
  margin-top: 10px;
}

/* 特效样式 */
.fliters {
  display: flex;
  flex-direction: column;
  position: absolute;
  bottom: 382rpx;
  right: 120rpx;
  z-index: 201;
}

.shapes {
  display: flex;
  flex-direction: column;
  position: absolute;
  bottom: 242rpx;
  right: 120rpx;
  z-index: 201;
}

.fliters image, .shapes image {
  width: 60rpx;
  height: 60rpx;
  border: 2rpx solid #eb4985;
}

2. 文件index.js存放所有功能的逻辑代码,相对比较复杂,下面分开来分析几个重点方法:

1)方法uploadImg+setDropItem:获取上传图片的信息,跟设置的最大宽高进行判断(maxWidth, maxHeight),然后根据判断的结果进行缩放,避免大图溢出,且设置图片的地址、坐标、定位和是否选中等信息;用于后面功能使用,支持多图使用;

2)方法WraptouchStart+WraptouchMove:获取图片移动坐标和触发时坐标的差值,加上图片本来的坐标来实现移动效果,注意要把移动坐标赋值给触发时坐标(items[index].lx = e.touches[0].clientX),不然会导致移动有问题;

3)方法oTouchStart+oTouchMove:获取拖动后图片的半径跟触发时图片的半径的比值,再使用scale来实现缩放功能(items[index].disPtoO / items[index].r);获取触发时的图片角度+拖动后图片的角度,再使用rotate来实现旋转功能(items[index].angle = items[index].rotate);

4)方法imgEffects:调用滤镜方法util.imgFliters(详细可到https://jingyan.baidu.com/article/ed15cb1b9fd9bb1be3698183.html查看),根据设置的滤镜值,进行不同的滤镜处理;而调用形状方法util.imgShapes,根据设置的形状值,进行不同的切图效果;

5)方法synthesis:用来把所有图片的最终效果合成一个画布,用于保存图片到本地使用;

6)方法saveImg:把画布保存到本地相册。

let index = 0, // 当前点击图片的index
  items = [], // 图片数组信息
  itemId = 1, // 图片id,用于识别点击图片
  fliter = 'init', // 默认过滤类型(原图)
  shape = 'init'; // 默认形状(原图)
const hCw = 1.62; // 图片宽高比
const canvasPre = 1; // 展示的canvas占mask的百分比
const maskCanvas = wx.createCanvasContext('maskCanvas', this); // 创建 canvas 的绘图上下文 CanvasContext 对象

const util = require('../../utils/util.js');
Page({
  /**
   * 页面的初始数据
   */
  data: {
    itemList: [],
    showFliters: false, // 默认不显示过滤效果框
    showShapes: false, // 默认不显示形状效果框
    fliters: [{
      fliter: 'init',
      src: '../../images/init.jpg'
    }, {
      fliter: 'bw',
      src: '../../images/bw.jpg'
    }, {
      fliter: 'groundGlass',
      src: '../../images/groundGlass.jpg'
    }, {
      fliter: 'pictureStory',
      src: '../../images/pictureStory.jpg'
    }, {
      fliter: 'reminiscence',
      src: '../../images/reminiscence.jpg'
    }, {
      fliter: 'sketch',
      src: '../../images/sketch.jpg'
    }],
    shapes: [{
      shape: 'circle',
      src: '../../images/init.jpg'
    }, {
      shape: 'star',
      src: '../../images/init.jpg'
    }, {
      shape: 'irregularityHeart',
      src: '../../images/init.jpg'
    }, {
      shape: 'SudokuHeart',
      src: '../../images/init.jpg'
    }]
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
    items = this.data.itemList;
    wx.getSystemInfo({ // 获取系统信息
      success: sysData => {
        this.sysData = sysData
        // 设置画布宽高,this.sysData.windowWidth为屏幕的宽度
        this.setData({
          canvasWidth: this.sysData.windowWidth * canvasPre, // 如果觉得不清晰的话,可以把所有组件、宽高放大一倍
          canvasHeight: this.sysData.windowWidth * canvasPre * hCw,
        })
      }
    })
  },
  // 上传图片
  uploadImg() {
    let that = this;
    wx.chooseImage({
      count: 1,
      sizeType: ['original', 'compressed'],
      sourceType: ['album', 'camera'],
      success (res) {
        // tempFilePath可以作为img标签的src属性显示图片
        that.setDropItem({
          url: res.tempFilePaths[0]
        });
      }
    })
  },
  // 设置图片的信息
  setDropItem(imgData) {
    let data = {}; // 存储图片信息
    // 获取图片信息,网络图片需先配置download域名才能生效
    wx.getImageInfo({
      src: imgData.url,
      success: res => {
        // 初始化数据
        let maxWidth = 150, maxHeight = 150; // 设置最大宽高
        if (res.width > maxWidth || res.height > maxHeight) { // 原图宽或高大于最大值就执行
            if (res.width / res.height > maxWidth / maxHeight) { // 判断比例使用最大值的宽或高作为基数计算
                data.width = maxWidth;
                data.height = Math.round(maxWidth * (res.height / res.width));
            } else {
                data.height = maxHeight;
                data.width = Math.round(maxHeight * (res.width / res.height));
            }   
        }
        data.image = imgData.url; // 显示地址
        data.initImage = imgData.url; // 原始地址
        data.id = ++itemId; // id
        data.top = 0; // top定位
        data.left = 0; // left定位
        // 圆心坐标
        data.x = data.left + data.width / 2;
        data.y = data.top + data.height / 2;
        data.scale = 1; // scale缩放
        data.rotate = 1; // 旋转角度
        data.active = false; // 选中状态
        items[items.length] = data; // 每增加一张图片数据增加一条信息
        this.setData({
          itemList: items
        })
      }
    })
  },
  // 点击图片
  WraptouchStart: function(e) {
    // 循环图片数组获取点击的图片信息
    for (let i = 0; i < items.length; i++) {
      items[i].active = false;
      if (e.currentTarget.dataset.id == items[i].id) {
        index = i;
        items[index].active = true;
      }
    }
    this.setData({
      itemList: items
    })

    // 获取点击的坐标值
    items[index].lx = e.touches[0].clientX;
    items[index].ly = e.touches[0].clientY;
  },
  // 拖动图片
  WraptouchMove(e) {
    items[index]._lx = e.touches[0].clientX;
    items[index]._ly = e.touches[0].clientY;

    items[index].left += items[index]._lx - items[index].lx;
    items[index].top += items[index]._ly - items[index].ly;
    items[index].x += items[index]._lx - items[index].lx;
    items[index].y += items[index]._ly - items[index].ly;

    items[index].lx = e.touches[0].clientX;
    items[index].ly = e.touches[0].clientY;

    this.setData({
      itemList: items
    })
  },
  // 放开图片
  WraptouchEnd() {
    this.synthesis(); // 调用合成图方法
  },
  // 点击伸缩图标
  oTouchStart(e) {
    //找到点击的那个图片对象,并记录
    for (let i = 0; i < items.length; i++) {
      items[i].active = false;
      if (e.currentTarget.dataset.id == items[i].id) {
        index = i;
        items[index].active = true;
      }
    }
    //获取作为移动前角度的坐标
    items[index].tx = e.touches[0].clientX;
    items[index].ty = e.touches[0].clientY;
    //移动前的角度
    items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty);
    //获取图片半径
    items[index].r = this.getDistancs(items[index].x, items[index].y, items[index].left, items[index].top);
  },
  oTouchMove: function(e) {
    //记录移动后的位置
    items[index]._tx = e.touches[0].clientX;
    items[index]._ty = e.touches[0].clientY;
    //移动的点到圆心的距离
    items[index].disPtoO = this.getDistancs(items[index].x, items[index].y, items[index]._tx, items[index]._ty - 10)

    items[index].scale = items[index].disPtoO / items[index].r;

    //移动后位置的角度
    items[index].angleNext = this.countDeg(items[index].x, items[index].y, items[index]._tx, items[index]._ty)
    //角度差
    items[index].new_rotate = items[index].angleNext - items[index].anglePre;

    //叠加的角度差
    items[index].rotate += items[index].new_rotate;
    items[index].angle = items[index].rotate; //赋值

    //用过移动后的坐标赋值为移动前坐标
    items[index].tx = e.touches[0].clientX;
    items[index].ty = e.touches[0].clientY;
    items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty)

    //赋值setData渲染
    this.setData({
      itemList: items
    })
  },
  // 计算坐标点到圆心的距离
  getDistancs(cx, cy, pointer_x, pointer_y) {
    var ox = pointer_x - cx;
    var oy = pointer_y - cy;
    return Math.sqrt(
      ox * ox + oy * oy
    );
  },
  /*
   *参数cx和cy为图片圆心坐标
   *参数pointer_x和pointer_y为手点击的坐标
   *返回值为手点击的坐标到圆心的角度
   */
  countDeg: function(cx, cy, pointer_x, pointer_y) {
    var ox = pointer_x - cx;
    var oy = pointer_y - cy;
    var to = Math.abs(ox / oy);
    var angle = Math.atan(to) / (2 * Math.PI) * 360;
    if (ox < 0 && oy < 0) //相对在左上角,第四象限,js中坐标系是从左上角开始的,这里的象限是正常坐标系  
    {
      angle = -angle;
    } else if (ox <= 0 && oy >= 0) //左下角,3象限  
    {
      angle = -(180 - angle)
    } else if (ox > 0 && oy < 0) //右上角,1象限  
    {
      angle = angle;
    } else if (ox > 0 && oy > 0) //右下角,2象限  
    {
      angle = 180 - angle;
    }
    return angle;
  },
  deleteItem: function(e) {
    let newList = [];
    for (let i = 0; i < items.length; i++) {
      if (e.currentTarget.dataset.id != items[i].id) {
        newList.push(items[i])
      }
    }
    if (newList.length > 0) {
      newList[newList.length - 1].active = true; // 剩下图片组最后一个选中
    }
    items = newList;
    this.setData({
      itemList: items
    })
  },
  // 打开遮罩层
  openMask() {
    this.synthesis();
    this.setData({
      showCanvas: true
    })
  },
  synthesis() { // 合成图片
    maskCanvas.save();
    maskCanvas.beginPath();

    // 画背景色(白色)
    maskCanvas.setFillStyle('#fff');
    maskCanvas.fillRect(0, 0, this.data.canvasWidth, this.data.canvasHeight);

    items.forEach((currentValue, index) => {
      maskCanvas.save();
      maskCanvas.translate(0, 0);
      maskCanvas.beginPath();
      maskCanvas.translate(currentValue.x, currentValue.y); // 圆心坐标
      maskCanvas.rotate(currentValue.angle * Math.PI / 180);
      maskCanvas.translate(-(currentValue.width * currentValue.scale / 2), -(currentValue.height * currentValue.scale / 2))
      maskCanvas.drawImage(currentValue.image, 0, 0, currentValue.width * currentValue.scale, currentValue.height * currentValue.scale);
      maskCanvas.restore();
    })
    // reserve 参数为 false,则在本次调用绘制之前 native 层会先清空画布再继续绘制
    maskCanvas.draw(false, (e) => {
      wx.canvasToTempFilePath({
        canvasId: 'maskCanvas',
        success: res => {
          this.setData({
            canvasTemImg: res.tempFilePath
          })
        }
      }, this);
    })
  },
  // 点击切换显示过滤框
  toggleFliters() {
    this.setData({
      showFliters: !this.data.showFliters,
      showShapes: false
    });
  },
  // 点击切换显示形状框
  toggleShapes() {
    this.setData({
      showShapes: !this.data.showShapes,
      showFliters: false
    });
  },
  // 图片特效
  imgEffects(e) {
    fliter = e.currentTarget.dataset.fliter || 'init';
    shape = e.currentTarget.dataset.shape || 'init';
    let that = this;
    items.forEach((currentValue, index) => {
      if (currentValue.active) {
        maskCanvas.save();
        maskCanvas.beginPath();
        util.imgShapes(maskCanvas, 0, 0, currentValue.width, currentValue.width, shape, 0, currentValue); // 图片剪切不同形状
        maskCanvas.clearRect(0, 0, currentValue.width, currentValue.height);
        maskCanvas.drawImage(currentValue.initImage, 0, 0, currentValue.width, currentValue.height);

        maskCanvas.draw(false, function() {
          wx.canvasGetImageData({ // 获取canvas区域的像素数据
            canvasId: 'maskCanvas',
            x: 0,
            y: 0,
            width: currentValue.width,
            height: currentValue.height,
            success(res) {
              let imageData = res.data;
              util.imgFliters(maskCanvas, fliter, res); // 调用图片滤镜函数

              maskCanvas.clearRect(0, 0, currentValue.width, currentValue.height); // 清除旧的,不然会导致卡顿

              maskCanvas.restore();
              wx.canvasPutImageData({ // 将像素数据绘制到canvas
                canvasId: 'maskCanvas',
                x: 0,
                y: 0,
                width: currentValue.width,
                height: currentValue.height,
                data: imageData,
                success(res) {
                  wx.canvasToTempFilePath({
                    canvasId: 'maskCanvas',
                    width: currentValue.width,
                    height: currentValue.height,
                    destWidth: currentValue.width,
                    destHeight: currentValue.height,
                    success: res => {
                      items[index].image = res.tempFilePath
                      that.setData({
                        itemList: items
                      })
                    }
                  }, this)
                }
              })
            }
          });
        })
      };
    })
  },
  // 关闭遮罩层
  disappearCanvas() {
    this.setData({
      showCanvas: false
    })
  },
  // 保存图片到系统相册
  saveImg: function() {
    wx.saveImageToPhotosAlbum({
      filePath: this.data.canvasTemImg,
      success: res => {
        wx.showToast({
          title: '保存成功',
          icon: "success"
        })
      },
      fail: res => {
        wx.openSetting({
          success: settingdata => {
            if (settingdata.authSetting['scope.writePhotosAlbum']) {
              console.log('获取权限成功,给出再次点击图片保存到相册的提示。')
            } else {
              console.log('获取权限失败,给出不给权限就无法正常使用的提示')
            }
          },
          fail: error => {
            console.log(error)
          }
        })
        wx.showModal({
          title: '提示',
          content: '保存失败,请确保相册权限已打开',
        })
      }
    })
  }
})

查看公众号信息:

微信小程序实现图片拖动、放大、缩小、旋转、滤镜和切图功能

 

相关标签: 小程序