Loading scripts the right way for everyone

Differential loading is the technique where you load different content for different browsers that support different sets of Javascript features and APIs.

<script type="module" src="/js/modern.mjs"></script>
<script nomodule defer src="/js/legacy.js"></script>

This works awesome with modern browsers that understand type="module" and that will happily ignore nomodule.

The problem is that we can’t make that assumption safely. Some browsers will download the nomodule script twice and others that will download both scripts, even when they will only execute one of them.

Jeremy Wagner’s article A Less Risky Differential Serving Pattern proposes the following hack to make sure that all browsers will load a single version of the code for the page depending on whether they use modules or not.

<script>
  // Create a new script element 
  //to slot into the DOM.
  var scriptEl = document.createElement("script");

  // Check whether the script element
  // supports the `nomodule` attribute.
  if ("noModule" in scriptEl) {
    scriptEl.src = "/js/modern.mjs";
    scriptEl.type = "module";
  } else {
    scriptEl.src = "/js/legacy.js";
    scriptEl.defer = true;
  }

  document.body.appendChild(scriptEl);
</script>

In a separate article in the 2018 Performance Calendar entry Doing Differential Serving in 2019 he goes more in-depth on how to prepare the bundles that will differentially serve.

Saving preferences in the user’s browser

In working on another idea (how to allow users to select their preferred reading configuration on a web page) I came across another issue: How do we allow users to save their preferences so they don’t have to redo their work every time they visit the content.

I remembered an old project that I worked on to test the same hypothesis from a different point of view.

Rather than use that project, I’ve created the full project in Github. I will only highlight details that I think are important.

The idea is that after the user changes the settings, the browser will save them to local storage and then when the user returns, it will use the values from local storage as the new values for the font parameters.

I hear the complaint that this will have to be done for each browser the user works with… That’s true but I’m ok with it, I don’t expect every single display to look the same and I don’t expect readers to do this on their own unless they need to.

Now that we’ve set the parameters and objectives, let’s look at the code.

The first part is an HTML box with multiple sliders, one for each attribute that we want to change. The example below only handles the weight of the font. I’ve deliberately chosen to use one decimal place for the value as I’m not certain readers would be able to tell the difference.

<div class="settings">
  <fieldset>
    <legend>Font Settings: Roboto</legend>

    <label for="robotoWeight">Weight</label>
    <input  type="range"
            id="robotoWeight"
            name="robotoWeight"
            min="400" max="900"
            value="400" step="0.1">
    <p><strong>Weight</strong>:
    <span class="weightSlider"></span></p>
  </fieldset>
</div>

In the CSS section, we take advantage of CSS variables and the fact that they are “live”, if we change the value of a variable it will automatically reflect on the page.

We import the font that we will use in the project, Roboto Variable.

We then set up our variables in the :root pseudo-element. :root is similar to the html element but it has higher specificity.

The final part of the CSS block is to use the variables using the var() function. We’re still using font-variation-settings to make sure the code works in as many browsers as possible.

@font-face {
  font-family: Roboto;
  src: url("fonts/Roboto-min-VF.woff2");
}

/* Defaults */
:root {
  --line-height: 1;
  --font-weight: "wght" 100;
  --font-width: "wdth" 100;
  font-family: Roboto, sans-serif;
  font-size: 100%;
}

.content {
  line-height: var(--line-height);
  font-variation-settings:
    var(--font-weight),
    var(--font-width);
}

The Javascript block is where the magic happens.

First, we define two functions.

The first one tests if we support local storage by creating and removing an item inside and return true if the activity succeeds.

If we cannot set or remove an item the code will fail, log a message to console and return false.

We use a try/catch block to ensure that we can return from each branch and that both success and failure will be handled appropriately.

function hasLocalStorage() {
  try {
    localStorage.setItem(mod, mod);
    localStorage.removeItem(mod);
    return true;
  } catch (e) {
    console.log('Local Storage Not Supported');
    return false;
  }
}

The second function is a convenience function to insert rules into the :root pseudo-class in the base stylesheet.

