Today I Learned

Some of the things I picked up along the way

We can hint to the browser that it can paint to the screen before individual images are decoded using the decoding attribute.

<img src="./avatar.png" decoding="async" />

Setting decoding to async lets the browser know it can paint content earlier, before it is has finished decoding this image.

The HTML standard describes this as:

Image decoding is said to be asynchronous if it does not prevent presentation of other content.

Applying this to images "below the fold" can improve performance by allowing visible content to be painted to the screen earlier.

Applying decoding="async" to images "above the fold" can cause flashing - as other content such as text can be painted to the screen before images are decoded. After they've been decoded they will appear to pop in.

If we want to ensure an image is decoded before content is painted to the screen we need to use decoding="sync", like this:

<img src="./avatar.png" decoding="sync" />

The default state for this attribute is auto, which lets the browser decide whether to use async or sync decoding:

<img src="./avatar.png" decoding="auto" />

See:

Sometimes we want to tell the user how long ago (or in how long) an event happened.

We can do this with the RelativeTimeFormat object built in to JavaScript.

For example:

const relativeTimeFormatter = new Intl.RelativeTimeFormat('en');

relativeTimeFormatter.format(-1, 'day'); // '1 day ago'

relativeTimeFormatter.format(1, 'day'); // 'in 1 day'

relativeTimeFormatter.format(6, 'week'); // 'in 6 weeks'

const frTimeFormatter = new Intl.RelativeTimeFormat('fr');

frTimeFormatter.format(-1, 'day'); // 'il y a 1 jour'

frTimeFormatter.format(1, 'day'); // 'dans 1 jour'

frTimeFormatter.format(6, 'week'); // 'dans 6 semaines'

We can make the string returned even nicer, by passing some options to the call to format:

const relativeTimeFormatter = new Intl.RelativeTimeFormat('en', {
  numeric: 'auto',
});

relativeTimeFormatter.format(-1, 'day'); // 'yesterday'

relativeTimeFormatter.format(1, 'day'); // 'tomorrow'

relativeTimeFormatter.format(6, 'week'); // 'in 6 weeks'

const frTimeFormatter = new Intl.RelativeTimeFormat('fr', {
  numeric: 'auto',
});

frTimeFormatter.format(-1, 'day'); // 'hier'

frTimeFormatter.format(1, 'day'); // 'demain'

frTimeFormatter.format(6, 'week'); // 'dans 6 semaines'

See:

We now have the at() method in JavaScript, which allows us to access individual array elements:

const fib = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];

fib.at(0); // 0

fib.at(-1); // 55

fib.at(-2); // 34

Previously, to access the last element in the array we would have had to do this:

const fib = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];

fib[fib.length - 1]; // 55

The new at() method is much more readable.

Origin trials are a feature in Google Chrome and Microsoft Edge that allow developers to opt-in to experimental features.

Individual websites need to register to enable these features and can then enable them using a meta tag like this:

<meta http-equiv="origin-trial" content="TOKEN_GOES_HERE" />

They only work for a limited amount of time, but that time can be extended if developers submit feedback about the new feature.

Available origin trials:

The Intl.NumberFormat object can be used to format percentages.

const percentage = 0.12;

const enCaPercentageFormatter = Intl.NumberFormat('en-CA', {
  style: 'percent',
});
const frCaPercentageFormatter = Intl.NumberFormat('fr-CA', {
  style: 'percent',
});
const turkishPercentageFormatter = Intl.NumberFormat('tr', {
  style: 'percent',
});

enCaPercentageFormatter.format(percentage); // "12%"
frCaPercentageFormatter.format(percentage); // "12 %"
turkishPercentageFormatter.format(percentage); // "%12"

We can force yarn to install a specific version of a nested dependency. This can be useful if a nested dependency has a bug or security vulnerability.

To change the version of a dependency of a dependency installed by yarn, we simply need to add the name of the package and the version number we want to the resolutions object of our package.json, like this:

{
  "resolutions": {
    "colors": "1.4.0"
  }
}

Adding the above two fields in package.json will make sure the nested dependency colors will be installed at version 1.4.0, regardless of what the package requiring actually specifies.

We can achieve a similar thing with npm but not natively. We can use the npm-force-resolutions package, like this:

{
  "resolutions": {
    "colors": "1.4.0"
  },
  "scripts": {
    "preinstall": "npx npm-force-resolutions"
  }
}

I was already aware of the new media queries in CSS Media Queries Level 5 that aim to improve accessibility on the web - such as prefers-reduced-motion.

Today I learned about a new media query, which hasn't been widely written about yet: prefers-reduced-transparency.

"Reduce transparency" can be toggled in the macOS system preferences like other accessibility options. It's a system-wide option and when I turn it on, I immediately notice the macOS status bar changes from translucent to opaque. I find the menu options a lot easier to read like this, so maybe I'll keep it turned on.

reduce transparency option in macos system preferences

Back to CSS: if the user has turned on "Reduce transparency" then we need to reduce, or even disable, the amount of transparency we're using on our pages and web apps.

When would people want to reduce transparency on a web page? The first example that came to my mind is text on top of images. I thought of the links to news articles on the BBC home page. Here, we can see text directly on top of an image:

article headline on an image showing transparency

The subtitle text in the example above is problematic, especially at busier parts of the article image.

It's possible but complicated to achieve a better contrast ratio between the article headline and the image beneath it.

Interestingly, on the same BBC page, we can also see an example of how the developers could style the above content for users that have prefers-reduced-transparency turned on. The text is directly beneath the image, rather than over it.

article headline under an image, showing no transparency

This solution results in the article headline on an opaque background which means there are no transparent elements.

Using the prefers-reduced-transparency media query will allow developers to show the first design to users that haven't set a "Reduce transparency" preference, and the design in the second image to users who have enabled it.

W3C Media Queries Level 5 - Detecting the desire for reduced transparency on the page

We can tell TypeScript that a value is not null with the post-fix operator !. By adding the ! at the end of a function, we are saying "this function does not return null here", even if it normally could at other times.

For example:

// No post-fix !
const countryDropdown = document.querySelector('select#country');
// countryDropdown is Element | null

// With post-fix !
const yearDropdown = document.querySelector('select#year')!;
// yearDropdown is Element

In the example above, if we know for certain that <select id="year"> exists in the DOM, then by using the non-null assertion operator ! TypeScript knows that yearDropdown is an Element. This means we can avoid having to do null type checking before interacting with the yearDropdown variable.