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

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

summaryproblemsencounteredvue-grid-layoutvue

对于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):

1.jpg

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

4.jpg

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

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

为了性能,The project itself is upgraded tovue3,Because the whole project usesTSX,My modified version:https://github.com/zhoulujun/vue3-grid-layout

Look at the case code:https://github.com/jbaysolutions/vue-grid-layout/blob/master/website/docs/.vuepress/components/Example10DragFromOutside.vue

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,实现很麻烦:

WX20230125-162233@2x.jpg

首先,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,比如

js请求以下style信息时,触发回流(浏览器会立刻清空队列:)

  • clientWidth、clientHeight、clientTop、clientLeft

  • offsetWidth、offsetHeight、offsetTop、offsetLeft

  • scrollWidth、scrollHeight、scrollTop、scrollLeft

  • width、height

  • getComputedStyle()

  • getBoundingClientRect()

具体查看:《chromeOptimize for page repaints and reflows and optimizations》:https://www.zhoulujun.cn/html/webfront/browser/webkit/2016_0506_7820.html

这个在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在元素被拖动时反复触发
 dragend在拖动操作完成时触发
   
目的地对象dragenter当被拖动元素进入目的地元素所占据的屏幕空间时触发
 dragover当被拖动元素在目的地元素内时触发
 dragleave当被拖动元素没有放下就离开目的地元素时触发

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

https://www.zhoulujun.cn/html/webfront/SGML/html5/2016_0124_434.html

理解了这个, 其实直接在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.getData()在dragover,dragenter,dragleave中无法获取数据的问题

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

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.

具体查看官方文档: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 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.

 

drop事件不触发:

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

drop:源对象拖放到目标对象中,目标对象完全接受被拖拽对象时触发,可理解为在目标对象内松手时触发.

dragenter和dragover事件的默认行为是拒绝接受任何被拖放的元素.因此,我们必须阻止浏览器这种默认行为.e.preventDefault();

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

 

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

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

2.jpg

 

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

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;
el.dragging.data = { 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 {
    layout,
    gridLayoutRef,
    gridItemRefs,
  } = 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) {
    e.preventDefault();
    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({
        ...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('Mouse leaves the editor area————————');
      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,
  };
}

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

版权声明:本文为[zhoulujun]所创,转载请带上原文链接,感谢。 https://qdmana.com/2023/025/202301251942110439.html