微信小程序 单指平移 双指缩放图片功能

实现功能参考:微信小程序自定义组件实现图片单指拖动、双指缩放效果 有修改。

一、明确需求:

  1. 图片要以AspectFit填充样式展示在其父控件内(即宽和高中比较长的那一条边充满,另外一条边等比放大或缩小)。
  2. 单指拖动图片,图片跟随手指移动。
  3. 双指缩放图片,要以【两手指的中间点为原点】进行放大和缩小。
  4. 单/双指操作松手后的回弹
  • 图片scale<1(即宽和高均小于父控件),则直接还原(scale=1)
    图片scale<1的回弹示例
  • 图片scale==1(即图片只被移动过,没有进行缩放),则将图片移回中心(水平居中&&垂直居中)
    图像未进行缩放的回弹示例
  • 图片scale>1

    • 如果图片宽/高(比较短的那一边)没有超过父控件则图片需要水平居中/垂直居中
      较短边为宽的图片宽度未充满时的回弹示例
  • 长度已经超过父控件的边(无论宽或高),且图片被拉出父控件的区域,则图片要移回区域内
    高度超过父控件 && 顶部空白回弹示例

    (底部空白同理 不贴示例图)


    高度和宽度超过父控件 && 顶部和左侧空白回弹示例
    (其他三个角空白同理 不贴示例图)

二、实现思路:

给图片加拖动事件,我们需要知道它什么时候被点击、被拖拽和拖拽结束。
通过修改imageView的margin-left和margin-top来改变图片的位置以实现图片的单指移动效果和所有操作结束松手后的回弹效果。
通过修改imageView的宽和高以实现图片放大缩小的效果。且需要同时修改其margin-left和margin-top使图片能以【两手指的中间点为原点】缩放。

三、实现

由于我们用到margin来改变图片的位置,所以给imageView设置样式的时候就不是单单写个AspectFit这么简单了。需要计算出图片为了展示出AspectFit的效果所需的宽高和margin。
需要计算的数值

Talk is cheap, show you the code.

<!--pages/Character/CharacterRecog.wxml-->
<scroll-view scroll-y="true" scroll-x="true" class = "imageScroll" style="width:{{img_view_width}}px;height:{{img_view_height}}px">
  <image src="{{recogImg}}" class="img" mode="aspectFit" 
      catchload="_imgLoadEvent"
      catchtouchstart='_touchStartEvent'
      catchtouchmove='_touchMoveEvent'
      catchtouchend='_touchEndEvent'
      catchtouchcancel='_touchEndEvent'  
      style="width: {{ imgWidth }}px;height: {{ imgHeight }}px;margin-top:{{marginTop}}px;margin-left:{{marginLeft}}px;"></image>
</scroll-view>
/* pages/Character/CharacterRecog.wxss */
.imageScroll {
  width: 100%;
  height: 430rpx;
  position: fixed;
  background-color: #f0f1f3;
}

.img {
  display: block;
  background-color: #f0f1f3;
  text-align: center;
}

接下来是交互事件

