首页 > 其他分享 >vue-grid-layout数据可视化图表面板优化过程所遇问题汇总

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

时间:2023-01-25 18:44:26浏览次数:61  
标签:vue layout 所遇 drop drag dragover const data panel

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

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

1.jpg

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

4.jpg

这个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上面,如果组件里面也需要接收左侧的拖曳组件,实现很麻烦:

WX20230125-162233@2x.jpg

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

回流问题:

其实很多初级的前端同学只知道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()提取出来就可以,代码如下:

2.jpg

 

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可以拆解成多个钩子,方便代码的复用与维护

c62d369e384e4a6199b9147f8035672b copy.jpg

 

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

标签:vue,layout,所遇,drop,drag,dragover,const,data,panel
From: https://www.cnblogs.com/zhoulujun/p/17067134.html

相关文章

  • 基于PHP语言Laravel9+Vue+ElementUI搭建的Web项目框架
    项目介绍一款PHP语言基于Laravel9.x、Vue、ElementUI等框架精心打造的一款模块化、插件化、高性能的前后端分离架构敏捷开发框架,可用于快速搭建前后端分离后台管理系统,本......
  • 基于PHP实现的Laravel9+Vue+ElementUI大数据分析管理系统
    项目介绍一款PHP语言基于Laravel9.x、Vue、ElementUI等框架精心打造的一款模块化、插件化、高性能的前后端分离架构敏捷开发框架,可用于快速搭建前后端分离后台管理系统,本......
  • 基于Laravel9+Vue+ElementUI的管理系统模板源码
    项目介绍一款PHP语言基于Laravel9.x、Vue、ElementUI等框架精心打造的一款模块化、插件化、高性能的前后端分离架构敏捷开发框架,可用于快速搭建前后端分离后台管理系统,本......
  • PHP语言的Laravel9+Vue+ElementUI开源框架推荐
    项目介绍一款PHP语言基于Laravel9.x、Vue、ElementUI等框架精心打造的一款模块化、插件化、高性能的前后端分离架构敏捷开发框架,可用于快速搭建前后端分离后台管理系统,本......
  • Vue源码case
    Object的变化侦测1.封装可观测数据通过Object.defineProperty方法实现了对object数据的可观测,并且封装了Observer类,能够方便的把object数据中所有属性(包括子属性)都转换成......
  • Vue 中 v-html 无法被 style scoped 渲染的问题
    假设有这么一个vue组件:<template><divv-html="docPreview"/></template><stylesrc="style.css"scoped></style>这样来说,div内的html的元素并不会受到c......
  • vue
    vue目录前置知识:html、css、javascript、ajax一、Vue基础二、本地应用内容绑定v-textv-htmlv-on显示切换,属性绑定v-showv-ifv-bind列表循环v-forv......
  • vuejs从入门到精通——Vue语法——插值绑定
    Vue语法——插值绑定插值绑定是Vue中最常见的、最基本的语法。绑定的内容主要有文本插值和HTML插值两种。一、文本插值文本插值用双大括号{{}}将要绑定的变量、值......
  • vue 练手项目
    1.网易云项目Vue-NeteaseCloud-WebMusicApp哔哩哔哩giteegithubmusic播客......
  • 使用vite创建vue3 遇到 process is not defined
    今天新建项目遇到报错,查资料得出,需要在vite.config.js中添加代码如下import{defineConfig}from'vite'importvuefrom'@vitejs/plugin-vue'import{resolve}f......