Vue UseMouse: Track Mouse Coordinates with a Custom Hook
This challenge is about building a reusable Vue composable function, useMouse, that tracks the user's mouse coordinates on the screen. This is a common utility in interactive web applications, enabling features like parallax effects, custom cursor tracking, or dynamic UI element positioning based on mouse movement.
Problem Description
Your task is to create a composable function named useMouse using Vue 3's Composition API with TypeScript. This function should:
- Track Mouse Position: Listen for
mousemoveevents on thewindowobject and update reactive state with the current X and Y coordinates of the mouse. - Return Reactive State: Expose the mouse coordinates as reactive
refobjects so that they can be easily used and updated in any Vue component. - Clean Up Event Listeners: Ensure that the
mousemoveevent listener is properly removed when the component using the composable is unmounted to prevent memory leaks.
Key Requirements:
- The function must be written in TypeScript.
- It should export a single composable function named
useMouse. - The
useMousefunction should return an object containing tworefproperties:xandy, representing the mouse's horizontal and vertical coordinates respectively. - The
xandyrefs should be initialized to0ornullbefore the first mouse movement. - The event listener should be attached to the
windowobject. - A
beforeUnmounthook (or equivalent lifecycle management) should be used to remove the event listener.
Expected Behavior:
When useMouse is called within a Vue component's setup function, the x and y refs will start with their initial values. As the user moves their mouse over the browser window, the x and y refs will update in real-time to reflect the mouse's current position. When the component using useMouse is no longer rendered, the associated event listener will be detached from the window.
Edge Cases to Consider:
- Initial State: How should the
xandycoordinates be represented before the firstmousemoveevent occurs? - No Mouse Movement: If the user never moves the mouse, the coordinates should reflect their initial state.
Examples
Example 1:
Input (in a Vue component's setup):
<template>
<p>Mouse X: {{ mouseX }}</p>
<p>Mouse Y: {{ mouseY }}</p>
</template>
<script setup lang="ts">
import { useMouse } from './composables/useMouse'; // Assuming useMouse is in this path
const { x: mouseX, y: mouseY } = useMouse();
</script>
Output (when the mouse is at client coordinates 150, 200):
<p>Mouse X: 150</p>
<p>Mouse Y: 200</p>
Explanation:
The useMouse composable is imported and used. The returned x and y refs are destructured and aliased to mouseX and mouseY for clarity in the template. As the mouse moves, mouseX and mouseY update, and the template displays the current coordinates.
Example 2: Initial State
Input (in a Vue component's setup):
<template>
<p>Mouse X: {{ mouseX }}</p>
<p>Mouse Y: {{ mouseY }}</p>
</template>
<script setup lang="ts">
import { useMouse } from './composables/useMouse';
// Assume useMouse initializes x and y to 0
const { x: mouseX, y: mouseY } = useMouse();
</script>
Output (when the component is mounted but before any mouse movement):
<p>Mouse X: 0</p>
<p>Mouse Y: 0</p>
Explanation:
The useMouse composable initializes its internal x and y refs to 0. The template displays these initial values until a mousemove event occurs.
Constraints
- The solution must be implemented using Vue 3 Composition API and TypeScript.
- The
useMousefunction should return an object withxandyproperties, where each is aRef<number>. - Event listeners should be managed to prevent memory leaks.
- The
mousemoveevent listener should be attached to thewindowobject.
Notes
- Consider using
reffromvueto create reactive variables. - You will need to use Vue's lifecycle hooks to manage the event listener. The
onUnmountedhook is particularly relevant for cleanup. - The
MouseEventobject hasclientXandclientYproperties that provide the coordinates relative to the viewport. - Think about how you want to handle the initial state of the mouse coordinates. Returning
0is a common approach, but you might considernullas an alternative if that makes more sense for your application's logic.