Dylan Tientcheu Software engineer | Data analyst | Technical content creator | Baller ๐Ÿ€ |

Responding to browser state changes with Tornis

5 min read 1413

An image of the Tornis logo.

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.

 

Why Tornis

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.

We made a custom demo for .
No really. Click here to check it out.

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.

Installing Tornis

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.

Understanding what the states tracked

Tornis currently tracks state for:

  • Mouse position
  • Mouse cursor velocity
  • Viewport size
  • Scroll position
  • Scroll velocity
  • Browser position relative to the screen
  • Browser velocity relative to the screen
  • Device orientation
  • Device pixel ratio

You can subscribe to store updates and combine these values to create all sorts of effects.

Tornis in action

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.

A gif showing an emoji that gets dizzy as the mouse velocity increases.

Setup

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.

Get more information here.

Now that we’ve imported the necessary function from Tornis, we can start tracking state.

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

More

Because there are React Hooks for everything, there are also Hooks for Tornis.

Conclusion

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.

Experience your Vue apps exactly how a user does

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. https://logrocket.com/signup/

LogRocket is like a DVR for web 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 - .

Dylan Tientcheu Software engineer | Data analyst | Technical content creator | Baller ๐Ÿ€ |

Leave a Reply