vue-grid-layout数据可视化图表面板优化过程所遇问题汇总

news2025/1/13 11:45:26

对于drag事件不熟悉的,请先阅读:《drag事件详解:html5鼠标拖动排序及resize实现方案分析及实践

之前老项目grafana面板,如下图所示(GEM添加图表是直接到图表编辑,编辑完成后自动插入到面板最后):

产品希望做成从左侧拖曳进入,所见即所得,如图所示:

这个vue-grid-layout 本身就是支持:

https://jbaysolutions.github.io/vue-grid-layout/guide/10-drag-from-outside.html

为了性能,项目本身升级到vue3,因为整个项目采用TSX,本人改造的版本: https://github.com/zhoulujun/vue3-grid-layout

看了下案例代码:https://github.com/jbaysolutions/vue-grid-layout/blob/master/website/docs/.vuepress/components/Example10DragFromOutside.vue

整个代码如果用在工程里,肯定会卡死,因为:

drag: function (e) {

let parentRect = document.getElementById('content').getBoundingClientRect();

}

这个代码为什么不行?首先这个里面拖动计算直接在drag事件里面做的,其次这个案例drogover 是绑定在body上面,如果组件里面也需要接收左侧的拖曳组件,实现很麻烦:

首先,我们解决卡顿问题,其中比较隐蔽的是回流问题,造成掉帧严重

回流问题:

其实很多初级的前端同学只知道JS改变CSS会让浏览器回流,其实JS读取某些属性也会让浏览器回流,比如

js请求以下style信息时,触发回流(浏览器会立刻清空队列:)
clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
width、height
getComputedStyle()
getBoundingClientRect()
具体查看:《 chrome对页面重绘和回流以及优化进行优化 》: https://www.zhoulujun.cn/html/webfront/browser/webkit/2016_0506_7820.html

这个在drag里面即使加了防抖,组件多了照样会卡死页面的。

还有有些实现还使用了Bus 透传 drag/dragend 事件,其实这里可能没有理解 :


整个拖拽事件触发的顺序如下:dragstart-> drag -> dragenter -> dragover -> dragleave -> drop ->dragend
https://www.zhoulujun.cn/html/webfront/SGML/html5/2016_0124_434.html

理解了这个, 其实直接在dragover 做就可以了,这个案例给很多开源项目做了些误导哈*_*

既然

整个拖拽事件触发的顺序如下:dragstart-> drag -> dragenter -> dragover -> dragleave -> drop ->dragend,

那么在dragstart时在dataTransfer.setData,在后续的过程中直dataTransfer.getData读取就行行了吗?

dataTransfer.getData()在dragover,dragenter,dragleave中无法获取数据的问题

dataTransfer.getData()在dragover,dragenter,dragleave中无法获取数据的问题

dataTransfer.setData()中所设置的数据是存储在drag data store中,而根据W3C标准,drag data store有三种模式,Read/write mode, Read-only mode跟Protected mode。

W3C Working Draft中5.7.2.关于三种drag data store mode的定义

A drag data store mode, which is one of the following:
Read/write mode(读/写模式)
For the dragstart event. New data can be added to the drag data store.
读/写模式,在dragstart事件中使用,可以添加新数据到drag data store中。

Read-only mode(只读模式)
For the drop event. The list of items representing dragged data can be read, including the data. No new data can be added.
在drop事件中使用,可以读取被拖拽数据,不可添加新数据。

Protected mode(保护模式)

For all other events. The formats and kinds in the drag data store list of items representing dragged data can be enumerated, but the data itself is unavailable and no new data can be added.
在所有其他的事件中使用,数据的列表可以被枚举,但是数据本身不可用且不能添加新数据。
具体查看官方文档: https://html.spec.whatwg.org/multipage/dnd.html#drag-data-store

这样就可以解释为什么dragover中dataTransfer.getData()返回的数据为空,以及在dragover时dataTransfer中的types不为0了,因为在除了dragstart,drop以外的事件,包括dragover,dragenter,dragleave中,drag data store出于安全原因处于保护模式,因此不可访问

如果要实现dragover中访问dragstart中设置的数据,可以采用定义一个全局变量的方法,在dragstart中赋值,之后在dragend中清空。

另外,我在ondragover时,尝试给被拖拽元素添加class以改变其样式发现,虽然拖拽时class已经改变,但在拖拽过程中样式并没有改变,而是等到拖拽动作完成后,也就是drop之后样式才被应用上去,所以在dragover,dragenter,dragleave中做得更多的应该是对数据的处理,而不是应用效果。

drop事件不触发:

在发现页面拖动过程中,drop事件不触发,重新了看了下《drag事件详解:html5鼠标拖动排序及resize实现方案分析及实践

drop:源对象拖放到目标对象中,目标对象完全接受被拖拽对象时触发,可理解为在目标对象内松手时触发。
dragenter和dragover事件的默认行为是拒绝接受任何被拖放的元素。因此,我们必须阻止浏览器这种默认行为。e.preventDefault();

如果drop接收盒子要想接收到元素,那么接收的拖动元素 dragenter和dragover必须阻止默认行为。

发行也阻止默认事件了,但是我使用了节流事件,发现不行:

把 e.preventDefault()提取出来就可以,代码如下:

clientX、offsetX、screenX、pageX、x、y、clientLeft、clientTop区别

整体部分可以参看:《再谈BOM和DOM(6):dom对象及event对象位值计算—如offsetX/Top,clentX

  • clientX、clientY:点击位置距离当前body可视区域的x,y坐标

  • pageX、pageY:对于整个页面来说,包括了被卷去的body部分的长度

  • screenX、screenY:点击位置距离当前电脑屏幕的x,y坐标

  • offsetX、offsetY:相对于带有定位的父盒子的x,y坐标

所以在drogover 中,直接获取offsetY、offsetX 即可:

const { offsetY: top, offsetX: left } = e;

el.dragging.data = { top, left };

const new_pos = el.calcXY(top, left);

这样其实是很方便的

整体实现:

代码23年中应该会全部提交github,这里把拖动的钩子函数贴出来供参考下

import { onUnmounted } from 'vue';

import { BaseHooksData } from '@dashboard/grid-panel/hooks/useHooks';

import { IGridPos, PanelModel } from '@/typings';

import useDashboardModuleStore, { getNewPanel } from '@store/dashboard';

import { initPanel, VIRTUAL_ROOT } from '@/constants';

import { throttle } from 'lodash';

import { deepClone } from '@/utils';

import usePanelEditorStore from '@store/panelEditor';

import createUID from '@/utils/createUID';

export default function useDragMove(

data: Partial<BaseHooksData>,

getLayout: () => void,

editChart: (panel: PanelModel) => void,

) {

const {

layout,

gridLayoutRef,

gridItemRefs,

} = data;

const PanelEditorModule = usePanelEditorStore();

const DashboardModule = useDashboardModuleStore();

// 移动的临时组件

let panel: PanelModel = null;

let dragPos: IGridPos;

onUnmounted(() => {

dragPos = null;

panel = null;

});

/**

* 图表拖到仪表盘,穿件图表

* @param e

*/

function dragenter(e: DragEvent) {

e.preventDefault();

console.log('————————鼠标进入编辑器区域');

if (layout.value.findIndex(item => item.i === 'drop') === -1) {

const { addPanelData = deepClone(initPanel), addPanelType: type } = PanelEditorModule;

const { space_uid } = DashboardModule.dashboard;

panel = getNewPanel(type, new PanelModel({

...addPanelData,

uid: 'drop',

type,

space_uid,

}));

dragPos = panel.gridPos;

layout.value.push(dragPos);

}

}

const dragoverThrottle = throttle((e: DragEvent) => {

const index = layout.value.findIndex(item => item.i === 'drop');

if (index === -1) {

return;

}

const el = gridItemRefs.value[index];

if (!el) {

return;

}

const { offsetY: top, offsetX: left } = e;

el.dragging.data = { top, left };

const new_pos = el.calcXY(top, left);

const { h, w } = panel.gridPos;

gridLayoutRef.value.dragEvent('dragstart', 'drop', new_pos.x, new_pos.y, h, w);

dragPos.x = layout.value[index].x;

dragPos.y = layout.value[index].y;

}, 300);

function dragover(e: DragEvent) {

e.preventDefault();

dragoverThrottle(e);

}

function leaveDragArea(refresh = true) {

const { x, y, h, w } = dragPos;

gridLayoutRef.value.dragEvent('dragend', 'drop', x, y, h, w);

// 强制隐藏placeholder

let t = setTimeout(() => {

if (gridLayoutRef.value.isDragging) {

gridLayoutRef.value.isDragging = false;

}

clearTimeout(t);

t = null;

}, 100);

if (refresh) {

panel = null;

layout.value = layout.value.filter(item => item.i !== 'drop');

}

}

function dragleave(e: DragEvent) {

console.log('dragleave');

const { offsetX, offsetY, clientX, clientY } = e;

if (!((clientX === 0 && clientY === 0) && (offsetX < 0 && offsetY < 0))) {

console.log('鼠标离开编辑器区域————————');

leaveDragArea();

}

}

function drop(e: DragEvent) {

console.log('drop');

e.preventDefault();

const { type } = panel;

leaveDragArea(false);

if (['row', 'tab', 'column'].includes(type)) {

const uid = createUID();

panel.uid = uid;

panel.gridPos.i = uid;

DashboardModule.addCharts([panel]);

} else {

panel.uid = VIRTUAL_ROOT;

panel.gridPos.i = VIRTUAL_ROOT;

DashboardModule.addCharts([panel]);

editChart(panel);

}

getLayout();

}

return {

dragenter,

dragover,

dragleave,

drop,

};

}

这是其中拖曳的部分,其中的drop 钩子,可以在tab、swiper、column组件中使用。

代码优化

工程上,当然还得对代码进行拆解,整个仪表盘差不多5000多行代码,vue3可以拆解成多个钩子,方便代码的复用与维护

先写到这吧,后面有时间再理顺一下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/178931.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flume集群安装部署、Kafka集群安装部署以及Maxwell安装部署实战

1、Flume集群安装部署 1.1、安装地址 Flume官网地址&#xff1a;http://flume.apache.org/文档查看地址&#xff1a;http://flume.apache.org/FlumeUserGuide.html下载地址&#xff1a;http://archive.apache.org/dist/flume/ 1.2、安装部署 将apache-flume-1.9.0-bin.tar.…

Redis实现笔记点赞排行榜 | 黑马点评-达人探店

一、发布探店笔记 探店笔记类似点评网站的评价&#xff0c;往往是图文结合。对应的表有两个 探店笔记表&#xff08;主键、商户id、用户id、标题、文字、图片、探店文字描述、点赞数量、评论数量&#xff09;评价表&#xff08;笔记的评价&#xff09;先上传图片请求一次保存…

【Java基础】--Java排序

【Java基础】--Java排序1、选择排序(Select Sort)2、冒泡排序(Bubble Sort)3、插入排序(Insert Sort)4、希尔排序(Shell Sort)5、归并排序(Merge Sort)6、快速排序(Quick Sort)7、堆排序(Heap Sort)小结待排序的元素需要实现 Java 的 Comparable 接口&#xff0c;该接口有 com…

Facebook SEO中参与度的重要性

参与度&#xff0c;也就是大家所说的浏览量&#xff0c;在 Facebook SEO中也叫做页面访问者参与度。一般来说&#xff0c; Facebook的用户在上面停留的时间越长代表着它在用户心目中的形象越好&#xff0c;这也是为什么 Facebook上的访客愿意打开自己的 Facebook页面让好友帮忙…

使用vite构建vue3项目详细介绍(ts+pinia+sass+vue-router+axios+element-plus)

