Implementation of Component Drag‑and‑Drop in the Wukong Low‑Code Visual Platform
This article explains how the Wukong low‑code visual platform implements component drag‑and‑drop, covering material‑to‑canvas dragging, intra‑canvas movement, resizing via eight handles, alignment guide generation, performance optimizations using CSS transforms, and component encapsulation with Vue 3.
The article begins with a historical overview of data visualization and introduces Wukong as a low‑code platform that relies heavily on drag‑and‑drop interactions for building large‑screen visualizations.
It then details the technical implementation, focusing on three main aspects: component dragging, component resizing, and alignment guides.
Component Dragging
Dragging from the material area to the canvas uses native HTML5 drag events. The dragstart event is bound to the material element, and the drop event on the canvas adds the component at the drop coordinates:
// 内容区添加组件
onDrop(e: DragEvent) {
this.isDragDroping = false;
this.addCard(e.offsetX, e.offsetY);
}During dragging, drag and dragover events fire continuously; preventing the default behavior of dragover enables the canvas to accept the drop:
function handleDragOver(e) {
// 阻止默认的重置行为
e.preventDefault();
}
// 也可以直接在元素上添加如下代码
@dragover.prevent="() => {}"Intra‑canvas dragging tracks mouse movements. On mousedown the initial position is recorded, mousemove updates the component’s left and top values using the difference between current and previous mouse coordinates, and mouseup ends the operation:
onMousedown = (evt: MouseEvent) => {
mouseClickPosition.mouseX = evt.pageX;
mouseClickPosition.mouseY = evt.pageY;
document.documentElement.addEventListener("mousemove", mousemove, true);
document.documentElement.addEventListener("mouseup", mouseup, true);
};
mousemove = (evt: MouseEvent) => {
left.value = left.value + (evt.pageX - mouseClickPosition.mouseX);
top.value = top.value + (evt.pageY - mouseClickPosition.mouseY);
mouseClickPosition.mouseX = evt.pageX;
mouseClickPosition.mouseY = evt.pageY;
};Component Resizing
Resizing is achieved with eight handles (tl, tm, tr, mr, br, bm, bl, ml). Each handle registers a mousedown event that starts a resize operation, adjusting width or height based on mouse movement. Example for the top‑right handle:
const ononMousedownHandle = (item: any, evt: any) => {
if (evt.stopPropagation) evt.stopPropagation();
handle.value = item;
mouseClickPosition.mouseX = evt.pageX;
mouseClickPosition.mouseY = evt.pageY;
document.documentElement.addEventListener("mousemove", handleResize, true);
document.documentElement.addEventListener("mouseup", handleResizeUp, true);
};
handleResize = (evt: MouseEvent) => {
if (handle.value === "tr" && evt.pageY - mouseClickPosition.mouseY < height.value) {
width.value = width.value + (evt.pageX - mouseClickPosition.mouseX);
height.value = height.value + (mouseClickPosition.mouseY - evt.pageY);
top.value = top.value + (evt.pageY - mouseClickPosition.mouseY);
mouseClickPosition.mouseX = evt.pageX;
mouseClickPosition.mouseY = evt.pageY;
}
};Alignment Guides
During dragging, auxiliary lines are generated to assist alignment. The system compares the moving component’s top and left values with those of other components on the canvas (retrieved via parentNode.childNodes ) and creates guide lines when the values are within a ±4 px tolerance. Sample code for vertical alignment:
if (top.value > nodeTop - 4 && top.value < nodeTop + 4) {
top.value = nodeTop;
rtlShow = true;
if (left.value > nodeLeft) {
rtlStyle.tlWidth = left.value - nodeLeft + width.value;
rtlStyle.tlLeft = nodeLeft;
} else {
rtlStyle.tlWidth = nodeLeft - left.value + nodeWidth;
rtlStyle.tlLeft = left.value;
}
rtlStyle.tlTop = top.value;
rtlStyle.tlHeight = 1;
rtlStyle.tlDisplay = "inline-block";
}These guides support various alignment cases such as top‑to‑top, top‑to‑bottom, bottom‑to‑bottom, bottom‑to‑top, and center‑to‑center, improving layout precision.
Performance Optimizations
The implementation prefers CSS transform: translate(...) over absolute positioning because it leverages GPU acceleration, reduces CPU calculations, and creates a new compositing layer, resulting in smoother interactions.
Component Encapsulation
To avoid adding drag logic to each material component individually, the drag functionality is encapsulated into a reusable Vue component. The component renders the eight handles and a slot for the actual content, separating drag behavior from the visual elements:
<div :class="['vue-drag-resize', { active: enabled }]" ref="vdrag" :style="style" @mousedown="onMousedown" @click.stop.prevent="onClick">
<div v-for="(item, index) in handles" :key="index" :style="handleStyle" :class="['handle', 'handle-' + item]" @mousedown.stop.prevent="ononMousedownHandle(item, $event)"></div>
<slot></slot>
</div>Finally, the article concludes that drag‑and‑drop is a core interaction in low‑code platforms, and the upcoming grouping feature will further enhance layout efficiency.
Zhengtong Technical Team
How do 700+ nationwide projects deliver quality service? What inspiring stories lie behind dozens of product lines? Where is the efficient solution for tens of thousands of customer needs each year? This is Zhengtong Digital's technical practice sharing—a bridge connecting engineers and customers!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.