对于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 本身就是支持:
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,实现很麻烦:
首先,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,代码如下:
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
先写到这吧,I have time to sort it out later