好家伙,
本篇我们来说说,编辑器内如何实现拖拽
完整代码已开源
https://github.com/Fattiger4399/ph-questionnaire.git
0.效果预览
1.思路
1.1.视图操作分析
这一块是这一章节最核心的部分
到
用户进行了什么操作?
(1)点击编辑器中第一个组件
(2)松开
(3)在setter中修改第一个组件的数据
(4)按下第一个组件(不松开鼠标左键)
(5)拖拽(不松开鼠标左键)
(6)到达目标地点(松开鼠标左键)
上述操作中,只有第六个是需要我们进行分析的
但其实也非常简单,
图片中画红框的区域如何确定呢?
目标区域的位置 小于 第四个组件的开始坐标 加上 一半的第四个组件的高度
并且
目标区域的位置 大于 第四个组件的开始坐标 减去 一半的第三个组件的高度
换成公式大概长这个样子
mouseupY > phoffsetTopbox[i] + 0.5 * phoffsetHeightbox[i] && mouseupY 1] + 0.5 * phoffsetHeightbox[i + 1]
放到四个后面。。。放到第五个后面。。。
后面的情况以此类推
好了
1.2.数据操作分析
编辑器本身就是按数据的顺序渲染的,所以,只要排好序,然后更新渲染器就可以了
2.开始操作
我们需要用到的一些坐标
事件对象e的一些属性
isTrusted
: 表示事件是否是由用户操作触发的,如果是由脚本创建的事件,则为false
,如果是由用户操作触发的则为true
。altKey
,ctrlKey
,metaKey
,shiftKey
: 分别表示是否按下了 Alt、Ctrl、Meta、Shift 键。bubbles
: 表示该事件是否会冒泡。button
: 表示按下的是哪个鼠标按钮(左键为 0,中键为 1,右键为 2)。clientX
,clientY
: 表示鼠标指针在视口中的坐标。pageX
,pageY
: 表示鼠标指针相对于页面的坐标。screenX
,screenY
: 表示鼠标指针相对于屏幕的坐标。target
: 表示事件的目标元素。type
: 表示事件的类型,这里是 “dragend”。timeStamp
: 表示事件发生的时间戳。x
,y
: 与clientX
,clientY
相同,表示鼠标指针在视口中的坐标。offsetX
: 表示鼠标指针位置相对于触发事件的对象的 X 坐标。换句话说,它是鼠标指针距离事件目标元素的左侧边缘的像素距离。offsetY
: 表示鼠标指针位置相对于触发事件的对象的 Y 坐标。它是鼠标指针距离事件目标元素的顶部边缘的像素距离。
3.代码分析
(以下仅分析关键代码,完整代码请参考)
select(config) { // 去除所有选中样式 this.dsl = removeChildrenBorder(this.dsl); // 添加选中样式 config.dsl = addChildrenBorder(config.dsl); this.model.selected = config.dsl; // 组件拖拽处理 const editorElement = this.$refs.editor.$el; const node = `div.${this.model.selected.component}`; const allElements = editorElement.querySelectorAll('div'); const childElements = editorElement.querySelectorAll(node); let sameid = this.model.selected.wid - 1; // 筛选出以 ph- 开头的 div 元素 const phElements = Array.from(allElements).filter(div => { // 获取元素的类名数组 const classList = div.classList; // 检查是否有任何类名以 'ph-' 开头 return Array.from(classList).some(className => className.startsWith('ph-')); }); let phoffsetTopbox = [] let phoffsetHeightbox = [] // 打印出所有匹配的元素 phElements.forEach((item) => { phoffsetTopbox.push(item.offsetTop) phoffsetHeightbox.push(item.offsetHeight) }) // 定义事件处理函数,并保存引用 this.dragStartHandler = (e) => { }; this.dragEndHandler = (e) => { // 注意:这里不需要移除事件监听器,因为它们会在select方法开始时被移除 // console.log(e); // console.log("mouseupY轴 " + e.offsetY); //进行位置交换操作 let newdsl; if (e.offsetY) { const arraylength = this.dsl.children.length; console.log(arraylength, phoffsetTopbox) //将一号位组件拖拽到三号位上方 //拖拽大约为范围为150-250px let i, j; let overdragid; let using_id = this.model.selected.wid; //phoffsetTopbox //phoffsetHeightbox const mouseupY = phoffsetTopbox[using_id - 1] + e.offsetY; const childlength = this.dsl.children.length服务器托管 console.log(mouseupY) //向下 if (e.offsetY > 0) { for (i = using_id; i this.dsl.children.length;) { // console.log(i) // console.log(phoffsetTopbox[i], phoffsetHeightbox[i]) //向下 if (mouseupY > phoffsetTopbox[i] + 0.5 * phoffsetHeightbox[i] && mouseupY 1] + 0.5 * phoffsetHeightbox[i + 1]) { i++; break; } else if (mouseupY > 0 && mouseupY 0.5 * phoffsetHeightbox[i]) { break; } else if (mouseupY > phoffsetTopbox[childlength - 1] + 0.5 * phoffsetHeightbox[childlength - 1]) { i = childlength; break; } else { i++; } } } else { //向上 for (i = using_id; i > 0;) { // console.log(i) console.log(mouseupY) if (mouseupY > phoffsetTopbox[i - 2] - 0.5 * phoffsetHeightbox[i - 2] && mouseupY 1] - 0.5 * phoffsetHeightbox[i - 1]) { i = i - 2; break; } //上半部分 else if (mouseupY phoffsetTopbox[i - 2] + 0.5 * phoffsetHeightbox[i - 2]) { break; } else if (mouseupY 0] + 0.5 * phoffsetHeightbox[0]) { i = 0; break; } else { i--; } } } console.log(this.dsl.children) if (this.model.selected.wid != i) { //复制 let copied_element = this.dsl.children[this.model.selected.wid - 1] // //插入 this.dsl.children.splice(i, 0, copied_element) //删除 this.dsl.children.splice(using_id - 1, 1) } console.log(i) } }; // 在添加新的监听器之前,先移除旧的监听器 if (childElements[sameid]) { childElements[sameid].removeEventListener('dragstart', this.dragStartHandler); childElements[sameid].removeEventListener('dragend', this.dragEndHandler); } // 添加新的监听器 childElements[sameid].setAttribute("draggable", "true"); childElements[sameid].addEventListener('dragstart', this.dragSt服务器托管artHandler); childElements[sameid].addEventListener('dragend', this.dragEndHandler); //更新视图 this.$refs.editor.$forceUpdate(); },
-
首先,根据传入的
config
对象,更新选中的样式,将选中的组件标记为config.dsl
。 -
然后,通过获取编辑器元素和选中组件的类名,找到所有符合条件的子元素并保存在
childElements
中。 -
根据选中组件的
wid
属性计算出sameid
,用于定位对应的子元素。 -
通过筛选出以
ph-
开头的 div 元素,计算出这些元素的offsetTop
和offsetHeight
,并保存在phoffsetTopbox
和phoffsetHeightbox
数组中。 -
定义了两个事件处理函数
dragStartHandler
和dragEndHandler
,分别处理拖拽开始和拖拽结束的逻辑。 -
在拖拽结束时,根据鼠标在 Y 轴上的位置,判断拖拽的方向(向上或向下),并根据计算得到的位置信息,将选中的组件插入到新的位置上。
-
移除旧的事件监听器,添加新的事件监听器,并设置元素为可拖拽状态。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
第七章 前端工程化_2 六、Vue3视图渲染技术 6.1 模版语法 6.1.1 插值表达式和文本渲染 6.1.2 Attribute属性渲染 6.1.3 事件的绑定 6.2 响应式基础 6.2.1 响应式需求案例 6.2.2 响应式实现关键字ref 6.2.3…