Drag-and-drop is one of the most common interactions in front-end development:
From dragging files in Baidu Netdisk to moving objects in Figma’s canvas, it’s everywhere.
Many developers think — isn’t drag-and-drop just mousedown + mousemove + mouseup
?
Three lines of code and done!
But once you implement it in a real production environment, the pitfalls start appearing one after another:
- Different event models between PC and mobile
- Elements “flying” outside their containers
- Events lost when dragging over iframes
- Conflicts between drag and scroll on mobile devices
- State loss when using frameworks like Vue or React
Building a working drag-and-drop is easy.
Building a robust and smooth drag-and-drop experience is hard.
Today, let’s break down how to implement a solid drag-and-drop system — and avoid the most common pitfalls.
The Basic Principle of Drag-and-Drop
Drag-and-drop relies on three core events:
- mousedown — Start dragging
- mousemove — Move the element
- mouseup — End dragging
Here’s the simplest implementation:
const box = document.getElementById('box');
let isDragging = false;
let offsetX = 0, offsetY = 0;
// Start dragging
box.addEventListener('mousedown', (e) => {
isDragging = true;
offsetX = e.clientX - box.offsetLeft;
offsetY = e.clientY - box.offsetTop;
});
// Move the element
document.addEventListener('mousemove', (e) => {
if (isDragging) {
box.style.left = (e.clientX - offsetX) + 'px';
box.style.top = (e.clientY - offsetY) + 'px';
}
});
// Stop dragging
document.addEventListener('mouseup', () => {
isDragging = false;
});
👉 Live demo: CodeSandbox
In short: drag-and-drop simply means continuously updating an element’s left/top
while moving the mouse.
Common Pitfalls in Real Development
Here are some of the most frequent issues developers run into:
1. Multi-Device Compatibility
Different devices have different event systems:
- PC → uses mouse events (
mousedown
,mousemove
,mouseup
) - Mobile → uses touch events (
touchstart
,touchmove
,touchend
)
function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
('ontouchstart' in window) ||
(navigator.maxTouchPoints > 0);
}
2. Boundary Constraints
You need to keep draggable elements within their containers to avoid them flying off-screen.
Solution:
updatePosition(newX, newY) {
const containerRect = this.container.getBoundingClientRect();
const elementRect = this.element.getBoundingClientRect();
const minX = 0;
const minY = 0;
const maxX = containerRect.width - elementRect.width;
const maxY = containerRect.height - elementRect.height;
newX = Math.max(minX, Math.min(newX, maxX));
newY = Math.max(minY, Math.min(newY, maxY));
this.element.style.left = newX + 'px';
this.element.style.top = newY + 'px';
}
3. iframe Compatibility Issues
When dragging across an iframe, mouse events are lost as soon as the cursor enters the iframe area.
A common workaround: temporarily disable iframe pointer events during drag.
const iframes = document.getElementsByTagName('iframe');
for (const iframe of iframes) {
iframe.style.pointerEvents = 'none';
}
4. Framework Compatibility
In frameworks like Vue or React, component re-renders can cause drag state loss.
You can use MutationObserver to watch for DOM changes and prevent style resets.
this.observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === "style" &&
element.style.display === "none") {
element.style.display = "block";
}
});
});
this.observer.observe(element, {
attributes: true,
attributeFilter: ["style"],
});
5. Scroll Conflicts on Mobile
On touch devices, dragging often triggers page scrolling.
handleTouch(event) {
event.preventDefault();
event.stopPropagation();
this.startDrag(event);
}
Advanced Features
Besides the basics, real-world drag-and-drop often requires:
- Grid snapping – automatically align to a grid
- Edge snapping – auto-stick to edges
- Position persistence – remember the element’s position after refresh
These features require additional logic and careful design.
A Better Solution: drag-kit
After all these pitfalls, you can see that building a robust drag-and-drop solution isn’t trivial.
It involves event abstraction, boundary control, iframe compatibility, performance optimization, and framework integration.
That’s why it’s better to use a mature library — drag-kit.
Key Features of drag-kit
- Plug and play – ready to use in minutes
- Cross-platform – automatically handles PC, mobile, and tablets
- Rich features – built-in boundary, grid, and snap support
- Framework friendly – works with Vue 2/3 and React
- Performance optimized – smooth and lag-free
- TypeScript ready
Quick Start
import { createDraggable } from 'drag-kit';
// Basic usage
createDraggable('myElement');
// Advanced configuration
createDraggable('myElement', {
mode: 'screen',
initialPosition: { x: '100px', y: '200px' },
lockAxis: 'y',
gridSize: 50,
snapMode: 'auto',
shouldSave: true,
onDragStart: (el) => console.log('Start drag', el),
onDrag: (el) => console.log('Dragging', el),
onDragEnd: (el) => console.log('End drag', el)
});
Installation
npm install drag-kit
Vue 3 Example
import { onMounted } from 'vue';
import { createDraggable } from 'drag-kit';
export default {
setup() {
onMounted(() => {
createDraggable('draggableElement', {
initialPosition: { x: '100px', y: '200px' }
});
});
}
};
Conclusion
While drag-and-drop seems simple in theory, it becomes complex in production due to:
- Multi-device event differences
- Boundary handling
- iframe compatibility
- Framework state loss
- Mobile scroll conflicts
If you’re just building a quick demo — three lines of code are enough.
But if you’re building for production, a mature library like drag-kit saves you time, avoids pitfalls, and delivers a smooth, stable, and professional drag-and-drop experience.
👉 GitHub: drag-kit
Top comments (0)