Pointer
Pointer Capture: Taking Control
Pointer Capture for Drag Operations
element.addEventListener('pointerdown', (e) => {
element.setPointerCapture(e.pointerId);
// Element now receives all events for this pointer
});
Pointer capture allows an element to continue receiving events even when the pointer moves outside its boundaries:
const slider = document.getElementById('slider');
const thumb = slider.querySelector('.thumb');
let isDragging = false;
thumb.addEventListener('pointerdown', (e) => {
isDragging = true;
thumb.setPointerCapture(e.pointerId);
e.preventDefault();
});
thumb.addEventListener('pointermove', (e) => {
if (!isDragging) return;
const sliderRect = slider.getBoundingClientRect();
const thumbWidth = thumb.offsetWidth;
let newLeft = e.clientX - sliderRect.left - (thumbWidth / 2);
// Constrain to slider bounds
newLeft = Math.max(0, Math.min(newLeft, sliderRect.width - thumbWidth));
thumb.style.left = newLeft + 'px';
// Calculate value (0-100)
const value = (newLeft / (sliderRect.width - thumbWidth)) * 100;
console.log('Slider value:', Math.round(value));
});
thumb.addEventListener('pointerup', (e) => {
isDragging = false;
thumb.releasePointerCapture(e.pointerId);
});
thumb.addEventListener('lostpointercapture', () => {
isDragging = false;
});
Building a Complete Draggable Component
class DraggableElement {
constructor(element) {
this.element = element;
this.isDragging = false;
this.currentPointerId = null;
this.startX = 0;
this.startY = 0;
this.offsetX = 0;
this.offsetY = 0;
this.init();
}
init() {
this.element.style.touchAction = 'none';
this.element.style.userSelect = 'none';
this.element.addEventListener('pointerdown', this.onPointerDown.bind(this));
this.element.addEventListener('pointermove', this.onPointerMove.bind(this));
this.element.addEventListener('pointerup', this.onPointerUp.bind(this));
this.element.addEventListener('pointercancel', this.onPointerUp.bind(this));
}
onPointerDown(e) {
if (this.isDragging) return;
this.isDragging = true;
this.currentPointerId = e.pointerId;
this.element.setPointerCapture(e.pointerId);
const rect = this.element.getBoundingClientRect();
this.startX = e.clientX - rect.left;
this.startY = e.clientY - rect.top;
this.offsetX = rect.left;
this.offsetY = rect.top;
this.element.classList.add('dragging');
console.log(`Started dragging with ${e.pointerType}`);
}
onPointerMove(e) {
if (!this.isDragging || e.pointerId !== this.currentPointerId) return;
const newX = e.clientX - this.startX;
const newY = e.clientY - this.startY;
this.element.style.position = 'fixed';
this.element.style.left = newX + 'px';
this.element.style.top = newY + 'px';
// Provide haptic feedback for touch (if supported)
if (e.pointerType === 'touch' && 'vibrate' in navigator) {
navigator.vibrate(1);
}
}
onPointerUp(e) {
if (e.pointerId !== this.currentPointerId) return;
this.isDragging = false;
this.element.releasePointerCapture(e.pointerId);
this.element.classList.remove('dragging');
console.log('Drag ended');
this.currentPointerId = null;
}
}
// Usage
const draggable = new DraggableElement(document.getElementById('myElement'));