Smooth dragging (and other mouse movements)

If you ever need to implement dragging or to move something along with the mouse, here's how you do it:

  1. Always throttle your mouse events using requestAnimationFrame. Lodash's throttle method with no wait parameter will do this. If you don't throttle, your event will fire faster than the screen can even refresh, and you'll waste CPU cycles and the smoothness of the movement.
  2. Don't use absolute values of the mouse position. Instead, you should check how far the mouse has moved between frames. This is a more reliable and smoother method. If you use absolute values, the element's top-left corner will jump to where the mouse is when you first start dragging. Not a great UX if you grab the element from the middle.

Here's a basic example of tracking mouse movements using the Composition API. I didn't include throttling in order to keep things clearer:

// In your setup() function
window.addEventListener("mousemove", (e) => {
// Only move the element when we're holding down the mouse
if (dragging.value) {
// Calculate how far the mouse moved since the last
// time we checked
const diffX = e.clientX - mouseX.value;
const diffY = e.clientY - mouseY.value;
// Move the element exactly how far the mouse moved
x.value += diffX;
y.value += diffY;
}
// Always keep track of where the mouse is
mouseX.value = e.clientX;
mouseY.value = e.clientY;
});

Here's the full example. You can check out a working demo here:

<template>
<div class="drag-container">
<img
alt="Vue logo"
src="./assets/logo.png"
:style="{
left: `${x}px`,
top: `${y}px`,
cursor: dragging ? 'grabbing' : 'grab',
}"
draggable="false"
@mousedown="dragging = true"
/>
</div>
</template>
<script setup>
import { ref } from "vue";
const dragging = ref(false);
const mouseX = ref(0);
const mouseY = ref(0);
const x = ref(100);
const y = ref(100);
window.addEventListener("mousemove", (e) => {
if (dragging.value) {
const diffX = e.clientX - mouseX.value;
const diffY = e.clientY - mouseY.value;
x.value += diffX;
y.value += diffY;
}
mouseX.value = e.clientX;
mouseY.value = e.clientY;
});
window.addEventListener("mouseup", () => {
dragging.value = false;
});
</script>