It first captures a reference to the :root CSS rule in our stylesheet.

Then we build the CSS variable by setting a property in our stylesheet rule. We add the two dashes (--) required for CSS variables and the name, with the value as the second parameter.

function setRootVar(name, value) {
  let rootStyles = document.styleSheets[0].cssRules[1].style;
  rootStyles.setProperty('--' + name, value);
}

We use the oninput handler to tell the browser what to do when the content of the input element changes.

In this case, we call setRootVar to set the font-weight CSS variable using the string "wdth" and the value of the slider as the second parameter. I decided to go the extra mile so it would be easier to build the variable and use it when we update the font-variation-settings CSS.

I’ve also stored two elements in local storage:

One is the full value of font-weight: the string and the value of the slider.

The other one is just the value of the weight slider. I’ve done this to make it easier on myself when retrieving the data later.

weight.oninput = function() {
  weightSlider.innerHTML = weight.value;
  // setting the style
  setRootVar('font-weight', ' "wght" ' + weight.value);
  localStorage.setItem('font-weight', ' "wght" ' + weight.value);
  localStorage.setItem('weight-value', weight.value);
};

The last block is a DOMContentLoaded event handler to retrieve the settings from localStorage, set the font attributes accordingly and provide defaults if the attribute is not stored in local storage or is empty.

If the attribute exists and is not empty then we update the position of the slider and the value it reflects. This way the user will not have to redo the sliders with the values they wanted (and that are reflected in the text).

If the value is not set or is null, we provide defaults that match the values we set in the CSS stylesheet.

window.addEventListener('DOMContentLoaded', event => {
  if (localStorage.getItem('font-weight') &&
    localStorage.getItem('font-weight') !== null) {
    setRootVar('font-weight', localStorage.getItem('font-weight'));
    robotoWeight.setAttribute(
      'value',
      localStorage.getItem('weight-value'),
    );
    weightSlider.innerHTML = localStorage.getItem('weight-value');
  } else {
    setRootVar('font-weight', 400);
    robotoWeight.setAttribute(
      'value',
      localStorage.getItem(400),
    );
    weightSlider.innerHTML = 400;
  }
});

There are some things I’m still working on. Some times the values do not load properly and I’m trying to figure out why.

This is the first step in providing a way to save settings for an app. We might want to expand the test to something closer to a full-blown reading application.

Compound Grids for Art Directed Content

There are times when our traditional grids don’t work as well as we’d want them to, particularly in art-directed content.

Taking the default 12-column grid I use as default we can code it like this:

body {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-column-gap: 2vw;
  grid-row-gap: 2vh;
  align-content: start;
}

This code is good and it does a lot of the work of grids and layout. But there are times when we need a little more flexibility.

For example, we might want to have space between our main body and the aside portion of the page, something similar to this layout.

Compound Grid Image. Image taken from a page designed and built by Andy Clarke

Notice how there is space that is smaller than the text in the aside element to the left of the image?

If we look at the page using Firefox’s Grid Inspector, we can see that the layout of the page it’s not uniform.

Compound grid image viewed with Firefox Grid Inspector. Image taken from a page designed and built by Andy Clarke

The image uses a 12-column grid but it’s arranged differently, as shown in the code below:

All the values of grid-template-columns should be in one line. I broke them down into rows for readability.

The compound grid breaks the content into three areas of 2fr (2 equal portions of the screen width) separated by 2 columns of 1fr (1 equal portion of the screen width)

body {
  display: grid;
  grid-template-columns:  2fr
                          1fr 1fr
                          2fr 2fr
                          1fr 1fr
                          2fr;
  grid-column-gap: 2vw;
  grid-row-gap: 2vh;
  align-content: start;
}

This gives us flexibility in how we layout the content. without changing it. We can still place content in two 2fr increments or we can make the content narrower by using the 1fr columns as margins.

Using the grid above we can do interesting things. We can have asides with auxiliary information on either side of the main content just by changing the placement in the grid.

For left-side aside content we could do something like this:

