Tag: Users

One Way to Break Users Out of the Habit of Reloading Too Much

Page reloads are a thing. Sometimes we refresh a page when we think it’s unresponsive, or believe that new content is available. Sometimes we’re just mad at the dang site and rage-refresh to let it know we’re displeased.

Wouldn’t be nice to know when a user refreshes the page? Not just that, but how many times? That data can help us trigger some sort of behavior after a certain number of reloads.

A sports site is a good example. If I want to check the score of a game that’s in progress but the scores aren’t live-updated, then I might find myself refreshing a bunch.

Our goal is to break users out of that habit. We’ll use our page-refresh-counting powers to let folks know that refreshes are unnecessary, thanks to real-time score updates. And if they reload more than three times? We’ll kick ‘em out of their session. That’ll show them.

Here’s a simple demo of that concept.

Let’s re-create it together. But before we get going, there are few questions we need to answer before we start coding:

  • How can we persist the number of times user reloaded the site? We need a place to keep the number of times user reloaded the site (reloadCount), this place needs to persist that value between the reloads — localStorage sounds like a good solution.
  • How do we detect if user reloaded the site or just came back after few hours? If we store the reloadCount in localStorage it will persist the value between the reloads, but it will keep that value until we remove programmatically or clear the browser storage. It means that if we come back after few hours the site will still remember last reloadCount and may perform logout after first refresh without warning. We want to avoid that and allow user to reload the site two times each time the user comes back after some period of time. That last sentence holds the answer to the question. We need to store the time when the user left the site and then when the site loads again check when that happened. If that time period wasn’t long enough, we activate the reload counting logic.
  • How do we know when the user leaves the site? To store that time, we use beforeunload window event and store that value in localStorage.

OK, now that we have the answers, let’s dive into the code.

Step 1: We’ve gotta store the last reload time

We will store the time of last reload using a beforeunload window event. We need two things: (1) an event listener that will listen to the event and fire the appropriate method, and (2) our beforeUnloadHandler method.

First, let’s create a function called initializeReloadCount that will set our event listener using the addEventListener method on the window object.

function initializeReloadCount() {   window.addEventListener("beforeunload", beforeUnloadHandler) }

Then we create a second method that will be fired before we leave the site. This method will save the time the refresh happens in localStorage.

function beforeUnloadHandler() {   localStorage.setItem("lastUnloadAt", Math.floor(Date.now() / 1000))   window.removeEventListener("beforeunload", beforeUnloadHandler); }

Step 2: We need a way to handle and store the reload count

Now that we have the time when the site was last closed, we can proceed and implement logic that’s responsible for detecting and counting how many times the site was reloaded. We need a variable to hold our reloadCount and tell us how many times user reloaded the site.

let reloadCount = null

Then, in our initializeReloadCount function, we need to do two things:

  1. Check if we already have a reloadCount value stored in our localStorage, and if so, get that value and save it in our reloadCount. If the value doesn’t exist, it means that the user loaded the site for the first time (or at least did not reload it). In that case, we set the reloadCount to zero and save that value to localStorage.
  2. Detect if the site was reloaded or the user came back to the site after longer period of time. This is the place where we need our lastUnloadAt value. To detect if the site was actually reloaded, we need to compare the time when the site gets loaded (the current time) with the lastUnloadAt value. If those two happened within, say, five seconds (which is totally arbitrary), that means the user reloaded the site and we should run reload count logic. If the time period between those two events is longer, we reset the reloadCount value.

With that, let’s create a new function called checkReload and keep that logic there.

function checkReload() {   if (localStorage.getItem("reloadCount")) {     reloadCount = parseInt(localStorage.getItem("reloadCount"))   } else {     reloadCount = 0     localStorage.setItem("reloadCount", reloadCount)   }   if (     Math.floor(Date.now() / 1000) - localStorage.getItem("lastUnloadAt") <     5   ) {     onReloadDetected()   } else {     reloadCount = 0;     localStorage.setItem("reloadCount", reloadCount)   } }

The last function we need in this step is a method responsible for what happens when we confirm that the user reloaded the site. We call that function onReloadDetected, and inside it, we increment the value of reloadCount. If the user refreshed the site third time, we drop the bomb and call our logout logic.

