手把手教你学会用vue实现元素拖拽移动+滚轮缩放功能

15 篇文章 1 订阅
7 篇文章 0 订阅

项目中做看板重构时遇到的开发需求,不能使用组件,乍一看感觉很头大,但实际上手做出来后还是小有成就的。

浏览图:性能探究(一):使用对象代替数组匹配数据,从而省去遍历操作

小demo演示——拖拽移动:
在这里插入图片描述
滚轮缩放:
在这里插入图片描述

直接进入正题:

先创建一个简单的vue demo项目

<template>
  <div class="drag">
    <div class="back_box">
      这是一个背景
      <div class="drag_box">这是一个蓝色可拖拽元素</div>
    </div>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  data() {
    return {};
  },
  mounted() {
    console.log(this.$el);
  },
};
</script>

<style scoped>
.back_box {
  background: #ccc;
  width: 50vw;
  height: 50vh;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -30%);
}

.drag_box {
  width: 100px;
  height: 100px;
  background: skyblue;
  user-select: none; /* 不可选中,为了拖拽时不让文字高亮 */
}
</style>

1、实现拖拽移动

首先我们给需要实现功能的元素加一个draggable="true"让元素能够被拖拽,属性介绍:
在这里插入图片描述
回到页面发现我们的元素已经可以拖拽了(因为截图截不进鼠标,所以手绘了个鼠标的图案上去)
在这里插入图片描述
但拖拽完放开鼠标后,元素还是在原来的位置,根本没发生任何变化OTL

别急,让我们慢慢捣鼓它


如何实现拖拽功能?我想到的思路是:
①. 把元素设为绝对定位;
②. 鼠标最初摁下左键时,记录该点的坐标位置;
③. 鼠标拖拽移动完松开左键时,再次记录该点坐标位置;
④. 将两个位置的横纵值相减,即可获得拖拽的偏移量,也就是移动距离。
⑤. 最后动态设置元素绝对定位后的top和left值,实现元素移动。

于是我们来一步步实现思路:先来认识两个搭配draggable属性一起使用的事件——ondragstartondragend,它们的定义分别为:
①. ondragstart 事件在用户开始拖动元素或选择的文本时触发
②. ondragend 事件在用户完成元素或首选文本的拖动时触发。

于是我们给元素加上两个事件和方法并劫持$event:
在这里插入图片描述
在这里插入图片描述
我们再回到页面中对元素进行两次拖拽,看看控制台打印了什么:
在这里插入图片描述
两次事件里未改变的值肯定不是我们需要的,所以我们就研究那些值产生改变的属性就ok了
在这里插入图片描述
首先我们排除掉screenXscreenY,因为浏览器框框也有宽高。
其次是offsetXoffsetY,用这两个值也是可以的,但若向左或向上拖拽,就会产生负数的值,某些地方用起来会很麻烦,增加不必要的逻辑。
所以最符合我们开发该功能要用到的属性就是剩下的clientXclientY了。

我们先在data里定义几个需要用到的变量:
在这里插入图片描述

HTML中也加一丢丢东西(给父元素加ref以取到父元素宽高,给拖拽元素加动态的style控制移动):
在这里插入图片描述

methods里的方法以及页面初始化时需要在mounted里执行的逻辑:
在这里插入图片描述

※ 在此额外补充一下initHeight的算法备注:正常而言,this.initHeight = this.initWidth * (1080 / 1920);这样写就ok了(以1920*1080分辨率的屏幕为参考计算自适应的高),但有些项目的看板(比如我手头上这个项目)父元素是会带顶部标题栏和左右带默认padding的,如下:
在这里插入图片描述
这时我们就需要稍微计算一下全屏时父元素占位的宽高比例,比如像我这样的看板粗略测出title栏大概是12vh,padding大概是左右各1vh,于是就用这个算式代替:this.initHeight = this.initWidth * ((1080 * 0.88) / (1920 - 1080 * 0.02))。


回到正题:css中给拖拽元素加上position: absolute开启绝对定位(为方便我在一开始就给父元素加了fixed定位,实际项目中也要记得给对应的父元素加定位属性噢~),以防万一再顺手加个z-index(可省略)
在这里插入图片描述
功能思路的每一步骤都实现好了,那么切回页面来见证奇迹的一刻——

在这里插入图片描述
OHHHHHHHHHHHHHHHHHHHHHHHHHH!~~

到此为止的源码:

