在做Cordova+vue项目的时候,有需求是可以放大缩小以及拖动图片的功能,就类似于手机图库里面自带的缩放等功能。经查询可以使用hammerjs来做。
说到缩放,大家可能会想到的是使用transform:scale(x,y)进行对元素的缩放。
但是对于手势缩放的过程中,我们需要在进行缩放的同时,保证双指之间的中心点一直保持在原来的位置,这时我们需要配合使用translate(x,y)来进行位置的调整。但如何计算得到translate(x,y)中的值呢?
参考如下:
hammerjs官方文档css3 transform中的matrix矩阵

scale.js

import Hammer from 'hammerjs'// 引用hammerjs
import Vue from 'vue'
// 定义缩放方法,接收一个element参数:使用export暴露该方法
export function zoomElement (el) {
  function point2D (x, y) {
    return { x: x, y: y }
  }
  // 判断 正数,负数,不是数字
  function checkNumType (num) {
    var reg = new RegExp("^-?[0-9]*.?[0-9]*$")
    if (reg.test(num)) { // 用于检测一个字符串是否匹配某个模式
      var absVal = Math.abs(num) // 如果参数是非负数,则返回该参数;如果参数是负数,则返回该参数的相反数。
      return num == absVal ? true : false
    } else {
      console.log('this is not number')
    }
  }
  function exChangeNum (num, reNum) {
    let flag = checkNumType(num)
    let reFlag = checkNumType(reNum)
    let realNum = 0
    if (!flag && reFlag) {
      realNum = Number('-' + reNum)
    } else {
      realNum = Number(reNum)
    }
    return realNum
  }
  var reqAnimationFrame = (function () {
    return window[Hammer.prefixed(window, 'requestAnimationFrame')] || function (callback) {
      window.setTimeout(callback, 1000 / 60)
    }
  })()
  var ticking = false
  var tMatrix = [1, 0, 0, 1, 0, 0] //x缩放,无,无,y缩放,x平移,y平移
  var initScale = 1 //初始化scale
  var mc = new Hammer.Manager(el)
  var nowScale = 0
  var poscenter = point2D(0, 0)// 缓存双指的中心坐标
  var duration = '' // 设置过渡效果,用于双击缩放效果
  var lastTranslate = point2D(0, 0) // 记录上次的偏移值
  var lastcenter = point2D(el.offsetWidth / 2, el.offsetHeight / 2) // 图像的中心点,用于对比双指中心点
  var center = lastcenter // 初始化为图片中心点
  // 添加缩放事件
  mc.add(new Hammer.Pan({ threshold: 0, pointers: 1 }))
  mc.add(new Hammer.Pinch({ threshold: 0 }))
  mc.add(new Hammer.Tap({ event: 'doubletap', taps: 2 }))
  mc.on("pinchstart", onPinchStart) // 双指缩放
  mc.on("pinchmove", onPinch) // 双指移动
  mc.on("panmove", onPan)
  mc.on("panstart", onPanStart)
  // 缩放开始
  function onPinchStart (ev) {
    duration = ''
    lastTranslate = point2D(tMatrix[4], tMatrix[5])//记录上一次的偏移值 0 0
    initScale = tMatrix[0] || 1
    // 手势中心点
    poscenter = point2D(ev.center.x, ev.center.y)
    // 图像中心点  = 初始化图像中心点  + 上一次偏移量的中心点
    lastcenter = point2D(center.x + lastTranslate.x, center.y + lastTranslate.y)//重新计算放大后的中心坐标
    // 手势中心点 = 缩放中心点 - 图像中心点
    poscenter = point2D(ev.center.x - lastcenter.x, ev.center.y - lastcenter.y)
    requestElementUpdate('onpinchStart')
  }
  // 缩放途中
  function onPinch (ev) {
    // 缩放倍数 这里的缩放倍数
    nowScale = initScale * ev.scale
    // 如果倍数小于1 则等于1
    if (nowScale < 1) {
      nowScale = 1
    }
    // 缩放倍数
    tMatrix[0] = tMatrix[3] = nowScale
    let x = Number((1 - ev.scale) * poscenter.x + lastTranslate.x)
    let y = Number((1 - ev.scale) * poscenter.y + lastTranslate.y)
    let tempPosX = el.getBoundingClientRect().width / 2 - point2D(el.offsetWidth / 2, el.offsetHeight / 2).x
    let tempPosY = el.getBoundingClientRect().height / 2 - point2D(el.offsetWidth / 2, el.offsetHeight / 2).y
    if (Math.abs(x) > Math.abs(tempPosX)) {
      x = exChangeNum(x, tempPosX)
    }

    if (Math.abs(y) > Math.abs(tempPosY)) {
      y = exChangeNum(y, tempPosY)
    }
    tMatrix[4] = x
    tMatrix[5] = y
    requestElementUpdate('onpinch')
  }
  // 开始拖动
  function onPanStart () {
    lastTranslate = point2D(tMatrix[4], tMatrix[5]) // 缓存上一次的偏移值
  }
  // 拖动过程
  function onPan (ev) {
    tMatrix[0] = tMatrix[3] = nowScale || initScale
    // 拖动的动画 1.6
    duration = '1.6'
    let x = Number(lastTranslate.x + ev.deltaX)
    let y = Number(lastTranslate.y + ev.deltaY)
    let tempPosX = el.getBoundingClientRect().width / 2 - point2D(el.offsetWidth / 2, el.offsetHeight / 2).x
    let tempPosY = el.getBoundingClientRect().height / 2 - point2D(el.offsetWidth / 2, el.offsetHeight / 2).y
    if (Math.abs(x) > Math.abs(tempPosX)) {
      x = exChangeNum(x, tempPosX)
    }
    if (Math.abs(y) > Math.abs(tempPosY)) {
      y = exChangeNum(y, tempPosY)
    }
    tMatrix[4] = x
    tMatrix[5] = y
    requestElementUpdate('onpan')
  }

  // 每次都会·更新 因为是在移动端 所以都采用rem 否则可以直接用matrix
  function updateElementTransform () {
    el.style.transition = duration
    let xRem = Vue.prototype.$pxToRem(tMatrix[4]) + 'rem'
    let yRem = Vue.prototype.$pxToRem(tMatrix[5]) + 'rem'
    el.style.transform = 'translate(' + xRem + ',' + yRem + ') ' + 'scale(' + tMatrix[0] + ',' + tMatrix[3] + ')'
    el.style.WebkitTransform = 'translate(' + xRem + ',' + yRem + ') ' + 'scale(' + tMatrix[0] + ',' + tMatrix[3] + ')'
    el.style.msTransform = 'translate(' + xRem + ',' + yRem + ') ' + 'scale(' + tMatrix[0] + ',' + tMatrix[3] + ')'
    // var tmp = tMatrix.join(',')
    // el.style.transform = 'matrix(' + tmp + ')'
    // el.style.WebkitTransform = 'matrix(' + tmp + ')'
    // el.style.msTransform = 'matrix(' + tmp + ')'
    ticking = false
  }
  function requestElementUpdate () {
    if (!ticking) {
      reqAnimationFrame(updateElementTransform)
      ticking = true
    }
  }
  /**
  初始化设置
  */

  requestElementUpdate()

}

使用:
scale.vue

<template>
  <div ref="topology">
    <img :src="topology" alt="" />
  </div>
</template>
<script>
import { zoomElement } from "@/assets/js/scale.js";
export default {
  data() {
    return {
      topology: require('../../public/img/topology.svg'),
    };
  },
  mounted() {
    // 如果需要缩放的图片有使用v-show 则使用this.$nextTick 否则 执行方法的时候获取不到图片的offsetWidth,offsetHeight
    // this.$nextTick(() => {
      let zoomEl = this.$refs.topology;
      zoomElement(zoomEl);
    // });
  },
};
</script>