aside {
    grid-column: 1 / 3;
    align-self: end;
}
main {
    grid-column: 4 / 8;
}

but if we want to flip it to the right, the code changes slightly:

main {
    grid-column: 2 /6;
}

aside {
    grid-column: 7 /9;
    align-self: end;
}

We can also use this as a further refinement to the grids we discussed in Art Directed Layouts and CSS Grid. We still get 12 columns in our grid but breaking it the way we did gives us additional flexibility to play and experiment.

Mirroring a site using Wget

Wget is the GNU/FSF alternative to CURL used to retrieve files from the network via command line. Getting a single file is easy but trying to get an entire directory it’s not so easy.

I’ve used a similar version to this command in the past but it turned into huge downloads that would take forever to complete.

I found this version in Guy Rutenberg’s site and liked it after I used it to mirror a site.

The command is:

wget --mirror \
--convert-links \
--adjust-extension \
--page-requisites
--no-parent http://example.org

These are flags we’re using:

  • –mirror – Mirrors a site by making the download recursive and enabling additional flags
  • –convert-links – After the download is complete, convert the links in the document to make them suitable for local viewing. This affects not only the visible hyperlinks but any part of the document that links to external content
  • –adjust-extension – If a file of type application/xhtml+xml or text/html is downloaded and the URL does not end with the regexp \.[Hh][Tt][Mm][Ll]?, this option will cause the suffix ‘.html’ to be appended to the local filename.

    This is useful, for instance, when you’re mirroring a remote site that uses ‘.asp’ pages, but you want the mirrored pages to be viewable on your stock Apache server

  • –page-requisites – Download things like CSS style-sheets and images required to properly display the page offline.
  • –no-parent – Do not go to the parent directory. It is useful for restricting the download to only a portion of the site.

Alternatively, the command above may be shortened:

wget -mkEpnp http://example.org

If the server’s robots.txt file is not configured to forbid it, you can run this command to mirror the specified directory and prepare it for offline viewing by making some changes to the content and downloading all the needed materials to make the page viewable offline or in a different server.

Hiding content on the page for advanced users

I was browsing through Brad Neuberg’s blog I came across stretchtext.js and I found it interesting enough to take a deeper look.

Stretchtext is a Javascript implementation of a hypertext going back to the work of Ted Nelson in 1967. Taken from Neuberg’s article:

Stretchtext consists of ordinary continuous text which can be “stretched”, or made longer and more detailed. By pointing at specific areas and pulling the throttle in the “magnify” direction, the reader may obtain greater detail on a specific subject, or area of the text. The text stretches, becoming longer, with replacement phrases, new details, and additional clauses popping into place.

The good of this structure should be evident. The reader remains oriented. If he loses track of where he is, he “shrinks” the text to a higher, shorter level; if he wants to study a topic in more detail, he magnifies it.

An important editorial constraint on stretchtext, then, is that details and narrative arrangements must remain fixed in their relative order through different levels of stretchtext. However, in one respect it appears to be easier to write than ordinary text: rather than deciding what details to “put in” and “leave out,” the author merely assigns altitudes (or “fineness”?) to topics and details, thus determining at how great a magnification they will be seen.

[…]

Editorially, the stretchtext is (1) always the same unit, and (2) always a continuous narrative. Thus it is unlike hypertexts with discrete chunks and breaks.

So, the idea is that we begin with our basic text or text for our beginner audience and then we can create additional material that we can “stretch” to allow for more advanced content at deeper levels or we can have multiple sections of stretched content at the same level.

The tool adds complexity to the writing process. If the result is 15 pages how do we create the content? Do we write all 15 and then insert the markup to create the stretch points? Do we create an initial five pages and then expand the content as needed?

In the Codepen Example I’m using to validate the usefulness of Stretchtext as a writing tool, I hid the quote from Ted Nelson to demonstrate how it works and to show that you can hide arbitrarily large portions of the content.

How I envision this tool working is to write the full content and then hide more advanced content using Stretchtext and let the user decide if they want the additional material or not.