A Potpourri of Things I Learned Making This Blog
Contents
1 Implementing dark mode
1.1 CSS variables
1.2 The localStorage
API
2 Service Workers
3 async
and defer
attributes for <script>
tags
4 Document state
5 -pointer-events
CSS property
6 min-width
and min-device-width
ignorance
7 text size adjust
8 python server vs node server
9 Icons using SVGs
10 SASS to organize and minify CSS
While working on this blog I learned some cool stuff. I won’t go into detail explaining each thing, but simply knowing that something exists can be pretty useful. So here, in no particular order, I’ll mention the existence of some of the things I learned. If it’s something you haven’t heard of you can then consider it’s usefulness.
Implementing dark mode
I decided it would be cool and trendy to add dark mode to my website. I had no idea where to start, but in the end implementing a switch to toggle dark mode involved:
- Add some arbitrary attribute
X
to all DOM elements you want to change when switching themes - In a JS script, when the user hits the dark mode button get all elements with attribute
X
and label them with some data attribute (e.g.data-theme="dark"
). - Write some CSS for each light/dark theme conditionally applying a theme depending on the
data-*
attribute of that element.
There are some other things to consider that I won’t go into detail in, such as:
- Changing the theme given the user chosen OS theme.
- Listening for changes if the user changes his preferred OS theme.
There is no shortage of good articles explaining how to implement dark mode.
Anyway, the two new things I learned were the existence of CSS variables and the localStorage
API.
CSS variables
This actually isn’t mandatory but using CSS variables makes it a whole lot easier to implement dark mode. You simply create two themes (light and dark) where each theme will have a set of variables.
For example just in the case of switching the background color of the html
element:
:root {
--dark-background: black;
--light-background: white;
}
You can declare the variables in :root
so as to add them to the global scope.
Then you can conditionally apply a styling depending on a data-*
attribute of an element:
html {
background-color: var(--light-background);
}
html[data-theme='dark'] {
background-color: var(--dark-background);
}
The localStorage
API
Once a user toggles a certain theme you need to remember what theme was chosen. Why? Well if you don’t and you reload the page or go to another page on your site then the theme will go back to the default option which may not be what the user wants.
How can you store the user’s chosen theme? With the localStorage
API!
Using it is Super Simple™:
localStorage.setItem('theme', theme);
localStorage.getItem('theme');
localStorage.clear();
Service Workers
“Service workers essentially act as proxy servers that sit between web applications, the browser, and the network (when available).”
- MDN Web Docs
I didn’t know what service workers were, but it turns out they can be incredibly useful, they can:
- Provide offline support for your site.
- Return a cached resource but also fetch the requested resource so as to refresh the cached resource.
- Download essential resources on startup so as to perform less requests in order to render a page at some future point in time.
- Return custom messages in case your server cannot be reached or some other error occurred while fetching a resource.
Some peculiarities regarding service workers:
- They only work over
https
. - They run in their own thread.
- They follow a
Download
,Install
,Activate
lifecycle. - and more …
You can learn more about service workers with this great introductory article.
async
and defer
attributes for <script>
tags
When including an external script in an HTML file, I always simply did this:
<script src="scripts/helloWorld.js"></script>
It turns out that most of the time this isn’t what you want. The way this script is included makes it a render-blocking resource, meaning that when the HTML document is parsed and the parser gets to the script, it will stop parsing the HTML and wait for the script to download and then also wait for the script to run. All of this delays the first paint of your page (when the browser renders the first pixels to the screen).
Both the async
and defer
attributes tell the browser to not wait on the script when parsing the initial HTML document. The difference between the two is that defer
specifically waits to fetch the script until the DOM is fully built, while async
just fetches the script in the background and runs it whenever it’s ready.
I now include most of my scripts with the async
attribute, I organized them as independent modules that can run whenever the browser fetches them.
In the case of this site, the only case where I don’t use async
is the critical JS that I included in the HTML document itself. This JS is important enough (and small enough in size) and I need it to run ASAP, that I put it at the end of the <body>
tag. The script in question handles toggling dark mode.
Document state
Something that came up when using the async
tag is that I was never sure what state the DOM was in when a certain script would run. This led me to discover that the document can be in any of 3 states when it is loading:
loading
interactive
complete
You can read the state by checking document.readyState
.
The MDN Web Docs clearly explain the difference between the above three states. All I’ll say is that one state refers to when the document is being parsed, another when parsing is complete but not all sub-resources (such as images) have been loaded, and finally when the document has been parsed.
The key for me was figuring out if a script had a specific time I wanted it to execute and what that time corresponded to in terms of the document’s state. Usually I wanted to run a script that interacted with some elements in the DOM as soon as the DOM could be interacted with (I therefore listened for the DOMContentLoaded event).
A small bug I encountered was that while I had the listener, if I included the script using async
, it was possible that the event in question had already been fired so the script wouldn’t do what I wanted it to. Fixing this simply involved checking the value of document.readyState
.
-pointer-events
CSS property
On the mobile version of this site, I wanted to prevent the user from clicking on anything on the page whenever the navigation menu was pulled out. At first I thought I could just put an element over the main content of the page at a higher z-index
and that would prevent anything behind it from being clicked on, but I was wrong.
It turns out there’s a useful CSS property that can be set to achieve this.
.no-click {
pointer-events: none;
}
min-width
and min-device-width
ignorance
Something that was driving me wild was that I couldn’t understand why some CSS properties would take effect when the screen width changed size and why others didn’t take effect unless I was in responsive
mode in Chrome’s DevTools.
It turns out I was using min-width
and min-device-width
interchangeably when I really only wanted to use min-width
. I hadn’t noticed the difference between the two and this is probably due to certain “inspirations” I got looking at code on Stack Overflow (i.e. copy/pasting code).
As you could probably guess by their respective names *-width
activates when the width of the window changes while *-device-width
changes according to the width of the device’s screen.
text size adjust
This may seem obvious but when developing a website it’s a good idea to interact with it via different devices and see how it behaves in different circumstances. At one point I noticed that when I browsed this site on my iPhone with Safari and I switched to landscape mode, the font size would blow up.
As it turns out this can be fixed via a CSS property:
.disable-text-inflation {
text-size-adjust: none;
-webkit-text-size-adjust: none;
}
This basically tells the browser to not use any text inflation algorithm to resize the text size in certain instances (e.g. mobile Safari when switching to landscape mode).
python server vs node server
For simplicity I was using python to run a local development server:
python3 -m http.server
However I found out I couldn’t play videos using the HTML5 video tag. I just switched to using the http-server npm package and HTML5 videos work fine now.
Icons using SVGs
At first I was using Google’s material icons to host icons on my site. But there were a couple of problems I had with this option:
- Before icons loaded a user would see the “name” of the icon (e.g.
brightness_5
) which apart from just being ugly to see, would take up much more room than the actual icon, causing elements to shift on the page which was pretty hideous to witness. - Even with a fix for the above problem, there would always be a delay between the page loading and the icons showing up. However I didn’t want to switch to using PNG images for icons because I couldn’t easily style them with CSS to match whatever theme I wanted.
The solution was to use the Google material icons in SVG format. The source code for the SVGs can be found on GitHub.
I just copy/pasted the SVG into the HTML document directly.
As far as styling the icons with CSS I found out you can use the fill
property to color SVGs:
svg {
fill: black;
}
svg[data-theme='dark'] {
fill: white;
}
Side note you can’t use the title
attribute with the <svg>
tag, but there are other ways of giving titles to SVGs.
SASS to organize and minify CSS
When generating Lighthouse reports for my pages I focused on performance for a bit and ran into the conundrum of what to do with my CSS.
The right solution seems to be different for different situations. I read in some cases people wanted to bundle up all the CSS and put it in a <style>
tag in the HTML document. However this would not allow for caching of different files. Bundling up all the CSS also isn’t necessarily faster than having separate files, what with parallel requests thanks to the HTTP2 protocol. Besides, in the end the same amount of CSS has to be downloaded.
I opted to split my CSS the following way. Have 1 file with all the CSS that is common to all pages. And another file that is page-specific. So basically each page gets 2 CSS files.
Obviously I didn’t want to actually have 1 giant file for all the CSS that needs to be on every page. While looking for options I stumbled upon SASS, which I had heard of, but this time I wanted to try it out specifically for its ability to combine multiple .scss
files into one final .css
file. It turned out to be a lovely tool, I split my CSS however was most convenient for development (e.g. having a file just for the style of the navigation bar).
To quickly explain what SASS does it compiles .scss
files (which allow you to do cool stuff with easy syntax) into regular .css
files. If you prepend your .scss
filename with an underscore _
that also specifies to SASS that that file is only to be imported by other .scss
and not directly parsed into a .css
file itself.
So for example you could have the following files in /input_dir
:
module1.scss
_module2.scss
common.scss
common.scss
begins with some imports like these:
@use 'module1';
@use 'module2';
You can generate the .css
files with the following command:
(As a bonus the SASS compiler can also minify the generated CSS file.)
sass input_dir:output_dir --no-source-map --style compressed
In /output_dir
you will find two minified CSS files:
module1.css
style.css