• 低开开发笔记(四):实现编辑器内拖拽


    好家伙,

    本篇我们来说说,编辑器内如何实现拖拽

    完整代码已开源

    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 < phoffsetTopbox[i + 1] + 0.5 * phoffsetHeightbox[i + 1]

    放到四个后面。。。放到第五个后面。。。

    后面的情况以此类推

     

    好了

     

    1.2.数据操作分析

    编辑器本身就是按数据的顺序渲染的,所以,只要排好序,然后更新渲染器就可以了

     

     

    2.开始操作

    我们需要用到的一些坐标 

    事件对象e的一些属性

    • isTrusted: 表示事件是否是由用户操作触发的,如果是由脚本创建的事件,则为 false,如果是由用户操作触发的则为 true
    • altKeyctrlKeymetaKeyshiftKey: 分别表示是否按下了 Alt、Ctrl、Meta、Shift 键。
    • bubbles: 表示该事件是否会冒泡。
    • button: 表示按下的是哪个鼠标按钮(左键为 0,中键为 1,右键为 2)。
    • clientXclientY: 表示鼠标指针在视口中的坐标。
    • pageXpageY: 表示鼠标指针相对于页面的坐标。
    • screenXscreenY: 表示鼠标指针相对于屏幕的坐标。
    • target: 表示事件的目标元素。
    • type: 表示事件的类型,这里是 "dragend"。
    • timeStamp: 表示事件发生的时间戳。
    • xy: 与 clientXclientY 相同,表示鼠标指针在视口中的坐标。
    • 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 < phoffsetTopbox[i + 1] + 0.5 * phoffsetHeightbox[i + 1]) {
                                    i++;
                                    break;
                                } else if (mouseupY > 0 && mouseupY < phoffsetTopbox[i] + 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 < phoffsetTopbox[i - 1] - 0.5 * phoffsetHeightbox[i - 1]) {
                                    i = i - 2;
                                    break;
                                }
                                //上半部分
                                else if (mouseupY < phoffsetTopbox[i] && mouseupY > phoffsetTopbox[i - 2] + 0.5 * phoffsetHeightbox[i - 2]) {
                                    break;
                                } else if (mouseupY < phoffsetTopbox[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.dragStartHandler);
                childElements[sameid].addEventListener('dragend', this.dragEndHandler);
    
                //更新视图
                this.$refs.editor.$forceUpdate();
            },
    复制代码

     

    1. 首先,根据传入的 config 对象,更新选中的样式,将选中的组件标记为 config.dsl

    2. 然后,通过获取编辑器元素和选中组件的类名,找到所有符合条件的子元素并保存在 childElements 中。

    3. 根据选中组件的 wid 属性计算出 sameid,用于定位对应的子元素。

    4. 通过筛选出以 ph- 开头的 div 元素,计算出这些元素的 offsetTop 和 offsetHeight,并保存在 phoffsetTopbox 和 phoffsetHeightbox 数组中。

    5. 定义了两个事件处理函数 dragStartHandler 和 dragEndHandler,分别处理拖拽开始和拖拽结束的逻辑。

    6. 在拖拽结束时,根据鼠标在 Y 轴上的位置,判断拖拽的方向(向上或向下),并根据计算得到的位置信息,将选中的组件插入到新的位置上。

    7. 移除旧的事件监听器,添加新的事件监听器,并设置元素为可拖拽状态。

     

  • 相关阅读:
    设计模式_迭代器模式
    一、springcloud-eureka服务注册与发现
    ScrollView 源码注解
    节点异常检测 node-problem-detector
    Google guava之SortedMultiset简介说明
    【Hadoop】学习笔记(六)
    【论文笔记】policy-space response oracles (PSRO)
    LeetCode——622.设计循环队列
    小程序页面导航和页面事件
    【行为型模式】模板方法模式
  • 原文地址:https://www.cnblogs.com/FatTiger4399/p/18146367