DEV Community

Cover image for Building a Simple Web App to Track Focus Time with Visibility API and Vanilla JavaScript
netsi1964 šŸ™šŸ»
netsi1964 šŸ™šŸ»

Posted on

Building a Simple Web App to Track Focus Time with Visibility API and Vanilla JavaScript

Description

In this blog post, we will go through the process of building a simple web app that tracks the time spent with and without focus using Vanilla JavaScript and the Visibility API. We will also use Tailwind CSS to make it look visually appealing.

The visibilitychange event is a part of the Visibility API that is used to detect changes in the visibility of a web page. When a page becomes hidden or visible, this event is fired. We can use this event to track the time a user spends on a web page with and without focus.

TLDR: We will build a web app to track the time spent with and without focus using the Visibility API and Vanilla JavaScript.

Demo

Over at Codepen.io I have made a short demo: Show how long time an app have been in focus

About Visibilitychange

The Visibility API is a part of the Web API that provides an interface for developers to determine the visibility of a web page. The visibilitychange event is fired when a web page's visibility changes. It is an essential tool for developers to detect if a user is interacting with their web page or not. You can find more information about the Visibility API in the mozilla web API documentation.

The Code

The HTML code of our web app consists of a simple structure that contains two p elements with IDs open-time and blur-time. The open-time element displays the time spent with focus, and the blur-time element displays the time spent without focus.

The JavaScript code uses the setInterval() method to update the time every second. The updateTime() function calculates the time spent with focus and without focus using the Date object and updates the values of the open-time and blur-time elements.

const openTime = document.getElementById("open-time");
const blurTime = document.getElementById("blur-time");

let startTime = new Date();
let blurStartTime = null;
let blurTimeElapsed = 0;

function updateTime() {
  const now = new Date();
  const timeElapsed = now - startTime;
  openTime.textContent = `Time with focus: ${formatTime(timeElapsed)}`;
  if (blurStartTime) {
    blurTimeElapsed = now - blurStartTime;
  }
  blurTime.textContent = `Time out of focus: ${formatTime(blurTimeElapsed)}`;
}

function formatTime(time) {
  const hours = Math.floor(time / (60 * 60 * 1000));
  const minutes = Math.floor((time / (60 * 1000)) % 60);
  const seconds = Math.floor((time / 1000) % 60);
  return `${hours.toString().padStart(2, "0")}:${minutes
    .toString()
    .padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
}

setInterval(updateTime, 1000);

document.addEventListener("visibilitychange", function () {
  if (document.hidden) {
    blurStartTime = new Date();
  } else {
    blurTimeElapsed += new Date() - blurStartTime;
    blurStartTime = null;
  }
});
Enter fullscreen mode Exit fullscreen mode


`

Disclaimer

This web app was created as a response to a prompt given by netsi1964 to ChatGPT+, a large language model trained by OpenAI. The purpose of this post is to provide a demonstration of how to use the Visibility API and Vanilla JavaScript to build a simple web app that tracks time spent with and without focus. Image generated using runwayml.com text-to-image tool.

Latest comments (3)

Collapse
 
pabuyajs profile image
PabuyaJS

hi @netsi1964
I like this mini web app and I see some use of it in tracking the work of my co-workers when they are performing some follow up calls and recording that in google sheets.
I noticed one issue with the blur counter. The counter didn't count in a right way and the time shown was always more then a real duration.
I made some change in the code which has resolved this issue.

const openTime = document.getElementById("open-time");
const blurTime = document.getElementById("blur-time");
const resetButton = document.getElementById("reset-button");

let startTime = new Date();
let blurStartTime = null;
let blurTimeElapsed = 0;

// Load saved times from localStorage
const savedOpenTime = localStorage.getItem("openTime");
const savedBlurTimeElapsed = localStorage.getItem("blurTimeElapsed");
console.log(savedBlurTimeElapsed)
if (savedOpenTime) {
  startTime = new Date(savedOpenTime);
}
if (savedBlurTimeElapsed) {
  blurTimeElapsed = parseInt(savedBlurTimeElapsed);
}

function updateTime() {
  const now = new Date();
  const timeElapsed = now - startTime;
  openTime.textContent = `Time with focus: ${formatTime(timeElapsed)}`;
  if (blurStartTime) {
        blurTimeElapsed += now - blurStartTime;
  }
  blurTime.textContent = `Time out of focus: ${formatTime(blurTimeElapsed)}`;

  // Save times to localStorage
  localStorage.setItem("openTime", startTime.toISOString());
    console.log("blurTimeElapsed", blurTimeElapsed)
  localStorage.setItem("blurTimeElapsed", blurTimeElapsed);
}

function formatTime(time) {
  const elapsed = new Date(time);
  return elapsed.toISOString().substr(11, 8);
}

setInterval(function () {
if (!document.hidden) {
    updateTime();
}
}, 1000);


document.addEventListener("visibilitychange", function () {
if (document.hidden) {
    blurStartTime = new Date();
} else {
    if (blurStartTime) {
    blurTimeElapsed += new Date() - blurStartTime;
    blurStartTime = null;
    updateTime(); // Update the UI immediately when the page becomes visible
    }
}
});


resetButton.addEventListener("click", function () {
  startTime = new Date();
  blurStartTime = null;
  blurTimeElapsed = 0;

  // Clear saved times in localStorage
  localStorage.removeItem("openTime");
  localStorage.removeItem("blurTimeElapsed");
});

// Load saved times in the UI
if (savedOpenTime) {
  const elapsed = new Date(new Date() - startTime);
  openTime.textContent = `Time with focus: ${formatTime(elapsed)}`;
}
if (savedBlurTimeElapsed) {
  const elapsed = new Date(savedBlurTimeElapsed);
  blurTime.textContent = `Time out of focus: ${formatTime(elapsed)}`;
}
Enter fullscreen mode Exit fullscreen mode

Best regards,
Jovan :)

Collapse
 
stanislavnemytov profile image
Stanislav Nemytov

Line 13 should be changed to:
blurTimeElapsed += now - blurStartTime;

Collapse
 
netsi1964 profile image
netsi1964 šŸ™šŸ» • Edited

Thank you @stanislavnemytov - I have fixed that problem, and have also prompted ChatGPT to add code which will use localstorage, so that the timings will be persisted - of cause adding a button to reset the timers :-)

Visit the running demo at CodePen to see the updated demo