Browser state tracking is a common practice — we always need to know how and with what configurations the user is interacting with our websites.
This is important because it can help us tailor our components to fit these configurations.
Nowadays, there are a lot of applications that can help us do this sort of tracking, including parallax, media queries, and cursor tracking.
Tornis is a JavaScript library that helps you seamlessly and easily track various state changes from the end user’s browser.
Tornis introduces itself as follows:
Taking its name from the forest watchtowers of Latvia, Tornis is a minimal JavaScript library that watches the state of your browser’s viewport, allowing you to respond whenever something changes. Think of Tornis as a store for your viewport.
It’s possible to manually track everything trackable in Tornis’s store using your JavaScript event handlers.
However, Tornis has significant advantages that give it an upper hand regarding performance and coding experience as compared to native JS event handlers.
Tornis eases the combination of different events tracking to achieve a goal.
It is much easier to work with complex events handlers in combination by using Tornis.
As mentioned on their website, Tornis takes a deferred approach.
Rather than binding directly to native events, Tornis throttles them and captures only the bare minimum — the updated values.
This simply means that your code will run only when there’s a change in the store, and when the browser is ready to render.
Check out the simple parallax and scroll tracking examples from Tornis’ website.
Once you have initialized your frontend project, simply run the following command inside your project root:
npm install tornis --save
Tornis is written in modern JavaScript with ES6, so you will need to transpile with babel if you are supporting legacy browsers.
Tornis currently tracks state for:
You can subscribe to store updates and combine these values to create all sorts of effects.
One of the most common use cases for browser tracking is creating parallax effects.
However, for this post, I decided to harness the power of Tornis for another purpose that can also be very valuable — cursor velocity.
We are going to create a simple component that tracks mouse cursor velocity to see the power and simplicity of Tornis.
Our component will have the ability to render a dizzy emoji whenever the cursor is moved at a very high speed.
Also, this velocity will be captured right at the moment when the emoji changes from normal to dizzy. Quite a simple requirement.
As mentioned above, before installing Tornis, we should make sure we are working on a Babel enabled project. Tornis is written in ES6, so it has to be transpiled so as to support legacy browsers.
I chose to work with Vue.js, because it easily sets up a quick scaffold.
If you want to work with Vue, follow these steps to get set up.
Once you have your Vue project set up, you can delete the unused scaffold components and styles to help you start from a clean component.
Now we’ll start by installing Tornis using our package manager (the command is above).
After installation, we import the Tornis package into our project:
import {watchViewport} from "tornis";
watchViewport
enables us to bind a watched function that will be run on each state update.
Basically, this helps us watch (track) and update the state of our elements.
There’s much more to Tornis than watchViewport
.
It also has unwatchViewport
,getViewportState
, and recalibrateOrientation
.
Now that we’ve imported the necessary function from Tornis, we can start tracking state.
To track state in Tornis, we pass a set of values that will get updated each time a tracked event is fired.
This is the heart of Tornis. This function will enable us to watch changes and act accordingly.
We need to start tracking changes once the component is mounted.
In Vue’s lifecycle plan, the appropriate moment is inside the mounted()
function.
For what we need to achieve, we have to track the mouse velocity, which is returned by the mouse
attribute in Tornis’s state object.
//... mounted: function() { const updateValues = ({ size, // tracks viewport size scroll, // tracks scroll events mouse, // tracks mouse events (position & velocity) position, // tracks mouse position orientation, // tracks device orientation devicePixelRatio // tracks pixel ration }) => { if (size.changed) { // do something related to size } if (scroll.changed) { // do something related to scroll position or velocity } if (mouse.changed) { // do something related to mouse position or velocity } if (position.changed) { // do something related to browser window position or velocity } if (orientation.changed) { // do something related to device orientation } if (devicePixelRatio.changed) { // do something related to pixel ratio } }; // bind the watch function // By default this will run the function as it is added to the watch list watchViewport(updateValues); } //...
From this code we are already tracking all of the browser states.
However, we need just the values gotten from the mouse
event.
What is nice about Tornis is that it gets data in a deferred way.
It throttles these values and captures only the final update. This is a big gain on runtime performance.
The watchViewport()
function enables us to run the function while watching the values.
As you may have noticed, the above code is still too cumbersome for us, because we only need mouse
related events.
Let’s clear this clutter up.
//... mounted: function() { const updateValues = ({mouse}) => { if (mouse.changed) { // do something related to mouse position or velocity } }; // bind the watch function // By default this will run the function as it is added to the watch list watchViewport(updateValues); } //...
Now we need to create the interaction between the updated values and the emoji. We’ll leverage the reactivity of Vue to create this link.
<template> <div id="app"> <p>{{emoji}}</p> <p>{{trackedVelocity}}</p> <small>maxVelocity: {{maxVelocity}}</small> </div> </template> <script> // import the Tornis store functions import { watchViewport } from "tornis"; export default { name: "App", data: function() { return { emoji: "😀", trackedVelocity: 0, maxVelocity: 0 }; }, mounted: function() { /* const updateValues = ({ .... */ } }; </script> <style> /*styling*/ </style>
Our single file component is now created. Let’s add the logic that will make the emoji’s state to change according to our update.
Our aim is to make the emoji feel dizzy once the mouse goes too fast and record the last velocity captured. This will be done in our updateValues
function.
Your complete App.vue
should look like this:
<template> <div id="app"> <p>{{emoji}}</p> <p>{{trackedVelocity}}</p> <small>maxVelocity: {{maxVelocity}}</small> </div> </template> <script> // import the Tornis store functions import { watchViewport } from "tornis"; export default { name: "App", components: {}, data: function() { return { emoji: "😀", trackedVelocity: 0, maxVelocity: 0 }; }, mounted: function() { const updateValues = ({ size, // tracks viewport size scroll, // tracks scroll events mouse, // tracks mouse events (position & velocity) position, // tracks mouse position orientation, // tracks device orientation devicePixelRatio // tracks pixel ration }) => { if (mouse.changed) { // do something related to mouse position or velocity this.trackedVelocity = mouse.velocity.x; if ( (mouse.velocity.x > 50 && mouse.velocity.x < 100) || (mouse.velocity.x < -50 && mouse.velocity.x > -100) ) { this.emoji = "🙄"; this.maxVelocity = mouse.velocity.x; } if ( (mouse.velocity.x > 100 && mouse.velocity.x < 200) || (mouse.velocity.x < -100 && mouse.velocity.x > -200) ) { this.emoji = "🤢"; this.maxVelocity = mouse.velocity.x; } if (mouse.velocity.x > 200 || mouse.velocity.x < -200) { this.emoji = "🤮"; this.maxVelocity = mouse.velocity.x; } } }; // bind the watch function // By default this will run the function as it is added to the watch list watchViewport(updateValues); } }; </script> <style> #app { text-align: center; margin-top: 4rem; } p { font-size: 4rem; } </style>
Our demo is ready:
tornis-emoji-mouse-tracker
tornis-emoji-mouse-tracker by blurdylan using @vue/cli-plugin-babel, tornis, vue
Because there are React Hooks for everything, there are also Hooks for Tornis.
Tornis tracks your browser’s viewport state and it does it extremely well, with a low performance cost and great malleability (or ability to combine different events).
Let me know how your experience with Tornis went in the comments.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.