How to Build a Touch Drag Component with NutUI 3.0: A Step‑by‑Step Vue Guide
This article walks through the design and implementation of NutUI 3.0's Drag component for mobile web, explaining the two main drag techniques, touch event handling, boundary checks, optional axis constraints, and snap‑to‑edge behavior with complete Vue code examples.
Introduction
NutUI is JD.com’s lightweight mobile‑first Vue component library. Version 3.0 introduces a revamped Drag component that lets developers move elements with finger gestures on mobile browsers. The article explains the motivation for component‑based development and provides a full implementation walkthrough.
Implementation Idea
Two common approaches exist for mobile drag: using position:fixed and updating the element’s coordinates, or applying CSS transform: translate. NutUI’s Drag component adopts the fixed‑position method, which requires updating top and left values during touch interactions.
Principle Analysis
The component relies on touch event coordinates ( clientX/Y, pageX/Y, screenX/Y, layerX/Y, offsetX/Y) to calculate movement. When a touch starts, the initial element offsets and finger positions are recorded; during movement, the delta is applied to the element’s style; on touch end, optional post‑processing such as snapping can be performed.
Touch Events
touchstart : fires when a finger contacts the screen.
touchmove : fires continuously as the finger slides; preventDefault() can stop page scrolling.
touchend : fires when the finger lifts.
The event object contains three touch lists: touches (all active points), targetTouches (points on the event target), and changedTouches (points that changed since the last event).
Detailed Process
1. Initial Setup
const state = reactive({
elWidth: 0, // element width
elHeight: 0, // element height
screenWidth: 0,
screenHeight: 0,
startTop: 0,
startLeft: 0,
direction: props.direction, // all / x / y
position: { x: 0, y: 0 },
boundary: { top: 0, left: 0, right: 0, bottom: 0 }
});
function touchStart(e: TouchEvent) {
const target = e.currentTarget as HTMLElement;
const touches = e.touches[0];
state.startTop = target.offsetTop;
state.startLeft = target.offsetLeft;
state.position.x = touches.clientX;
state.position.y = touches.clientY;
}2. Moving the Element
function touchMove(e: TouchEvent) {
e.preventDefault();
const target = e.currentTarget as HTMLElement;
if (e.targetTouches.length === 1) {
const touch = e.targetTouches[0];
state.nx = touch.clientX - state.position.x;
state.ny = touch.clientY - state.position.y;
state.xPum = state.startLeft + state.nx;
state.yPum = state.startTop + state.ny;
target.style.left = state.xPum + 'px';
target.style.top = state.yPum + 'px';
}
}3. Boundary Checking
const rightLocation = state.screenWidth - state.elWidth - state.boundary.right;
if (Math.abs(state.xPum) > rightLocation) {
state.xPum = rightLocation;
} else if (state.xPum <= state.boundary.left) {
state.xPum = state.boundary.left;
}
if (state.yPum < state.boundary.top) {
state.yPum = state.boundary.top;
} else if (state.yPum > state.screenHeight - state.elHeight - state.boundary.bottom) {
state.yPum = state.screenHeight - state.elHeight - state.boundary.bottom;
}4. Optional Axis Constraint
if (props.direction != 'y') {
target.style.left = state.xPum + 'px';
}
if (props.direction != 'x') {
target.style.top = state.yPum + 'px';
}5. Snap‑to‑Edge Effect
function goLeft(target: HTMLElement) {
if (state.boundary.left) {
if (+target.style.left.split('px')[0] > state.boundary.left) {
target.style.left = +target.style.left.split('px')[0] - 10 + 'px';
requestAniFrame(() => goLeft(target));
} else {
target.style.left = `${state.boundary.left}px`;
}
} else {
if (+target.style.left.split('px')[0] > 10) {
target.style.left = +target.style.left.split('px')[0] - 10 + 'px';
requestAniFrame(() => goLeft(target));
} else {
target.style.left = '0px';
}
}
}
function goRight(target: HTMLElement, rightLocation: number) {
if (rightLocation - parseInt(target.style.left.split('px')[0]) > 10) {
target.style.left = parseInt(target.style.left.split('px')[0]) + 10 + 'px';
requestAniFrame(() => goRight(target, rightLocation));
} else {
target.style.left = rightLocation + 'px';
}
}Visual Aids
Illustrative diagrams and GIFs demonstrate the touch event flow, the difference between touches, targetTouches, and changedTouches, and the final drag behavior with snapping.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JD Retail Technology
Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.
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.
