图片拖拽/放大/缩小并获取图片偏移量合缩放比(学习自用)

该文章介绍了一个Vue组件,该组件允许用户在矩形框内拖动和缩放图片。组件使用JavaScript处理鼠标事件来实现图片的位置调整和缩放操作,并能获取到图片的位置和缩放比例。用户可以通过鼠标滚轮或输入框手动改变缩放比例。
摘要由CSDN通过智能技术生成

功能简介:在矩形框内拖拽图片到合适位置(图片可超过自身宽度的一半),并可对图片进行放大缩小操作。获取到图片相对于矩形框的位置及图片缩放比例。

效果图

 

完整代码

DragImg.vue文件

<template>
  <div>
    <div class="box margin-b15">
      <img class="avatar" v-show="imageUrl" :src="imageUrl" draggable="false" />
    </div>
    <span class="margin-b15">请使用鼠标滚轮进行缩放操作,或输入缩放比例</span>
    <div class="hander-box margin-b15">
      缩放比例:
      <a-input-number
        :max="1.5"
        :min="0.5"
        :step="0.1"
        :precision="2"
        v-model="sizeProportion"
        @change="changeProportion"
      ></a-input-number>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      DELTA: 1.1,
      x: null,
      y: null,
      oBox: null,
      oDiv: null,
      skewing: {}, // 偏移量
      sizeProportion: 1, // 缩放比
    };
  },
  props: {
    imageUrl: String,
    imgInfo: {
      type: Object,
      default: () => {
        return {};
      },
    },
  },
  watch: {
    imgInfo: { 
      handler(imgInfo) {
        this.sizeProportion = imgInfo?.sizeProportion || 1;
        if (imgInfo?.transform) {
          setTimeout(() => {
            // 展示图片偏移调整
            this.oDiv.style.transform = imgInfo.transform;
            this.skewing = this.getElementTop(this.oBox, this.oDiv);
          }, 100);
        }
      },
      immediate: true,
      deep: true,
    },
    imageUrl: {
      handler() {
        if (!this.imgInfo?.transform) {
          setTimeout(() => {
            this.oDiv.style.transform = "translate(-50%, -50%)";
          }, 100);
        }
      },
      immediate: true,
    },
  },
  mounted() {
    this.oBox = document.querySelector(".box");
    this.oDiv = document.querySelector(".avatar");
    this.init();
  },
  methods: {
    getElementTop(parent, sub) {
      // 获取偏移位置
      const parentClient = parent?.getBoundingClientRect() || {};
      const subClient = sub?.getBoundingClientRect() || {};
      return {
        y: parseInt(subClient.top - parentClient.top),
        x: parseInt(subClient.left - parentClient.left),
      };
    },
    init() {
      // 初始化数据
      this.DELTA = 1.1;
      this.x = null;
      this.y = null;
      this.skewing = this.getElementTop(this.oBox, this.oDiv);

      // 禁止选中文字/图片
      document.addEventListener("selectstart", (e) => {
        e.preventDefault();
      });
      // 鼠标按下事件
      this.oDiv.addEventListener("mousedown", this.mouseDown);
      // 图片缩放
      this.oDiv.addEventListener("wheel", this.zoom);
    },
    // 鼠标按下 获取位置并添加事件监听
    mouseDown(e) {
      let transf = this.getTransform(this.oDiv);
      this.x = e.clientX - transf.transX; // 图片初始位置
      this.y = e.clientY - transf.transY; // 图片初始位置
      document.addEventListener("mousemove", this.mouseMove);
      document.addEventListener("mouseup", this.mouseUp);
    },
    // 鼠标拖动 更新transform
    mouseMove(e) {
      let multiple = this.getTransform(this.oDiv).multiple;
      let moveX = e.clientX - this.x; // x向移动距离
      let moveY = e.clientY - this.y; // y向移动距离
      let newTransf = this.limitBorder(this.oDiv, this.oBox, moveX, moveY, multiple);
      this.oDiv.style.transform = `matrix(${multiple}, 0, 0, ${multiple}, ${newTransf.transX}, ${newTransf.transY})`;
    },
    // 鼠标抬起 移除监听器
    mouseUp() {
      this.skewing = this.getElementTop(this.oBox, this.oDiv);
      document.removeEventListener("mousemove", this.mouseMove);
      document.removeEventListener("mouseup", this.mouseUp);
    },
    // 鼠标滚轮缩放 更新transform
    zoom(e) {
      let transf = this.getTransform(this.oDiv);
      if (e.deltaY < 0) {
        // deltaY属性在向下滚动时返回正值,向上滚动时返回负值,否则为0
        if (transf.multiple > 1.37) return; // 放大不可超过150%
        transf.multiple *= this.DELTA; // 放大DELTA倍
      } else {
        if (transf.multiple < 0.6) return; //缩小不可超过50%
        transf.multiple /= this.DELTA; // 缩小DELTA倍
      }
      this.sizeProportion = transf.multiple || 1; // 缩放比
      let newTransf = this.limitBorder(
        this.oDiv,
        this.oBox,
        transf.transX,
        transf.transY,
        transf.multiple
      );
      this.oDiv.style.transform = `matrix(${transf.multiple}, 0, 0, ${transf.multiple}, ${newTransf.transX}, ${newTransf.transY})`;
    },
    /**
     * 通过getComputedStyle获取transform矩阵 并用split分割
     * 如 oDiv 的 transform: translate(100, 100);
     * getComputedStyle可以取到"matrix(1, 0, 0, 1, 100, 100)"
     * 当transform属性没有旋转rotate和拉伸skew时
     * metrix的第1, 4, 5, 6个参数为 x方向倍数, y方向倍数, x方向偏移量, y方向偏移量
     * 再分别利用 字符串分割 取到对应参数
     */
    getTransform(DOM) {
      let arr = getComputedStyle(DOM).transform.split(",");
      return {
        transX: isNaN(+arr[arr.length - 2]) ? 0 : +arr[arr.length - 2], // 获取translateX
        transY: isNaN(+arr[arr.length - 1].split(")")[0])
          ? 0
          : +arr[arr.length - 1].split(")")[0], // 获取translateX
        multiple: +arr[3], // 获取图片缩放比例
      };
    },
    /**
     * 获取边框限制的transform的x, y偏移量
     * innerDOM: 内盒子DOM
     * outerDOM: 边框盒子DOM
     * moveX: 盒子的x移动距离
     * moveY: 盒子的y移动距离
     */
    limitBorder(innerDOM, outerDOM, moveX, moveY, multiple) {
      let {
        clientWidth: innerWidth,
        clientHeight: innerHeight,
        offsetLeft: innerLeft,
        offsetTop: innerTop,
      } = innerDOM;
      let { clientWidth: outerWidth, clientHeight: outerHeight } = outerDOM;
      outerWidth = outerWidth + 100;
      outerHeight = outerHeight + 100;
      let transX;
      let transY;
      // 放大的图片超出box时 图片最多拖动到与边框对齐
      if (innerWidth * multiple > outerWidth || innerHeight * multiple > outerHeight) {
        if (innerWidth * multiple > outerWidth && innerWidth * multiple > outerHeight) {
          transX = Math.min(
            Math.max(moveX, outerWidth - (innerWidth * (multiple + 1)) / 2 - innerLeft),
            -innerLeft + (innerWidth * (multiple - 1)) / 2
          );
          transY = Math.min(
            Math.max(moveY, outerHeight - (innerHeight * (multiple + 1)) / 2 - innerTop),
            -innerTop + (innerHeight * (multiple - 1)) / 2
          );
        } else if (
          innerWidth * multiple > outerWidth &&
          !(innerWidth * multiple > outerHeight)
        ) {
          transX = Math.min(
            Math.max(moveX, outerWidth - (innerWidth * (multiple + 1)) / 2 - innerLeft),
            -innerLeft + (innerWidth * (multiple - 1)) / 2
          );
          transY = Math.max(
            Math.min(moveY, outerHeight - (innerHeight * (multiple + 1)) / 2 - innerTop),
            -innerTop + (innerHeight * (multiple - 1)) / 2
          );
        } else if (
          !(innerWidth * multiple > outerWidth) &&
          innerWidth * multiple > outerHeight
        ) {
          transX = Math.max(
            Math.min(moveX, outerWidth - (innerWidth * (multiple + 1)) / 2 - innerLeft),
            -innerLeft + (innerWidth * (multiple - 1)) / 2
          );
          transY = Math.min(
            Math.max(moveY, outerHeight - (innerHeight * (multiple + 1)) / 2 - innerTop),
            -innerTop + (innerHeight * (multiple - 1)) / 2
          );
        }
      }
      // 图片小于box大小时 图片不能拖出边框
      else {
        transX = Math.max(
          Math.min(moveX, outerWidth - (innerWidth * (multiple + 1)) / 2 - innerLeft),
          -innerLeft + (innerWidth * (multiple - 1)) / 2 - 100 //最小到哪(左右)
        );
        transY = Math.max(
          Math.min(moveY, outerHeight - (innerHeight * (multiple + 1)) / 2 - innerTop),
          -innerTop + (innerHeight * (multiple - 1)) / 2 - 100 // 最大到哪(上下)
        );
      }
      return { transX, transY };
    },
    changeProportion(multiple) {
      // 手动修改缩放比例
      let transf = this.getTransform(this.oDiv);
      if (multiple > 1) {
        // deltaY属性在向下滚动时返回正值,向上滚动时返回负值,否则为0
        if (multiple > 1.37) return; // 放大不可超过150%
        multiple *= this.DELTA; // 放大DELTA倍
      } else {
        if (multiple < 0.6) return; //缩小不可超过50%
        multiple /= this.DELTA; // 缩小DELTA倍
      }
      let newTransf = this.limitBorder(
        this.oDiv,
        this.oBox,
        transf.transX,
        transf.transY,
        multiple
      );
      this.oDiv.style.transform = `matrix(${multiple}, 0, 0, ${multiple}, ${newTransf.transX}, ${newTransf.transY})`;
    },
    submit() {
      let submitInfo = {
        sizeProportion: this.sizeProportion,
        transform: this.oDiv.style.transform, // 用作记录位置,作为反选用
        skewing: JSON.stringify({ x: 50, y: 50, ...this.skewing, avatarProportion: 200 }), // avatarProportion为头像大小
      };
      debugger;
      this.$emit("getSubmitInfo", submitInfo);
    },
  },
};
</script>

