4

低开开发笔记(四):实现编辑器内拖拽 - 养肥胖虎

 4 months ago
source link: https://www.cnblogs.com/FatTiger4399/p/18146367
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

好家伙,

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

完整代码已开源

https://github.com/Fattiger4399/ph-questionnaire.git

 0.效果预览

2501855-20240422163139985-146447408.gif

1.1.视图操作分析

这一块是这一章节最核心的部分

2501855-20240422154536916-1690115034.png

2501855-20240422163722932-710719438.png

 用户进行了什么操作?

(1)点击编辑器中第一个组件

(2)松开

(3)在setter中修改第一个组件的数据

(4)按下第一个组件(不松开鼠标左键)

(5)拖拽(不松开鼠标左键)

(6)到达目标地点(松开鼠标左键)

上述操作中,只有第六个是需要我们进行分析的

2501855-20240422164432388-1712690522.png

 但其实也非常简单,

图片中画红框的区域如何确定呢?

目标区域的位置 小于 第四个组件的开始坐标 加上 一半的第四个组件的高度

并且

目标区域的位置 大于  第四个组件的开始坐标 减去 一半的第三个组件的高度   

换成公式大概长这个样子

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. 移除旧的事件监听器,添加新的事件监听器,并设置元素为可拖拽状态。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK