vue-grid-layout数据可视化图表面板优化过程所遇问题汇总
对于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
整个代码如果用在工程里,肯定会卡死,因为:
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 在元素被拖动时反复触发 dragend 在拖动操作完成时触发 目的地对象 dragenter 当被拖动元素进入目的地元素所占据的屏幕空间时触发 dragover 当被拖动元素在目的地元素内时触发 dragleave 当被拖动元素没有放下就离开目的地元素时触发 整个拖拽事件触发的顺序如下: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可以拆解成多个钩子,方便代码的复用与维护

先写到这吧,后面有时间再理顺一下
vue-grid-layout数据可视化图表面板优化过程所遇问题汇总的更多相关文章
- Webstorm+Webpack+echarts构建个性化定制的数据可视化图表&&两个echarts详细教程(柱状图,南丁格尔图)
Webstorm+Webpack+echarts ECharts 特性介绍 ECharts,一个纯 Javascript 的图表库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(I ...
- Python调用matplotlib实现交互式数据可视化图表案例
交互式的数据可视化图表是 New IT 新技术的一个应用方向,在过去,用户要在网页上查看数据,基本的实现方式就是在页面上显示一个表格出来,的而且确,用表格的方式来展示数据,显示的数据量会比较大,但是, ...
- ECharts-基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表
ECharts http://ecomfe.github.com/echarts 基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表.创新的拖拽重计算 ...
- 数据可视化图表ECharts
介绍: ECharts是一个基于ZRender(轻量级Canvas类库)的纯javascript图表库,提供可交互.个性化的数据可视化图表. ECharts提供了折线图.柱状图.散点图.饼图.K线图, ...
- Excel数据可视化图表设计需要注意的几个问题
大数据发展迅速的时代,数据分析驱动商业决策.对于庞大.无序.复杂的数据要是没经过合适的处理,价值就无法体现. 可以想象一本没有图片的教科书.没有图表.图形或是带有箭头和标签的插图或流程图,那么这门学 ...
- 百度数据可视化图表套件echart实战
最近我一直在做数据可视化的前端工作,我用的最多的绘图工具是d3.d3有点像photoshop,功能很强大,例子也很多,但是学习成本也不低,做项目是需要较大人力投入的.3月底由在亚马逊工作的同学介绍下使 ...
- 数据分析 | 数据可视化图表,BI工具构建逻辑
本文源码:GitHub·点这里 || GitEE·点这里 一.数据可视化 1.基础概念 数据可视化,是关于数据视觉表现形式的科学技术研究.其中,这种数据的视觉表现形式被定义为,一种以某种概要形式抽取出 ...
- 使用vue渲染大量数据时应该怎么优化?
Object.freeze 适合一些 big data的业务场景.尤其是做管理后台的时候,经常会有一些超大数据量的 table,或者一个含有 n 多数据的图表,这种数据量很大的东西使用起来最明显的感受 ...
- 📈📈📈📈📈iOS 图表框架 AAChartKit ---强大的高颜值数据可视化图表框架,支持柱状图、条形图、折线图、曲线图、折线填充图、曲线填充图、气泡图、扇形图、环形图、散点图、雷达图、混合图
English Document
- 一次MySQL两千万数据大表的优化过程,三种解决方案
问题概述 使用阿里云rds for MySQL数据库(就是MySQL5.6版本),有个用户上网记录表6个月的数据量近2000万,保留最近一年的数据量达到4000万,查询速度极慢,日常卡死.严重影响业务 ...
随机推荐
- python 远程操作svn
SVN操作脚本 安装模块 pip install pywinrm 脚本如下 #!/usr/bin/env python3 # coding=utf-8 # author:LJX # describe: ...
- 阿里发布AI编码助手:通义灵码,兼容 VS Code、IDEA等主流编程工具
今天是阿里云栖大会的第一天,相信场外的瓜,大家都吃过了.这里就不说了,有兴趣可以看看这里:云栖大会变成相亲现场,最新招婿鄙视链来了... . 这里主要说说阿里还发布了一款AI编码助手,对于我们开发者来 ...
- sed 原地替换文件时遇到的趣事
哈喽大家好,我是咸鱼 在文章<三剑客之 sed>中咸鱼向大家介绍了文本三剑客中的 sed sed 全名叫 stream editor,流编辑器,用程序的方式来编辑文本 那么今天咸鱼打算讲一 ...
- VMPFC可以融合既有的片段信息来模拟出将来的情感场景
Ventromedial prefrontal cortex supports affective future simulation by integrating distributed knowl ...
- AcWing 178. 第K短路
题意 给定一张 \(N\) 个点(编号 \(1,2-N\)),\(M\) 条边的有向图,求从起点 \(S\) 到终点 \(T\) 的第 \(K\) 短路的长度,路径允许重复经过点或边. 注意: 每条最 ...
- 🔥🔥Java开发者的Python快速实战指南:探索向量数据库之文本搜索
前言 如果说Python是跟随我的步伐学习的话,我觉得我在日常开发方面已经没有太大的问题了.然而,由于我没有Python开发经验,我思考着应该写些什么内容.我回想起学习Java时的学习路线,直接操作数 ...
- 【Android】做一个简单的每日打卡app-day01【还没做好】
任务: 第一阶段目标: 1.用户注册:用户注册信息包括用户ID(学号).用户名(姓名),手机号码,用户单位(班级),用户班级四项基本信息,用户第一次注册后,用户姓名不用每次输入 . 2.每日总结打卡: ...
- extern关键字的用法
extern关键字的理解 extern是C/C++语言中的一个关键字,用于声明一个变量或函数具有外部链接性(external linkage),即这些变量或函数可以被其他文件访问. 在C/C++中,如 ...
- 做数据分析,我们需要懂多少excel知识?
数据分析所需的Excel知识详解 在进行数据分析工作时,Excel是一个非常常用且强大的数据处理工具.以下是数据分析中常用的Excel知识点和技巧的详细描述. 1. 基本操作 在使用Excel进行数据 ...
- Net 高级调试之十二:垃圾回收机制以及终结器队列、对象固定
一.简介 今天是<Net 高级调试>的第十二篇文章,这篇文章写作时间的跨度有点长.这篇文章我们主要介绍 GC 的垃圾回收算法,什么是根对象,根对象的存在区域,我们也了解具有析构函数的对象是 ...