<template>
  <div class="drag">
    <div class="back_box" ref="back_box">
      这是一个背景
      <div
        class="drag_box"
        draggable="true"
        @dragstart="dragstart($event)"
        @dragend="dragend($event)"
        :style="`left:${elLeft}px;top:${elTop}px`"
      >
        这是一个蓝色可拖拽元素
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  data() {
    return {
      initWidth: 0, // 父元素的宽-自适应值
      initHeight: 0, // 父元素的高-自适应值
      startclientX: 0, // 元素拖拽前距离浏览器的X轴位置
      startclientY: 0, //元素拖拽前距离浏览器的Y轴位置
      elLeft: 0, // 元素的左偏移量
      elTop: 0, // 元素的右偏移量
    };
  },
  methods: {
    // 页面初始化
    initBodySize() {
      this.initWidth = this.$refs.back_box.clientWidth; // 拿到父元素宽
      // this.initHeight = this.initWidth * (1080 / 1920);
      this.initHeight = this.initWidth * ((1080 * 0.88) / (1920 - 1080 * 0.02)); // 根据宽计算高实现自适应
    },
    // 拖拽开始事件
    dragstart(e) {
      console.log(e);
      this.startclientX = e.clientX; // 记录拖拽元素初始位置
      this.startclientY = e.clientY;
    },
    // 拖拽完成事件
    dragend(e) {
      console.log(e);
      let x = e.clientX - this.startclientX; // 计算偏移量
      let y = e.clientY - this.startclientY;
      this.elLeft += x; // 实现拖拽元素随偏移量移动
      this.elTop += y;
    },
  },
  mounted() {
    // console.log(this.$el);
    this.initBodySize();
  },
};
</script>

<style scoped>
.back_box {
  background: #ccc;
  width: 50vw;
  height: 50vh;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -30%);
}

.drag_box {
  width: 100px;
  height: 100px;
  background: skyblue;
  position: absolute;
  z-index: 10;
  user-select: none; /* 不可选中,为了拖拽时不让文字高亮 */
}
</style>

2、实现滚轮缩放

接着我们来实现鼠标停留在元素上时,通过滚轮来实现缩小放大的功能

我的思路:
①. 通过滚轮事件得到判断滚轮是否在向上或向下滚动;
②. 向上滚动时放大元素,向下滚动时缩小元素,即按缩放量乘以元素的宽高,等比例缩放;
③. 使缩放元素内的子元素跟随父元素缩放比例一同缩放。

老样子,先来了解鼠标滚轮事件onmousewheel……噢不对,是onwheel
在这里插入图片描述
打印一下($event可省略):
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
于是我们发现滚轮向上滚动时wheelDelta>0,向下滚动时wheelDelta<0,以此做为元素缩放的判断依据。

缩放元素的宽高是动态的,于是我们给元素的动态style加上widthheight,并定义两个动态宽高的变量,以及缩放比例zoom
在这里插入图片描述
在这里插入图片描述
elWidthelHeight需要在mounted里初始化,计算方式为:父元素宽(高)的自适应值 * ( 元素宽(高) / 父元素宽(高)) ,我们在此前已经拿到initWidthinitHeight了,我设的元素为宽高100px的正方形,而父元素为宽50vw,高50vh的长方形,于是得到算式:
在这里插入图片描述
接下来写滚轮事件逻辑,按常规来说,我们不能允许元素无止尽地缩放下去,所以当缩放比例达到上下临界值时,需要加个判断,我设它为不能放大超过3倍且不能缩小低于0.5倍。然后将宽高乘以缩放比例,就能得到滚轮事件执行后的新宽高了:
在这里插入图片描述
让我们来看下效果~
在这里插入图片描述

看起来挺nice,但不难发现还有些问题,那就是缩放元素内的子元素/文字/背景图片并没有随着父元素一同缩放。那我们就再坚持一会会,完成这最后的功能~

我们先把这段“这是一个蓝色可拖拽元素”放入一个标签中,毕竟没有外层标签就不好控制样式
在这里插入图片描述

给该标签绝对定位,并加上宽高100px,因为定位后元素就脱离文档流了,必须给元素加个宽高以免文字不换行等内容溢出行为。
再加一个transform-origin,该属性为元素transform的基点,也可用作于缩放基点,X轴与Y轴的值都设为0,因为我们滚轮缩放实质是改变宽高,在改变时基点为元素的坐标(0,0)
由于子元素是随缩放比例动态缩放,所以其他的大小值可以不必写成动态,而在css里写死,如font-size、line-height等等:
在这里插入图片描述