// pages/Character/CharacterRecog.js
var lastTouchPoint = { x: 0, y: 0 };
var newDist = 0;
var oldDist = 0;
Page({
  /**
     * 图片加载完成方法
     */
  _imgLoadEvent: function (event) {
    lastTouchPoint = { x: 0, y: 0 };
    
  },
  /**
     * 触摸开始事件
     */
  _touchStartEvent: function () {
    lastTouchPoint = { x: 0, y: 0 }
    oldDist = 0
  },
  /**
   * 手指移动事件
   */
  _touchMoveEvent: function (e) {
    //单指移动事件
    if (e.touches.length == 1) {
      if (lastTouchPoint.x == 0 && lastTouchPoint.y == 0) {
        lastTouchPoint.x = e.touches[0].clientX
        lastTouchPoint.y = e.touches[0].clientY
      } else {
        var xOffset = e.touches[0].clientX - lastTouchPoint.x
        var yOffset = e.touches[0].clientY - lastTouchPoint.y
        this.setData({
          marginTop: this.data.marginTop + yOffset,
          marginLeft: this.data.marginLeft + xOffset,
        })
        lastTouchPoint.x = e.touches[0].clientX
        lastTouchPoint.y = e.touches[0].clientY
      }
      // console.log(this.data.marginTop)
    }
    //双指缩放事件
    if (e.touches.length == 2) {
      if (oldDist == 0) {
        oldDist = this._spacing(e);
      } else {
        newDist = this._spacing(e);
        if (newDist > oldDist + 1) {
          this._zoom(newDist / oldDist, e);
          oldDist = newDist;
        }
        if (newDist < oldDist - 1) {
          this._zoom(newDist / oldDist, e);
          oldDist = newDist;
        }
      }
    }
  },
  /**
   * 触摸事件结束
   */
  _touchEndEvent: function () {
    //开始回弹
    this._reboundAnimation();
  },
  /**
   * 计算x轴上的双指中心点比例
   */
  _calcXRatio: function (event) {
    var xRatio = ((event.touches[0].clientX + event.touches[1].clientX) / 2 - this.data.marginLeft) / this.data.imgWidth
    return xRatio
  },
  /**
   * 计算y轴上的双指中心点比例
   */
  _calcYRatio: function (event) {
    var yRatio = ((event.touches[0].clientY + event.touches[1].clientY) / 2 - this.data.marginTop) / this.data.imgHeight
    return yRatio
  },
  /**
   * 双指缩放
   */
  _zoom: function (f, event) {
    var xRatio = this._calcXRatio(event)
    var yRatio = this._calcYRatio(event)
    if (this.data.imgWidth <= this.data.view_width && f < 1) {
      var ratio = this.data.view_width / this.data.imgWidth
      this.setData({
        imgWidth: this.data.imgWidth * ratio,
        imgHeight: this.data.imgHeight * ratio
      })
      return;
    }
    if (this.data.imgHeight <= this.data.view_height && f < 1) {
      var ratio = this.data.view_height / this.data.imgHeight
      this.setData({
        imgWidth: this.data.imgWidth * ratio,
        imgHeight: this.data.imgHeight * ratio
      })
      return;
    }
    this.setData({
      //此处的ratio为双指中心点在图片的百分比
      marginLeft: this.data.marginLeft + xRatio * this.data.imgWidth * (1 - f),
      marginTop: this.data.marginTop + yRatio * this.data.imgHeight * (1 - f),
      imgWidth: this.data.imgWidth * f,
      imgHeight: this.data.imgHeight * f,
    })
    // console.log(this.data.marginTop)
  },
  /**
   * 计算两指间距
   */
  _spacing: function (event) {
    var x = event.touches[0].clientX - event.touches[1].clientX;
    var y = event.touches[0].clientY - event.touches[1].clientY;
    return Math.sqrt(x * x + y * y);
  },
  /**
   * 边界的回弹动画
   */
  _reboundAnimation: function () {
    if (this.data.imgWidth / this.data.srcWidth < 1) {
      //缩放比例已经小于1了,直接还原
      this.setData({
        marginLeft: this.data.srcMarginLeft,
        marginTop: this.data.srcMarginTop,
        imgWidth: this.data.srcWidth,
        imgHeight: this.data.srcHeight,
      })
      return ;
    }
    

    //图片已铺满宽或高
    if (this.data.imgWidth > this.data.img_view_width && this.data.marginLeft > 0) {
      //图片宽度已铺满scrollViewWidth且被拉至最左边
      this.setData({
        marginLeft: 0
      })
    }
    if (this.data.imgWidth > this.data.img_view_width && (this.data.marginLeft + this.data.imgWidth) < this.data.img_view_width) {
      //图片宽度已铺满scrollViewWidth且被拉至最右边
      this.setData({
        marginLeft: this.data.img_view_width - this.data.imgWidth
      })
    }
    if (this.data.imgHeight > this.data.img_view_height && this.data.marginTop > 0) {
      //图片高度已铺满scrollViewHeight且被拉至最顶部
      this.setData({
        marginTop: 0
      })
    }
    if (this.data.imgHeight > this.data.img_view_height && (this.data.marginTop + this.data.imgHeight) < this.data.img_view_height) {
      //图片高度已铺满scrollViewHeight且被拉至最底部
      this.setData({
        marginTop: this.data.img_view_height - this.data.imgHeight
      })
    }

    //图片未铺满宽或高
    if (this.data.imgHeight <= this.data.img_view_height) {
      //图片高未铺满scrollViewHeight,垂直居中
      this.setData({
        marginTop: (this.data.img_view_height - this.data.imgHeight) * 0.5
      })
    }

    if (this.data.imgWidth <= this.data.img_view_width) {
      //图片宽未铺满scrollViewWidth,水平居中
      this.setData({
        marginLeft: (this.data.img_view_width - this.data.imgWidth) * 0.5
      })
    }
    
  },
  /**
   * 页面的初始数据
   */
  data: {
    rpxR: "",
    img_view_width: "",//scrollview的宽
    img_view_height: "",//scrollview的高
    marginTop: 0,//图片aspectFit显示时顶部的间距
    marginLeft: 0,//图片aspectFit显示时左边的间距
    srcMarginTop: 0,//图片未被缩放时顶部的间距
    srcMarginLeft: 0,//图片未被缩放时左边的间距
    srcWidth: 0,//图片未被缩放时的高
    srcHeight: 0,//图片未被缩放时的宽
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

    //根据传入的值设置title
    wx.setNavigationBarTitle({
      title: "通用文字识别"
    })
    //给标题和按钮文字赋值
    this.setData({
      rpxR: getApp().globalData.rpxR,
      exampleImageName: options.image
    })
    this.setData({
      recogImg: this.data.exampleImageName
    })
  /**
   * 这里计算图片以AspectFill方式填充所需的宽、高、margin-left、margin-top数值
   */
    wx.getImageInfo({
      src: this.data.exampleImageName,
      complete: (res) => {
        let imageWidth = res.width;
        let imageHeight = res.height;
        let scrollWidth = getApp().globalData.clientWidth;
        let scrollHeight = 430 / this.data.rpxR;
        //图片显示在view中的宽高度
        var scaleWidth = 0;
        var scaleHeight = 0; 
        //计算图片居中显示需要的margin
        var mLeft = 0;
        var mTop = 0;
        if (imageWidth / imageHeight > scrollWidth / scrollHeight) {
          //图片比较宽,宽度填充
          mLeft = 0;
          mTop = (scrollHeight - (scrollWidth / imageWidth) * imageHeight) * 0.5;
          scaleWidth = scrollWidth;
          scaleHeight = imageHeight * (scrollWidth / imageWidth);
        } else {
          //图片比较长,长度填充
          mTop = 0;
          mLeft = (scrollWidth - (scrollHeight / imageHeight) * imageWidth) * 0.5;
          scaleHeight = scrollHeight;
          scaleWidth = imageWidth * (scrollHeight / imageHeight);
        }
        // console.log(mLeft)
        // console.log(mTop)
        this.setData({
          img_view_width: scrollWidth,
          img_view_height: scrollHeight,
          imgWidth: scaleWidth,
          imgHeight: scaleHeight,
          marginLeft: mLeft,
          srcMarginLeft: mLeft,
          marginTop: mTop,
          srcMarginTop: mTop,
          srcWidth: scaleWidth,
          srcHeight: scaleHeight,
        })
      }
    })
  },
})
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350