使用vite构建vue3项目详细介绍(tspiniasassvue-routeraxioselement-plus) 1. 创建项目 npm init vitelatest 2. 配置 vite.config.ts path需要安装--npm install types/node --save-dev import vue from vitejs/plugin-vue; import { resolve } from path; import { defineC…

Jupyter的安装与默认目录的切换

下载与安装 清华大学开源软件镜像站 使用国内镜像下载更快&#xff0c;官网下载很慢 下载msi镜像文件&#xff0c;打开安装&#xff1a; 安装完成后得到4个文件 Reset Spyder...和Anaconda Powershell....都是相应的配置&#xff0c;其中后者是Jupyter和anaconda的dos命令窗口…

Coolify系列02-从0到1超详细手把手教你上手Coolify

重启 如果由于某种原因&#xff0c;你的实例崩溃了&#xff0c;你可以用下面的命令重新启动它: wget -q https://get.coollabs.io/coolify/install.sh \ -O install.sh; sudo bash ./install.sh -r防火墙设置 您需要在防火墙中允许以下端口 Coolify: 3000 (required)Revers…

【Mysql】Mysql的存储引擎

【Mysql】Mysql的存储引擎 文章目录【Mysql】Mysql的存储引擎1.概述2. 特点2.1 InnoDB2.2 MyISAM2.3 Memory2.4 区别3. 选择1.概述 **存储引擎&#xff1a;存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。**存储引擎是基于表的&#xff0c;而不是 基于库的&…

HTTPS工作原理详解加密(TLS握手)过程

HTTPS概念 HTTPS就是一个有安全保障的HTTP通信&#xff0c;我们都知道&#xff0c;http是明文传输的&#xff0c;http报文是人肉眼就可识别的ASCII码&#xff0c;在通信过程中&#xff0c;http报文很容易被黑客窃听、篡改、伪造&#xff0c;而在互联网交易中&#xff0c;我们必…

【1】初识Linux

学习笔记目录 学习教程&#xff1a;B站 “黑马程序员” 初识Linux--入门Linux基础命令--会用Linux权限管控--懂权限Linux实用操作--熟练实战软件部署--深入掌握脚本&自动化--用的更强项目实战--学到经验云平台技术--紧跟潮流 操作系统概述 1.计算机由硬件和软件两个主要…

AcWing 12. 背包问题求具体方案

AcWing 12. 背包问题求具体方案AcWing 12. 背包问题求具体方案&#xff08;1&#xff09;问题&#xff08;2&#xff09;分析&#xff08;3&#xff09;代码AcWing 12. 背包问题求具体方案 &#xff08;1&#xff09;问题 &#xff08;2&#xff09;分析 我们先看一下这道题中…

tomcat更改默认端口

如下图把conf目录下的server.xml的下图所示处由原来的8080改为需要的即可&#xff1b;当前改为8087&#xff1b;保存之后重启tomcat&#xff1b; 网上一个资料说如果要运行2个tomcat&#xff0c;把下图的8009改为自己的&#xff0c;例如18009&#xff0c; 把下图的8005改为自己…

pythpon基础:创建文件索引升级版

需求环境 这是公司的一个需求&#xff0c;有一份很庞大的数据由好多视频文件组成&#xff0c;总共有12T左右&#xff0c;视频来源是一些下载的视频素材。每隔一段时间就要将一部分筛选好的视频文件剪切到server02服务器进行转码&#xff0c;筛选没被选中的文件将被删除。从下载…

5. 蒙特卡洛方法

蒙特卡洛方法5. 离轨策略5.1 策略评估&#xff08;基于重要度采样&#xff09;5.1.1 计算目标策略下的状态值5.1.2 蒙特卡洛算法&#xff08;状态值估计&#xff09;5.1.3 增量式的实现5.2 策略迭代5.3 减少重要性采样方差的方法5.3.1 折扣敏感的重要性采样5.3.2 每决策重要性抽…

go 语言 string 类型思考

string 作为 go 语言中的基础类型&#xff0c;其实有一些需要反复揣摩的&#xff0c;可能是我们使用的场景太简单&#xff0c;也可能是我们不需要那可怜的一点优化来提高性能&#xff0c;对它也就没那么上心了。 文章运行环境&#xff1a;go version go1.16.6 darwin/amd64 并…

浅析Java中的final关键字

一.final关键字的基本用法 在Java中&#xff0c;final关键字可以用来修饰类、方法和变量&#xff08;包括成员变量和局部变量&#xff09;。下面就从这三个方面来了解一下final关键字的基本用法。 1.修饰类 当用final修饰一个类时&#xff0c;表明这个类不能被继承。也就是说&a…

ACL访问控制的基本实例

典型案例&#xff1a; 配置需求∶ 在Router上部署基本ACL后&#xff0c;ACL将试图穿越Router的源地址为192.168.1.0/24网段的数据包过滤掉&#xff0c;并放行其他流量&#xff0c;从而禁止192.168.1.0/24网段的用户访问Router右侧的服务器网络。 配置&#xff1a; 1、Router已…

第三章 变量

一、数据类型&#xff08;P40&#xff09; 每一种数据都定义了明确的数据类型&#xff0c;在内存中分配了不同大小的内存空间(字节)。二、整数类型 整型的使用细节&#xff1a; &#xff08;1&#xff09;Java 各整数类型有固定的范围和字段长度&#xff0c;不受具体OS【操作系…

Java多线程(四)——ThreadPoolExecutor源码解析

ThreadPoolExecutor源码解析 多线程场景下&#xff0c;手动创建线程有许多缺点&#xff1a; 频繁创建、销毁线程会消耗大量 CPU 资源&#xff0c;销毁线程后需要被回收&#xff0c;对 GC 垃圾回收也有一定的压力 使用线程池有许多好处&#xff1a; 降低 CPU 资源消耗。通过…

Linux运维之解决服务器挖矿木马问题

文章目录1 挖矿木马1.1 定义1.2 挖矿特征1.3 解决挖矿木马1.3.1 阻断异常网络通信&#xff08;非必需&#xff09;1.3.2 清除定时任务1.3.3 清除启动项1.3.4 清除SSH公钥1.3.5 清除木马进程1.4 其他常见问题1.4.1 清除木马后又100%1.4.2 CPU占用100%却看不到进程1 挖矿木马 1.…