irpas技术客

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

irpas 3566

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

项目中的实际效果: 小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属性一起使用的事件——ondragstart和ondragend,它们的定义分别为: ①. ondragstart 事件在用户开始拖动元素或选择的文本时触发 ②. ondragend 事件在用户完成元素或首选文本的拖动时触发。

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

我们先在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加上width和height,并定义两个动态宽高的变量,以及缩放比例zoom elWidth和elHeight需要在mounted里初始化,计算方式为:父元素宽(高)的自适应值 * ( 元素宽(高) / 父元素宽(高)) ,我们在此前已经拿到initWidth和initHeight了,我设的元素为宽高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 * this.elWidth) / 100}px;top:${ (25 * this.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


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Vue #元素移动 #ampltdiv