Chrono: Building a Countdown Timer Progressive Web App
The WHAT, WHY & HOW of my Hashnode x Netlify hackathon submission
Introduction ๐
Hi there. In this article, I'll go through how I built Chrono, my submission for the #NetlifyHackthon. I'll be as down-to-earth and concise as possible without going into many technicalities.
Here's a quick demo:
Interested huh ๐? Keep on reading.
Idea ๐ญ
I have this folder on my PC where I stash ideas of projects I'd love to build out, or be a part of: from mini projects & SaaS ideas to full-scale startup ideas. Sometime in April 2021, I stumbled upon this amazing Dribbble about a countdown UI during some random web-surfing. Of course, I immediately saved it ๐, not knowing how I'd eventually use it. For the #NetlifyHackthon, I decided to BUIDL a unique productivity solution around that idea. Several studies have shown that people tend to be more productive if they set a time limit for themselves during activities. I put that finding to the test with my final year project I'm currently working on. While writing two subsections of a chapter, I decided to devote an hour and 30 minutes to it. Though I didn't finish the two subsections within that time, I made considerable progress than I'd normally have. Apparently, timing yourself creates a sense of urgency to finish the task at hand thereby increasing your productivity. So, there you have it.
Prerequisites ๐ง
None! Fortunately, this is NOT a code-along tutorial so you probably won't get bored. I'll be mostly explaining what I did, why I did it, and what I used to do it. Let's get started.
Technologies Used ๐
- Pure, plain-old HTML & CSS, no libraries (I decided against using a CSS library because I didn't see the need to in a small project ;). I also used some snippets from Dan Eden's pre-BEM animate.css).
- Flexbox (I "flexed" a lot in this app)
- BEM (For creating meaningful class names. I slightly strayed away from classic BEM by making use of camelCase instead of kebab-case when the "block" part of my BEM class name had a dash in it. To me, camelCase > all_other-cases except for TitleCase, maybe ๐)
- PostCSS (For compiling all my CSS partial files)
- JavaScript (For bringing the app to life)
- ESM Modules (For decluttering the code, creating reusable helper functions, and creating maintainable & scalable code)
- CacheStorage API & Service Workers (For caching the app's files & making it work offline. I used Google's Workbox for this)
- Webpack (For minimizing & bundling the CSS & JS files into a single file, mainly for performance reasons)
- ESLint + Prettier (To automatically check the source code for programmatic and stylistic errors)
- Commitlint (To ensure commit messages follow a particular convention)
- PWA (To make the app an "app", i.e., making it installable)
- Husky (For modern & hassle-free utilization of Git hooks)
- Netlify (For building the files for production & hosting)
Features โก
- Clean & Responsive UI
- Dynamic Timer
- Sweet Animations
- Sound Effects
- Settings to customize the app's functionality
- Pause/Resume Timer Function
- Dismiss Timer Function
- Browser Notifications
- Current Timer duration & status shows in the title bar
- Installable
- Works Offline
My Process ๐ป
Before I proceed, here's a labeled screenshot showing different components of the app (I messed up with the styles to get all components like this from Chrome's Dev Tools). I'll be referring to them throughout the article.
So, I started with the countdownContainer block (I almost always start with the visual part of any project). This was the toughest part of working on the app, at least for me (I literally pulled at my hair in exasperation while working on this ๐ฉ). I met some challenges "figuring out" how best to style the countdownContainer & the spinningBall thingy. Once I was done with that, coding the rest of the app flowed somewhat smoothly.
Coffee Is Love. via Tenor
The durationContainer block was next. While working on the idea for the app, I had to "wing it" most of the time because the original design didn't include features that would make the idea functional ๐คง. There had to be some way the countdownContainer block receives data for creating timers right? Right. Specifically, I needed some way to receive the minutes & seconds from the user. I created two inputs for that and styled it to taste. Then came input validation. I added an input
event listener on the two inputs to format & validate values. On the one hand, if the values entered were greater than the maximum allowed value for that input (For the minutes input, it's 99
, while the seconds input is 59
), the maximum value replaced the value for the same input. On the other hand, if the values entered were less than the minimum allowed value for that input (It's 0` for both minutes & seconds), the minimum value became the input's value. This validation also made it possible to format single digit values by adding a zero in front of it. I added this type of validation to let the user know the inputs' limit beforehand in a bid to improve the user's experience. The user's input still gets revalidated from the app's core though.
Working on the actions container was the easiest. I wanted a user to be able to change their setting before a timer countdown. I created a settings toggle button for that and added the start button beside it. The settings toggle button is meant to open up a panel where a user can adjust their settings. What I had in mind initially was for the panel to either be a popup modal or a popover. Glad I changed my mind about it & made it an accordion instead.
Ain't it sweet? ๐
The app settings accordion has custom checkboxes I made from scratch (yeah, from scratch). This was my first time doing that ๐. And yeah, I used the original checkbox element for that (using CSS pseudo-elements), meaning no weird extra element(s). The checkboxes have a change
event listener to save the new setting whenever it got toggled. The function that handled that setting change had a switch statement to effect global changes for settings that require it. A global change, in this app's context, is a change that needs to be reflected in the DOM immediately after a setting gets toggled.
Up next, we have the footer. I didn't want the app to have a traditional footer so I made it float. When the info button at the top-right corner gets clicked, the footer bounces in as it stays fixed to the bottom-left corner. I used some snippets from animate.css to achieve that. For most of the app's icons (the Twitter & GitHub icon in the footer, the pause & resume icons at the top-left corner, and the info icon at the top-right corner), I used their SVG version from the Line Awesome icon font.
Deployment ๐
Ideally, I'd have set up a Github Action for building files. I used a Netlify build script instead since the hackathon is based on Netlify.
Since I'm using Yarn and I didn't push the yarn.lock file to the repo, I made sure to set the NETLIFY_USE_YARN
environment variable to true. This ensures Netlify always uses Yarn for installing dependencies.
After each new push, I don't need to manually redeploy, all thanks to Netlify auto-deployment feature.
Here's the URL to the app: https://chrono-mvp.netlify.app/.
Improvements โจ
Here are some inexhaustive, non-conclusive suggestions for anyone looking to further improve this app. Some of them are not worth implementing (I'll drop them anyways for whatever sake ๐). Feel free to make up yours as far as it doesn't stray from the app idea.
- Add an 'Hour(s)' input to increase the max duration currently supported (which is
99:59
).I excluded it from the app because I thought it was kinda unnecessary. This is because working in rounds of short durations increases your productivity while working in long durations is mostly counter-intuitive. My decision was also influenced by the design I got the inspiration to build this app. It only supported 'Minute(s)' & 'Second(s)'. Adding this feature would mean "breaking" that design and it adds a feature no one may eventually end up using. I mean, who'd time themselves for THAT long ๐? Using an alarm would be best in that scenario in my honest opinion.
- Animate the digits when they're being updated.
If you check the Dribbble I got the app idea from, you'd notice that as a digit is being updated, it slides in while the old digit slides out. I made some progress working on this but had some issues with animating the digits so I scrapped it for now.
- Add custom themes & a theme selector button.
Here's how I think I'd do it: I'd make a list of ALL colors used in the app (Hint: Check the
_variables.css
file in thesrc/css/base
folder of the project for most of the colors). Then I'd select base colors for a new theme & create variants from it to make up for the other colors in the app's original theme. Check out Coolors.co (or any other good color palette site) for ideas. I'd really love to see how this turns out! - Keep the screen awake during the countdown.
During long countdown durations, a user's screen might dim if they're doing a task outside their system. Using the
Screen Wake Lock API
, you could keep the user's screen on for as long as necessary. This should also be added as a settings option (something like "Keep Screen Awake") because not everyone might appreciate their screen being kept on without their consent.
Version Next ๐ฅ
I was thrilled with the turnout of the MVP given my somewhat busy schedule ๐. This means that I'll most definitely continue to work on it (after my exams though ;). Wish me success). Here are my inconclusive plans for the next release (I might add new features, or drop some ๐):
- Getting a new app icon (The current app icon is SO generic & far from cool)
- Switching to React/Svelte (especially as the app's logic will only get more complicated)
- Accessibility improvements
- App analytics (using Netlify Analytics or Plausible. Never using GA again)
- View historical data (timers are saved in localStorage already. I'll probably add charts)
- Clear historical data
- Testing w/ Puppeteer
- Create a desktop app w/ NeutralinoJS (Really looking forward to this)
Bugs ๐
Found bug(s)? Do send me a DM on Twitter or, if you're up for it, make a PR to my repo with your fixes. Be sure to follow the guidelines in the CODE_OF_CONDUCT.md and CONTRIBUTING.md files ๐. Arigatou gozaimashita!
Important Links ๐
- App URL: https://chrono-mvp.netlify.app
- Quick Demo: https://youtu.be/kkC_Ez7oa1w
- Countdown UI Design: https://dribbble.com/shots/9357780-DAILY-UI-014-COUNTDOWN-TIMER
- App Logo: https://www.iconfinder.com/icons/5094677/alarm_clock_time_watch_icon
- Befunky's Designer Tool (For The Article's Cover Image): https://www.befunky.com/create/designer/
- #NetlifyHackathon Announcement: https://townhall.hashnode.com/netlify-hackathon
Conclusion ๐
Whew! That wasn't as difficult as I made it out to be ๐ . Glad you could make it this far ๐ช. I never imagined my first post on HashNode would be about a hackathon project ๐ (never). This hackathon makes it my 2nd hackathon ever & my first hackathon as a one-man team. Juggling my studies and my freelance activities leaves me with little time to commit to a task as time-consuming as writing. Anyways, I did it. This would probably be a trigger for me to try out this dev blogging thing. Probably... Until then, see you at the top ๐!
Have any constructive feedback(s) for me? I'd love to know in the comments section below or via a Twitter DM.
โญ Star the repo on GitHub
๐ต Connect with me on Twitter.
Mata ne โ