Summary of problems encountered in vue-grid-layout data visualization chart panel optimization process

zhoulujun 2023-01-25 20:05:22 阅读数:370


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

old projectgrafana面板,如下图所示(GEMAdding a chart is direct to the chart editor,Automatically inserted to the end of the panel after editing):


The product is expected to be dragged and entered from the left,所见即所得,如图所示:


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

为了性能,The project itself is upgraded tovue3,Because the whole project usesTSX,My modified version:

Look at the case code:

If the entire code is used in the project,肯定会卡死,因为:

drag: function (e) {
    let parentRect = document.getElementById('content').getBoundingClientRect();

Why is this code not working?First of all, drag and calculate directly in thisdragmade in the event,Second this casedrogover 是绑定在body上面,If the component also needs to receive the dragged component on the left,实现很麻烦:


首先,We solve the Caton problem,The more hidden one is the backflow problem,severe frame drop

reflow problem:

Many of the front end of the primary students only knowJS改变CSSwill cause the browser to reflow,其实JSReading certain properties also causes the browser to reflow,比如


  • clientWidth、clientHeight、clientTop、clientLeft

  • offsetWidth、offsetHeight、offsetTop、offsetLeft

  • scrollWidth、scrollHeight、scrollTop、scrollLeft

  • width、height

  • getComputedStyle()

  • getBoundingClientRect()

具体查看:《chromeOptimize for page repaints and reflows and optimizations》:

这个在dragEven with anti-shake inside,If there are too many components, the page will still be stuck.

Some implementations also useBus 透传 drag/dragend 事件,In fact, there may not be understanding here :


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

理解了这个, 其实直接在dragover 做就可以了,This case has misled many open source projects.*_*


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

那么在dragstart时在dataTransfer.setData,in the subsequent processdataTransfer.getDataIs it okay to read it??



dataTransfer.setData()The data set in is stored indrag 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事件中使用,New data can be added todrag 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事件中使用,Can read dragged data,No new data can be added.

  • 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.

    • In all other events use,A list of data can be enumerated,But the data itself is not available and new data cannot be added.


这样就可以解释为什么dragover中dataTransfer.getData()返回的数据为空,以及在dragover时dataTransfer中的types不为0了,因为在除了dragstart,drop以外的事件,包括dragover,dragenter,dragleave中,drag data storeIn protected mode for security reasons,因此不可访问.

如果要实现dragover中访问dragstart中设置的数据,You can use the method of defining a global variable,在dragstart中赋值,之后在dragend中清空.

另外,我在ondragover时,Try to add to the dragged elementclassto change its style to find,Although draggingclass已经改变,But in the process of drag and drop style has not changed,Instead, wait until the drag action completes,也就是dropThe styles are then applied,所以在dragover,dragenter,dragleaveWhat to do more in should be the processing of data,instead of applying the effect.



During discovery page dragging,drop事件不触发,re-read《drag事件详解:html5鼠标拖动排序及resize实现方案分析及实践



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


The release also blocks the default event,But i used throttling event,发现不行:

把 e.preventDefault()Just extract it,代码如下:




The whole part can be seen:《再谈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; = { top, left };
const new_pos = el.calcXY(top, left);

This is actually very convenient



代码23All should be submitted mid-yeargithub,Paste the dragging hook function here for reference

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 {
  } = data;
  const PanelEditorModule = usePanelEditorStore();
  const DashboardModule = useDashboardModuleStore();
  // Moved Temporary Components
  let panel: PanelModel = null;
  let dragPos: IGridPos;
  onUnmounted(() => {
    dragPos = null;
    panel = null;

   * Drag the chart to the dashboard,Wear a chart
   * @param e
  function dragenter(e: DragEvent) {
    console.log('————————Mouse enters the editor area');
    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({
        uid: 'drop',
      dragPos = panel.gridPos;
  const dragoverThrottle = throttle((e: DragEvent) => {
    const index = layout.value.findIndex(item => item.i === 'drop');
    if (index === -1) {
    const el = gridItemRefs.value[index];
    if (!el) {
    const { offsetY: top, offsetX: left } = e; = { 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) {

  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;
      t = null;
    }, 100);
    if (refresh) {
      panel = null;
      layout.value = layout.value.filter(item => item.i !== 'drop');

  function dragleave(e: DragEvent) {
    const { offsetX, offsetY, clientX, clientY } = e;
    if (!((clientX === 0 && clientY === 0) && (offsetX < 0 && offsetY < 0))) {
      console.log('Mouse leaves the editor area————————');

  function drop(e: DragEvent) {
    const { type } = panel;
    if (['row', 'tab', 'column'].includes(type)) {
      const uid = createUID();
      panel.uid = uid;
      panel.gridPos.i = uid;
    } else {
      panel.uid = VIRTUAL_ROOT;
      panel.gridPos.i = VIRTUAL_ROOT;

  return {

Here's the dragging part of it,其中的drop 钩子,可以在tab、swiper、column组件中使用.


工程上,Of course, the code has to be disassembled,Almost the entire dashboard5000多行代码,vue3Can be disassembled into multiple hooks,Facilitate code reuse and maintenance

c62d369e384e4a6199b9147f8035672b copy.jpg


先写到这吧,I have time to sort it out later