Appearance
๐ฅ (#61) Smooth Dragging (and other mouse movements)
May 2022
Hey there,
Parts 3-5 of the "Coding Better Composables" series are now up!
You can check them all out here:
- Coding Better Composables: (1/5)
- Coding Better Composables: Flexible Arguments (2/5)
- Coding Better Composables: Dynamic Returns (3/5)
- Coding Better Composables: Start with the Interface (4/5)
- Coding Better Composables: Async Without Await (5/5)
And now on to your weekly tips.
โ Michael
๐ฅ 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:
Always throttle your mouse events using
requestAnimationFrame. Lodash'sthrottlemethod with nowaitparameter 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.
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>
import { ref } from "vue";
export default {
setup() {
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;
});
return {
x,
y,
dragging,
};
},
};
</script>
๐ฅ Teleportation
You can get an element to render anywhere in the DOM with the teleport component in Vue 3:
<template>
<div>
<div>
<div>
<teleport to="body">
<footer>This is the very last element on the page</footer>
</teleport>
</div>
</div>
</div>
</template>
This will render the footer at the very end of the document body:
<html>
<head>
<!-- ... -->
</head>
<body>
<div>
<div>
<div>
<!-- Footer element was moved from here... -->
</div>
</div>
</div>
<!-- ...and placed here -->
<footer>This is the very last element on the page</footer>
</body>
</html>
This is very useful when the logic and state are in one place, but they should be rendered in a different location.
One typical example is a notification (sometimes called a toast).
We want to be able to display notifications from wherever inside of our app. But the notifications should be placed at the end of the DOM so they can appear on top of the page:
<!-- DogList.vue -->
<template>
<div>
<DogCard
v-if="dogs.length > 0"
v-for="dog in dogs"
:key="dog.id"
v-bind="dog"
/>
<teleport to="#toasts">
<!-- Show an error notification if we have an error -->
<Toast
v-if="error"
message="Ah shoot! We couldn't load all the doggos"
>
</teleport>
</div>
</template>
This will render this to the DOM:
<html>
<head>
<!-- ... -->
</head>
<body>
<div id="#app">
<!-- Where our Vue app is normally mounted -->
</div>
<div id="toasts">
<!-- All the notifications are rendered here,
which makes positioning them much easier -->
</div>
</body>
</html>
Here's the complete documentation: https://v3.vuejs.org/api/built-in-components.html#teleport
๐ What's new in the Vue 3 Dev Tools
My friend Marina takes us through all the great features in the updated dev tools.
Note: these dev tools also work with Vue 2!
Read it here: What's new in the Vue 3 Dev Tools
๐ฌ Obvious Deficiencies
"There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult." โTony Hoare
๐ง Spaced-repetition: When to use v-if
The best way to commit something to long-term memory is to periodically review it, gradually increasing the time between reviews ๐จโ๐ฌ
Actually remembering these tips is much more useful than just a quick distraction, so here's a tip from a couple weeks ago to jog your memory.
Instead of using v-if, it's sometimes more performant to use v-show instead:
<ComplicatedChart v-show="chartEnabled" />
When v-if is toggled on and off it will create and destroy the element completely. Instead, v-show will create the element and leave it there, hiding it by setting it's style to display: none.
Doing this can be much more efficient if the component you're toggling is expensive to render.
On the flip side, if you don't need that expensive component immediately, use v-if so that it will skip rendering it and load the page just a bit faster.
p.s. I also have two courses: Reusable Components and Clean Components
ๆฅๆบ
ๅๆ https://michaelnthiessen.com/weekly-061-may-18/
ๆฌไฝๅ้็จ็ฅ่ฏๅ ฑไบซ็ฝฒๅ-็ธๅๆนๅผๅ ฑไบซ 4.0 ๅฝ้ ่ฎธๅฏๅ่ฎฎ่ฟ่ก่ฎธๅฏใ