User actions such as hover, click, or focus give a clear signal about what a user wants to achieve. Often, they can also tell us what the user is about to do.
For instance, when a user hovers over a button, we can make a good assumption that they might click it. If that button starts an expensive task (e.g. fetching data or doing calculations), then the hover event gives us a chance to start the work sooner.
However, hover can sometimes be too late. You may only get milliseconds between the hover and the click. The pointer has already reached its target by the time you receive the hover event.
But what if we could predict the user’s intent? Instead of waiting for the near certainty of a hover, we could be more optimistic and see if the user looks like they are going towards the element.
This demo shows how we can use pointer direction and velocity to detect intent sooner. As part of a useLoadOnIntent hook, we can fire a callback such as fetching data much sooner, creating a faster and better experience.
The circle shows the active intent area. It grows when the pointer moves quickly towards the button and shrinks when the pointer slows down or moves away.
By listening to pointermove, we can figure out the direction and velocity of the pointer. Once the pointer enters our ‘zone of interest’, we can begin to fire a callback such as fetching data.
Dynamic radius
The radius around the element changes depending on where the pointer is and how fast it’s moving towards the target. For each pointer movement, we store a snapshot of the x/y position and the time it occurred.
From these snapshots we can figure out the velocity of the pointer movement.
When we normalise it, we can change the radius size depending on whether the pointer is moving towards the target at speed, moving away from the target, or slowly progressing towards it.
We also consider the alignment, or where the pointer is coming from relative to the element. This means the pointer is measured from the closest point on the element, not the centre. This means we don’t upset the calculation if we have a large element.
Stopping pass-throughs
But what if the pointer is just moving past the target element?
Just because the pointer moves near it doesn’t mean we have ‘intent’. Part of the idea is we’re guessing but we can be smart about what we define as genuine movement towards the target and what is just a quick ‘pass-through’.
A simple timer wraps our callback to make sure we don’t fire it until the user has dwelled in the radius for a short amount of time. By storing whether the callback has fired before, we prevent repeated function calls.
Full code
Here’s the full hook. It’s not tested or used in production, but it works fairly well.