common-components/preview-image/preview-image.js

/**
 * @module preview-image
 * @description 图片预览组件,支持图片放大、缩小、移动、横屏查看
 * @property {string} src 要预览的图片地址
 */

// 参数调优:
// 两指最大距离阈值
const maxDistance = Number.MAX_VALUE;
// 两指最小距离阈值
const minDistance = 20;
// 最短移动距离阈值
const minMoveDistance = 40;
// 缩放范围
const minScaleRange = 0.8;
const maxScaleRange = 6;
// 距离最大有效比例
const maxValidRatio = 60;

// 放大缩小涉及参数:
// 两指初始距离
let initialDistance = 0;
// 两指当前距离
let currentDistance = 0;
// 算出放大增量与距离比例的比
let k = (maxScaleRange - 1) / (maxValidRatio - 1);

// 移动涉及参数:
// 起点位置
let startX;
let startY;
// 终点位置
let endX;
let endY;
// 移动距离
let moveDistance = 0;

// 操作状态:移动状态move、缩放状态scale
let state = '';

// 当前是否正在渲染中
let isRendering = false;

// 窗口宽、高
let {
  windowWidth,
  windowHeight
} = wx.getSystemInfoSync();

Component({

  options: {
    addGlobalClass: true,
    pureDataPattern: new RegExp('^_'),
  },

  properties: {
    // 图片地址
    src: {
      type: 'string',
      value: ''
    }
  },

  observers: {
    'src': function(newValue) {

      if (!newValue) return;

      let self = this;
      wx.getImageInfo({
        src: newValue,
        success(res) {
          let {
            width,
            height,
          } = res;
          self.setData({
            _picWidth: windowWidth,
            _picHeight: windowWidth * height / width,
          })
        },
      })

    }
  },

  data: {
    // 当前方向
    orientation: 'portrait',
    // x轴方向移动偏移量
    offsetX: 0,
    // y轴方向移动偏移量
    offsetY: 0,
    // 放大缩小倍数
    scale: 1,
    // 图片宽、高
    _picWidth: 0,
    _picHeight: 0,
  },

  pageLifetimes: {
    resize(res) {
      // console.log('resize...', res);
      this.setData({
        orientation: res.deviceOrientation,
        scale: 1,
        offsetX: 0,
        offsetY: 0,
      }, () => {
        initialDistance = 0;
        currentDistance = 0;
        startX = 0;
        startY = 0;
        endX = 0;
        endY = 0;
        moveDistance = 0;
      })
    }
  },

  methods: {

    onTouchstart(e) {

      // console.log('start...', e);
      let {
        touches
      } = e;
      if (touches.length === 2) {
        state = 'scale';
        // 两根手指同时按下,获取两指起始位置,计算二者距离,作为初始距离
        initialDistance = this._calcDistance(touches[0].pageX, touches[0].pageY, touches[1].pageX, touches[1].pageY);
      } else if (touches.length === 1) {
        state = 'move';
        // 单指按下,记录当前位置
        startX = touches[0].pageX;
        startY = touches[0].pageY;
      }
    },

    onTouchmove(e) {

      // console.log('move...', e);
      if (isRendering) return;

      let {
        touches
      } = e;
      if (state === 'scale' && touches.length === 2) {

        // 两根手指同时移动,获取两指移动时移动位置,计算二者距离,如果比上次距离大,则放大图片,如果小,则缩小图片,注意当大于某个距离不再放大,小于某个距离不再缩小
        currentDistance = this._calcDistance(touches[0].pageX, touches[0].pageY, touches[1].pageX, touches[1].pageY);

        let absDiff = Math.abs(currentDistance - initialDistance);
        if (absDiff > minDistance && absDiff < maxDistance) {

          // console.log('scale...', currentDistance, initialDistance);
          isRendering = true;

          // scale是增量变化,使用全量变化的话下次放大不是基于上次进行放大。
          if (currentDistance > initialDistance) {
            // 距离比例>1 放大
            this.setData({
              scale: this.data.scale + k * (currentDistance / initialDistance - 1)
            }, () => {
              isRendering = false;
            })
          } else {
            // 距离比例<1 缩小
            this.setData({
              scale: Math.max(this.data.scale - minScaleRange * (1 - currentDistance / initialDistance), minScaleRange)
            }, () => {
              isRendering = false;
            })
          }
        }
      } else if (state === 'move' && touches.length === 1) {
        // 单指移动,获取当前位置,计算二者距离,进行偏移
        endX = touches[0].pageX;
        endY = touches[0].pageY;
        moveDistance = this._calcDistance(startX, startY, endX, endY);

        if (moveDistance > minMoveDistance) {

          // console.log('move...',startX,endX);
          // console.log('move...',startY,endY);
          // console.log('move...',moveDistance);
          isRendering = true;

          //计算图片宽、高与窗口宽、高的差绝对值,再除以2
          let deltaX = Math.abs(windowWidth - this.data._picWidth * this.data.scale) / 2;
          let deltaY = Math.abs(windowHeight - this.data._picHeight * this.data.scale) / 2;

          let offsetX = this.data.offsetX + endX - startX;
          let offsetY = this.data.offsetY + endY - startY;
          if (offsetX > 0) {
            // 右移,取当前计算偏移量与deltaX的最小值
            offsetX = Math.min(offsetX, deltaX);
          } else {
            // 左移,取当前计算偏移量与deltaX的最大值
            offsetX = Math.max(offsetX, -deltaX);
          }
          if (offsetY > 0) {
            // 下移,取当前计算偏移量与deltaY的最小值
            offsetY = Math.min(offsetY, deltaY);
          } else {
            // 上移,取当前计算偏移量与deltaY的最大值
            offsetY = Math.max(offsetY, -deltaY);
          }

          this.setData({
            offsetX,
            offsetY,
          }, () => {
            startX = endX;
            startY = endY;
            isRendering = false;
          })
        }
      }
    },

    onTouchend(e) {
      // console.log('end...', e);
      if (state === 'scale') {
        if (this.data.scale === minScaleRange) {
          this.setData({
            scale: 1,
          });
        } else if (this.data.scale > maxScaleRange) {
          this.setData({
            scale: maxScaleRange,
          })
        }
      }
      state = '';
    },

    // 计算两点间绝对距离
    _calcDistance(startX, startY, endX, endY) {
      return Math.round(Math.sqrt(Math.pow(Math.abs(startX - endX), 2) + Math.pow(Math.abs(startY - endY), 2)));
    },

    onPreviewImage(e) {

      // console.log('longpress...', e);

      if (state === 'scale') return;

      let {
        pageX,
        pageY
      } = e.touches[0];
      if (pageX === startX && pageY === startY) {
        wx.previewImage({
          urls: [this.data.src],
        });
        state = '';
      }

    }
  }
})