In this post, we create a custom JavaScript front-end script to track user activity on a webpage.
The tracking of course is not as sophisticated as Google Analytics. It surely is incomplete, may have bugs, and might miss edge cases. But it’s a starting point nonetheless.
Live Example
First, let’s see the page information and real-time counters in the table below. Scroll, click (anywhere in the body, then on dummy buttons and link at the bottom), move mouse, and key press to see the values increment. Change tabs or windows to see timer stop and start. Sit idle for 10 seconds, and it will stop too.
Time Zone | |
Region | |
Country | |
Page | |
Time Spent (secs) | 0 |
Scrolling | |
Mouse Clicks | |
Button Clicks | |
Link Clicks | |
Mouse Movement | |
Key Presses |
Dummy Buttons:
The Implementation
Let us quickly discuss a few implementation details:
The Page Path
We get that by window.location.pathname
, as soon as the DOM content loads.
Getting Time Zone, Region and Country of the User
The details of how to do this is explained in this post: get user country and region on browser with JavaScript only.
Initial Wait
When the page loads the timer starts automatically. The initial wait is 3 seconds, in which if there’s no activity, the timer stops.
Interval Wait
For every activity, we reset the start time. The interval wait is 10 seconds from the start time, after which, if there’s no activity recorded, we stop the timer.
10 seconds is a reasonable choice because if the user is not interacting with the screen in any way for more than 10 seconds, then they’re most likely not viewing the page. But it’s still an arbitrary value that you can change to more or less than 10 seconds.
Timer
Our calculations for the timer are in milliseconds, which we convert to hh:mm:ss format with a helper method.
Timer Check Interval
We check and refresh the timer every second.
Screen Visibility
Every second we check if the screen is visible or not with Document.hidden check before doing any other calculation. So if you’re on another tab or window, the timer stops immediately.
Events to Track
We listen to mouseup, keydown, scroll and mousemove events. For Button
and Link
clicks, we check their event target nodeName to be “Button” or “A” respectively. In the case of Button clicks, we keep track of total and individual click count.
Using the Tracked Values
Since this implementation is for demonstration, we use the counter values for display only. In the code shared below, we do that by getting the elements by their ids and replacing their innerHtml with their counter values.
To create a complete custom analytics solution of your own, however, you’ll need to send these values to the backend and persist them in a database. You could use polling or sockets to transfer the data from the front-end.
To find the location of the user, you can use two approaches: 1. find the country and the region of the user on browser with JavaScript only. 2. For more accurate detection of the user country use a geolocation API, such as ipstack, and track a session with cookies.
You could also exclude yourself (the owner) from being tracked following something similar to what I have shared for excluding yourself from Google analytics.
The Code
<script>
var INITIAL_WAIT = 3000;
var INTERVAL_WAIT = 10000;
var ONE_SECOND = 1000;
var events = [
"mouseup",
"keydown",
"scroll",
"mousemove"
];
var startTime = Date.now();
var endTime = startTime + INITIAL_WAIT;
var totalTime = 0;
var clickCount = 0;
var buttonClicks = {
total: 0,
};
var buttonClickCount = 0;
var linkClickCount = 0;
var keypressCount = 0;
var scrollCount = 0;
var mouseMovementCount = 0;
var linkClickCount = 0;
setInterval(function () {
if (!document.hidden && startTime <= endTime) {
startTime = Date.now();
totalTime += ONE_SECOND;
document.getElementById("timer").innerHTML = formatTime(totalTime);
}
}, ONE_SECOND);
document.addEventListener("DOMContentLoaded", function () {
document.getElementById("page").innerHTML = window.location.pathname;
events.forEach(function (e) {
document.addEventListener(e, function () {
endTime = Date.now() + INTERVAL_WAIT;
if (e === "mouseup") {
clickCount++;
document.getElementById("click").innerHTML = clickCount;
if (event.target.nodeName === 'BUTTON') {
if(!buttonClicks[event.target.innerText]){
buttonClicks[event.target.innerText] = 0;
}
buttonClicks[event.target.innerText] += 1;
buttonClicks.total += 1;
document.getElementById("button").innerHTML = JSON.stringify(buttonClicks, null, 2);
}
else if (event.target.nodeName === 'A') {
linkClickCount++;
document.getElementById("link").innerHTML = linkClickCount;
}
}
else if (e === "keydown") {
keypressCount++;
document.getElementById("keypress").innerHTML = keypressCount;
}
else if (e === "scroll") {
scrollCount++;
document.getElementById("scroll").innerHTML = scrollCount;
}
else if (e === "mousemove") {
mouseMovementCount++;
document.getElementById("mouse").innerHTML = mouseMovementCount;
}
});
});
});
function formatTime(ms) {
return Math.floor(ms / 1000);
}
</script>
See also
- SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
- Yup Date Format Validation With Moment JS
- Yup Number Validation: Allow Empty String
- Exactly Same Query Behaving Differently in Mongo Client and Mongoose
- JavaScript Unit Testing JSON Schema Validation
- Reduce JS Size With Constant Strings
- JavaScript SDK