<style lang="less" scoped>
.box {
  position: relative;
  width: 300px;
  height: 300px;
  background-image: url("https://static.mycplife.com/xka/static/wx/headImagBg.png");
  overflow: hidden;
}
.avatar {
  position: absolute;
  width: 200px;
  height: 200px;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
.margin-b15 {
  margin-bottom: 15px;
}
</style>

使用:

<template>
  <div>
    <div class="upload-content">
      <div class="img-content">
        <drag-img
          ref="DragImg"
          :imageUrl="submitInfo.image"
          :imgInfo="submitInfo"
          @getSubmitInfo="getSubmitInfo"
        ></drag-img>
      </div>
    </div>
    <button @click="saveHead">保存</button>
    图片信息{{ positinoInfo }}
  </div>
</template>

<script>
import DragImg from "./DragImg.vue";

export default {
  components: { DragImg },
  data() {
    return {
      submitInfo: {},
      positinoInfo: {}, // skewing: {x, y, avatarProportion: 头像大小}, transform: 用作记录位置,作为反选用, sizeProportion: 缩放比
    };
  },

  mounted() {
    this.initDraw({});
  },
  methods: {
    initDraw(currentRow = {}) {
      this.submitInfo = {
        ...currentRow,
        image:
          "https://image.mycplife.com/mycp888/w800h800/png/2022/11/16/23af3007b6dd41e3bcf152cd85a5d102.png",
      };
    },
    // 保存
    saveHead() {
      this.$refs["DragImg"]?.submit();
    },
    getSubmitInfo(positinoInfo = {}) {
      this.positinoInfo = positinoInfo;
    },
  },
};
</script>

<style lang="less" scoped></style>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Vue移动端中实现图片的双指放大缩小拖拽,可以通过以下步骤进行操作。 首先,需要在Vue组件中引入相应的移动端手势库,比如AlloyFinger或是基于它封装的vue-alloyfinger插件。这些手势库可以监听移动端的触摸事件,方便实现手势操作。 其次,在组件的模板中需要渲染一张图片,并设置图片的初始宽度和高度。可以通过绑定样式属性的方式,将图片的宽度和高度与组件中的data数据绑定起来。 然后,需要为图片绑定手势事件,并在对应的方法中实现双指放大缩小拖拽的逻辑。比如,可以监听双指缩放事件,在事件处理函数中根据手指的位置和缩放比例来更新图片的宽度和高度。可以监听拖拽事件,在事件处理函数中根据手指的移动距离来更新图片的位置。 最后,还可以添加一些边界判断,比如设置图片的最大和最小缩放比例,防止图片过小或过大。还可以添加过渡动画,使操作更加平滑。 需要注意的是,双指放大缩小拖拽的实现需要一定的数学计算,比如计算手指的距离和角度,或是计算图片偏移量等。因此,在实现过程中需要对数学计算有一定的了解。 综上所述,通过Vue和移动端手势库,我们可以很方便地实现图片的双指放大缩小拖拽功能。通过监听手势事件,并在事件处理函数中更新图片的属性和位置,可以实现用户友好的图片操作效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值