既然提到了缩放,接下来的操作就很明显了,在标签中加入动态的transform: scale实现元素缩放,并且设变量meter_zoom控制缩放倍数。顺带加一个元素定位的位置,该值也需要动态变化,即(位移量 * 父元素动态宽高) / 父元素原宽高
(在案例中我设该子元素向右位移0px,向下位移25px)
在这里插入图片描述
子元素的缩放比例计算方法则为:父元素动态宽高 / 父元素原宽高
在这里插入图片描述
滚轮缩放事件里也要同步添加:
在这里插入图片描述
如此一来就算是全部大功告成啦~

在这里插入图片描述
收工!


demo全部代码:

<template>
  <div class="drag">
    <div class="back_box" ref="back_box">
      这是一个背景
      <div
        class="drag_box"
        draggable="true"
        @dragstart="dragstart"
        @dragend="dragend"
        @wheel="handleWeel"
        :style="`left:${elLeft}px;top:${elTop}px;width:${elWidth}px;height:${elHeight}px;`"
      >
        <div
          class="text"
          :style="`left:${(0 * elWidth) / 100}px;top:${
            (25 * elHeight) / 100
          }px;-webkit-transform: scale(${meter_zoom} )`"
        >
          这是一个蓝色可拖拽元素
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  data() {
    return {
      initWidth: 0, // 父元素的宽-自适应值
      initHeight: 0, // 父元素的高-自适应值
      startclientX: 0, // 元素拖拽前距离浏览器的X轴位置
      startclientY: 0, //元素拖拽前距离浏览器的Y轴位置
      elLeft: 0, // 元素的左偏移量
      elTop: 0, // 元素的右偏移量

      zoom: 1, // 缩放比例
      elWidth: 0, // 元素宽
      elHeight: 0, // 元素高
      meter_zoom: 0, // 子元素缩放比例
    };
  },
  methods: {
    // 页面初始化
    initBodySize() {
      this.initWidth = this.$refs.back_box.clientWidth; // 拿到父元素宽
      // this.initHeight = this.initWidth * (1080 / 1920);
      this.initHeight = this.initWidth * ((1080 * 0.88) / (1920 - 1080 * 0.02)); // 根据宽计算高实现自适应
      this.elWidth = this.initWidth * (100 / (1920 / 2));
      this.elHeight = this.initHeight * (100 / (1080 / 2));
      this.meter_zoom = this.elWidth / 100; // 计算子元素缩放比例
    },
    // 拖拽开始事件
    dragstart(e) {
      console.log(e);
      this.startclientX = e.clientX; // 记录拖拽元素初始位置
      this.startclientY = e.clientY;
    },
    // 拖拽完成事件
    dragend(e) {
      console.log(e);
      let x = e.clientX - this.startclientX; // 计算偏移量
      let y = e.clientY - this.startclientY;
      this.elLeft += x; // 实现拖拽元素随偏移量移动
      this.elTop += y;
    },

    // 滚轮放大缩小事件
    handleWeel(e) {
      console.log(e);
      if (e.wheelDelta < 0) {
        this.zoom -= 0.05;
      } else {
        this.zoom += 0.05;
      }
      if (this.zoom >= 3) {
        this.zoom = 3;
        return;
      }
      if (this.zoom <= 0.5) {
        this.zoom = 0.5;
        return;
      }

      this.elWidth = this.initWidth * (100 / (1920 / 2)) * this.zoom;
      this.elHeight = this.initHeight * (100 / (1080 / 2)) * this.zoom;
      this.meter_zoom = this.elWidth / 100;
    },
  },
  mounted() {
    // console.log(this.$el);
    this.initBodySize();
  },
};
</script>

<style scoped>
.back_box {
  background: #ccc;
  width: 50vw;
  height: 50vh;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -30%);
}

.drag_box {
  width: 100px;
  height: 100px;
  background: skyblue;
  position: absolute;
  z-index: 10;
  user-select: none; /* 不可选中,为了拖拽时不让文字高亮 */
}

.text {
  position: absolute;
  width: 100px;
  height: 100px;
  transform-origin: 0 0; /* 用作缩放基点 */
  font-size: 16px;
}
</style>

喜欢的话请关注+点赞噢~
您的支持就是我的更新动力~~THX

  • 102
    点赞
  • 237
    收藏
    觉得还不错? 一键收藏
  • 40
    评论
评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值