function onReloadDetected() {   reloadCount = reloadCount + 1   localStorage.setItem("reloadCount", reloadCount)   if (reloadCount === 3) {     logout()   } }

Step 3: “Dear user, why you didn’t listen?!”

In this step, we implement the logic responsible for the situation when the user reloads the site to the point of breaching our three-limit threshold, despite our clear warnings to stop doing it.

When that happens, we call our API to log the user out, then we clean up all properties related to the reload count logic. That will allow the user to come back and have a clean account of reloads. We can also redirect the user somewhere useful, like the login screen. (But wouldn’t it be funny to send them here instead?)

function logout(params) {   // logout API call   resetReloadCount() }  function resetReloadCount() {   window.removeEventListener("beforeunload", beforeUnloadHandler)   localStorage.removeItem("lastUnloadAt")   localStorage.removeItem("reloadCount"); }

Bonus: Let’s re-Vue it!

Now that we have the logic implemented, let’s see how can move that logic to a Vue site based on this example:

First, we need to move all of our variables into our component’s data, which is where all reactive props live.

export default {   data() {     return {       reloadCount: 0,       warningMessages: [...]     }   },

Then we move all our functions to methods.

// ...   methods: {     beforeUnloadHandler() {...},     checkReload() {...},     logout() {...},     onReloadDetected() {...},     resetReloadCount() {...},     initializeReloadCount() {...}   } // ...

Since we are using Vue and its reactivity system, we can drop all direct DOM manipulations (e.g. document.getElementById("app").innerHTML) and depend on our warningMessages data property. To display the proper warning message we need to add a computed property that will re-calculate each time our reloadCount is changed so that we can return a string from our warningMessages.

computed: {   warningMessage() {     return this.warningMessages[this.reloadCount];   } },

Then we can access our computed property directly in the component’s template.

<template>   <div id="app">     <p>{{ warningMessage }}</p>   </div> </template>

Last thing we need to do is find a proper place to activate the reload prevention logic. Vue comes with component lifecycle hooks that are exactly what we need, specifically the created hook. Let’s drop that in.

// ...   created() {     this.initializeReloadCount();   }, // ...

Nice.

Wrapping up

And there it is, the logic that checks and counts how many times a page has been refreshed. I hope you enjoyed the ride and you find this solution useful or at least inspiring to do something better. 🙂

The post One Way to Break Users Out of the Habit of Reloading Too Much appeared first on CSS-Tricks.

CSS-Tricks

, , , ,

Detecting Inactive Users

Most of the time you don’t really care about whether a user is actively engaged or temporarily inactive on your application. Inactive, meaning, perhaps they got up to get a drink of water, or more likely, changed tabs to do something else for a bit. There are situations, though, when tracking the user activity and detecting inactive-ness might be handy.

Let’s think about few examples when you just might need that functionality:

  • tracking article reading time
  • auto saving form or document
  • auto pausing game
  • hiding video player controls
  • auto logging out users for security reasons

I recently encountered a feature that involved that last example, auto logging out inactive users for security reasons.

Why should we care about auto logout?

Many applications give users access to some amount of their personal data. Depending on the purpose of the application, the amount and the value of that data may be different. It may only be user’s name, but it may also be more sensitive data, like medical records, financial records, etc.

There are chances that some users may forget to log out and leave the session open. How many times has it happened to you? Maybe your phone suddenly rang, or you needed to leave immediately, leaving the browser on. Leaving a user session open is dangerous as someone else may use that session to extract sensitive data.

One way to fight this issue involves tracking if the user has interacted with the app within a certain period of time, then trigger logout if that time is exceeded. You may want to show a popover, or perhaps a timer that warns the user that logout is about to happen. Or you may just logout immediately when inactive user is detected.

Going one level down, what we want to do is count the time that’s passed from the user’s last interaction. If that time period is longer than our threshold, we want to fire our inactivity handler. If the user performs an action before the threshold is breached, we reset the counter and start counting again.

This article will show how we can implement such an activity tracking logic based on this example.

Step 1: Implement tracking logic

Let’s implement two functions. The first will be responsible for resetting our timer each time the user interacts with the app, and the second will handle situation when the user becomes inactive:

  • resetUserActivityTimeout – This will be our method that’s responsible for clearing the existing timeout and starting a new one each time the user interacts with the application.
  • inactiveUserAction – This will be our method that is fired when the user activity timeout runs out.
let userActivityTimeout = null;  function resetUserActivityTimeout() {   clearTimeout(userActivityTimeout);   userActivityTimeout = setTimeout(() => {     inactiveUserAction();   }, INACTIVE_USER_TIME_THRESHOLD); }  function inactiveUserAction() {   // logout logic }

OK, so we have methods responsible for tracking the activity but we do not use them anywhere yet.

Step 2: Tracking activation

Now we need to implement methods that are responsible for activating the tracking. In those methods, we add event listeners that will call our resetUserActivityTimeout method when the event is detected. You can listen on as many events as you want, but for simplicity, we will restrict that list to a few of the most common ones.

function activateActivityTracker() {   window.addEventListener("mousemove", resetUserActivityTimeout);   window.addEventListener("scroll", resetUserActivityTimeout);   window.addEventListener("keydown", resetUserActivityTimeout);   window.addEventListener("resize", resetUserActivityTimeout); }

That’s it. Our user tracking is ready. The only thing we need to do is to call the activateActivityTracker on our page load.

We can leave it like this, but if you look closer, there is a serious performance issue with the code we just committed. Each time the user interacts with the app, the whole logic runs. That’s good, but look closer. There are some types of events that are fired an enormous amount of times when the user interacts with the page, even if it isn’t necessary for our tracking. Let’s look at mousemove event. Even if you move your mouse just a touch, mousemove event will be fired dozens of times. This is a real performance killer. We can deal with that issue by introducing a throttler that will allow the user activity logic to be fired only once per specified time period.

Let’s do that now.

Step 3: Improve performance

First, we need to add one more variable that will keep reference to our throttler timeout.

let userActivityThrottlerTimeout = null

Then, we create a method that will create our throttler. In that method, we check if the throttler timeout already exists, and if it doesn’t, we create one that will fire the resetUserActivityTimeout after specific period of time. That is the period for which all user activity will not trigger the tracking logic again. After that time the throttler timeout is cleared allowing the next interaction to reset the activity tracker.

userActivityThrottler() {   if (!userActivityThrottlerTimeout) {     userActivityThrottlerTimeout = setTimeout(() => {       resetUserActivityTimeout();        clearTimeout(userActivityThrottlerTimeout);       userActivityThrottlerTimeout = null;     }, USER_ACTIVITY_THROTTLER_TIME);   } }

We just created a new method that should be fired on user interaction, so we need to remember to change the event handlers from resetUserActivityTimeout to userActivityThrottler in our activate logic.

activateActivityTracker() {   window.addEventListener("mousemove", userActivityThrottler);   // ... }

Bonus: Let’s reVue it!

Now that we have our activity tracking logic implemented let’s see how can move that logic to an application build with Vue. We will base the explanation on this example.

First we need to move all variables into our component’s data, that is the place where all reactive props live.

export default {   data() {     return {       isInactive: false,       userActivityThrottlerTimeout: null,       userActivityTimeout: null     };   }, // ...

Then we move all our functions to methods:

// ...   methods: {     activateActivityTracker() {...},     resetUserActivityTimeout() {...},     userActivityThrottler() {...},     inactiveUserAction() {...}   }, // ...

Since we are using Vue and it’s reactive system, we can drop all direct DOM manipulations i.(i.e. document.getElementById("app").innerHTML) and depend on our isInactive data property. We can access the data property directly in our component’s template like below.

<template>   <div id="app">     <p>User is inactive = {{ isInactive }}</p>   </div> </template>

Last thing we need to do is to find a proper place to activate the tracking logic. Vue comes with component lifecycle hooks which are exactly what we need — specifically the beforeMount hook. So let’s put it there.

// ...   beforeMount() {     this.activateActivityTracker();   }, // ...

There is one more thing we can do. Since we are using timeouts and register event listeners on window, it is always a good practice to clean up a little bit after ourselves. We can do that in another lifecycle hook, beforeDestroy. Let’s remove all listeners that we registered and clear all timeouts when the component’s lifecycle comes to an end.

// ...   beforeDestroy() {     window.removeEventListener("mousemove", this.userActivityThrottler);     window.removeEventListener("scroll", this.userActivityThrottler);     window.removeEventListener("keydown", this.userActivityThrottler);     window.removeEventListener("resize", this.userActivityThrottler);        clearTimeout(this.userActivityTimeout);     clearTimeout(this.userActivityThrottlerTimeout);   } // ...

That’s a wrap!

This example concentrates purely on detecting user interaction with the application, reacting to it and firing a method when no interaction is detected within specific period of time. I wanted this example to be as universal as possible, so that’s why I leave the implementation of what should happened when an inactive user it detected to you.

I hope you will find this solution useful in your project!

The post Detecting Inactive Users appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Optimizing Images for Users with Slow Network Speeds

For every website, page load time is a critical factor that can make or break the business. Thanks to the better user experience that comes with a fast-loading webpage, those who focus on page load optimization enjoy better conversion rates, better SEO, better retention, and lower bounce rates.

And this fact has been highlighted in several studies. For example, according to one of the studies, 47% of consumers expect a web page to load in 2 seconds or less. No wonder that developers across the globe focus a lot on improving the webpage load time.

Logic dictates that keeping other factors the same, a lighter webpage should load faster than a heavier webpage, and that is the direction in which our webpages should head too. However, contrary to that logic, our websites have become heavier over the years. A look at data from HTTP Archive reveals that an average webpage in 2017 was almost three times heavier than what it used to be in 2011.

With more powerful user devices, faster networks, and the growing popularity of client-side frameworks and media-rich experiences, we have started pushing more and more data to the user’s device.

However, as developers, we miss a crucial point. We usually develop and test our websites in our offices over stable WiFi or wired connections. However, when a real-user uses our website, the network speed and stability may not be that great. Especially with more and more users coming online via mobile devices, the problem of fluctuating network conditions is even more significant.

Don’t believe it? ImageKit.io conducted a study to determine the network speed reported by the Network Info API of Chrome browser for users of a website (with visitors mostly from India). It is not very surprising that almost 40% of the visitors tracked had reported speed lower than 4G, i.e., less than 700 Kbps as per the Network Info API Spec.

While this percentage of users experiencing poor network conditions might be lower if we get visitors from developed countries like the USA or those in Europe, we can still safely assume that varying network conditions impact a sizeable chunk of our users. And we have all experienced it as well. When we enter an elevator or move to the basement parking lot of a building or travel to a remote location, the mobile data download speeds drop significantly. 

Therefore, we need to keep in mind that our users, especially the ones on mobile, will invariably try to visit our website on a slow network, and our goal as a developer should be to provide them with at least a decent browsing experience.

Why optimize images for slow networks?

The ultimate goal of optimizing a website for slower networks is to be able to serve its lighter variant. This way, all the essential stuff gets downloaded and displayed quickly on the user’s device. 

Amongst all the resources that load on a webpage, images make up for most of the payload. And even if we do take care of optimizing the images in general, optimizing them further for slower networks can reduce the overall page weight by almost 30%. 

Also, additional compression of images doesn’t break the critical functionality of any application. Yes, the image quality drops a bit to provide for better user experience. But unlike stripping away Javascript, which would require a lot of thought, compressing images is relatively straightforward.

How to optimize images for a slow network?

Now that we have established that optimizing our webpage based on the user’s network speed is essential and that images are the lowest-hanging fruit to get started, let’s look at how we can achieve network-based image optimization.

There are two parts of the solution.

Determine the user’s network speed

We need to determine the network speed that the user is experiencing and divide them into buckets. For example, users experiencing speed above a certain threshold should be classified in a single group and served a particular quality level of an image. This classification is simple in modern web browsers with the Network Information API. This API automatically classifies the users into four buckets – 4G, 3G, 2G, and slow 2G, with 4G being the fastest and slow 2G being the slowest. 

// returns '4g', '3g', '2g' or 'slow-2g' var effectiveType = NetworkInformation.effectiveType;

Compress the images to an appropriate quality level

The second part of the solution is to be able to alter the compression level of an image in real-time, depending on the user’s network speed determined in step 1. It should be as simple as passing an additional parameter in the image URL when the browser triggers a load for it.

While we rely on the browser to determine the user’s network speed, a tool like ImageKit.io makes the real-time compression bit simple. 

ImageKit.io is a real-time image optimization and transformation product that helps us deliver images in the right format, change compression levels, resize, crop, and transform images directly from the URL and deliver those images via a global image CDN. We can get the image with the desired compression level by just passing the image quality parameter in the URL. Quality is directly proportional to image size, i.e., higher the quality number, larger will be the resulting image.

// ImageKit URL with quality 90 https://ik.imagekit.io/demo/default-image.jpg?tr=q-90  // ImageKit URL with quality 50 https://ik.imagekit.io/demo/default-image.jpg?tr=q-50

How else does ImageKit help with network-based image optimization?

While ImageKit has always supported real-time URL-based image quality modification, it has started supporting the network-based image optimization features recently. With these new features, it has become effortless to implement complete network-based optimization with minimum effort.

Of course, first, we need to sign up for ImageKit and start delivering the images on our website through it. Once this is done, in the ImageKit dashboard, we have to enable the setting for network-based image optimization. We get a code snippet right there that and add it to an existing service worker on our website or to a new service worker. 

// Adding the code snippet in a service worker importScripts("https://runtime.imagekit.io/<your_imagekit_id>/v1/js/network-based-adaption.js?v=" + new Date().getTime());

Within the dashboard itself, we also need to specify the desired quality level for different network speed buckets. For example, we have used a quality of 90 for images for users classified as 4G users and a quality of 50 for our slow 2G users. Remember that lower quality results in smaller image sizes.

This code snippet is like a plugin meant for use in service workers. It intercepts the image requests originating from the user’s browser, detects the user’s network speed, and adds the necessary parameters to the image URL. These parameters can be understood by the ImageKit server to compress the image to the desired level and maintain efficient client-side caching. For network-based image optimization, all that we need to do is to include it on our website and done!

Additionally, in the ImageKit dashboard, we can specify the image URLs (or patterns in URLs) that should not be optimized based on the network type. For example, we would want to present the same crisp logo of our brand to our users regardless of their network speed.

Verifying the setup

Once set up correctly, we can quickly check if the network-based optimization is working using Chrome Developer Tools. We can emulate a slow network on our browser using the developer tools. If set up correctly, the service worker should add some parameters to the original image request indicating the current network speed. These parameters are understood by ImageKit’s servers to compress the image as per the quality settings specified in our ImageKit dashboard corresponding to that network speed.

How does caching work with the service worker in place?

ImageKit’s service worker plugin by-passes the browser cache in favor of network-based image cache in the browser. By-passing the browser cache means that the service worker can maintain different caches for different network types and choose the correct image from the cache or request a new one based on the user’s current network condition. 

The service worker plugin automatically uses the cache-first technique to load the images and also implements a waterfall method to pick the right one from the cache. With this waterfall method, images at higher quality get preference over images at a lower quality. What it means is that, if the user’s speed drops to 2G and he has a particular image cached from the time when he was experiencing good download speed on a 4G network, the service worker will use that cached 4G image for delivery instead of downloading the image over the 2G network. But the reverse is not valid. If the user is experiencing 4G network speeds, the service worker won’t pick up the 2G image from the cache, because it is possible to fetch a better quality image and the resources allow for it.

And there is more!

Apart from a simple, ready-to-use service worker plugin and dashboard settings, ImageKit has a few more things to offer that make it an attractive tool to get started with network-based optimization.

Analytics

ImageKit provides us with analytics on our user’s observed network type. It gives us an insight into the number of times network-based optimization gets triggered and what does the user distribution look like across different network types. This distribution analysis can be helpful even for optimizing other resources on our website.

Cost of optimization

With network-based image optimizations, the size of the images goes down, but at the same time, the number of transformation requests can potentially go up. Unlike a lot of other real-time image optimization products out there, ImageKit’s pricing is based only on the output image bandwidth and nothing else. So with the favorable pricing model, implementing network-based image optimization not only provides a lot more value to our users but also helps us cut down on image delivery costs.

Conclusion

Improving page load performance is essential. However, there is one bit that we have all been missing – optimizing it for slow networks.

Images present an easy start when it comes to optimizing our entire website for different networks. With the support of network-based optimization features via service workers, it has become effortless to achieve it with ImageKit. 

It will be a great value add for our users and will help to improve the user experience even further, which will have a positive impact on the conversions on our website. 

Sign up now for ImageKit and get started with it now!

The post Optimizing Images for Users with Slow Network Speeds appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]