Keyboard events

In addition to pointer events, we should also provide a way for keyboard-only users or those users who prefer keyboard navigation to access content. This is particularly important when using custom items like video players and others.

We’ll look at how to use UI events and Keyboard Event Codes to work with our content.

keyboard shortcuts

We can use keyboard events and the keyboard event code values to create shortcuts for our apps. In this example, also from the custom video player, we set up a keydown event in the window object to listen for key presses.

When the key matches one the choices in the switch statement, we do something with it depending on the key that was pressed.

If either the space or enter keys were pressed we check if the video is paused. If it is we play the video and change the pause icon to the pause one. If the video is not paused then we pause it and change the video to the play icon.

If the user pressed the left arrow key then we rewind the video 30 seconds.

If the user pressed the right arrow key then we fast forward the video 30 seconds.

// Event handler for keyboard navigation
window.addEventListener('keydown', (e) => {
  switch (e.code) {
    case 'Enter': // Enter
    case 'Space': // Space
      e.preventDefault();
      if (video.paused) {
        video.play();
        playIcon.src = 'images/icons/pause.svg';
      } else {
        video.pause();
        playIcon.src = 'images/icons/play-button.svg';
      }
      break;

    case 'ArrowLeft': // Left arrow
      console.log(`back 30`);
      skip(-30);;
      break;

    case 'ArrowRight': // Right arrow
      console.log(`forward 30`)
      skip(30);
      break;
  }
});

So, in addition to having pointer event support for the player controls, we can also control the video playback using the keyboard.

Two things to keep in mind:

  • We use the keydown event because it’s one of the two events defined in the spec and the first event of a chain described in Keyboard Event Order
  • We can get away with attaching the event to the window because the example has one large video on the page an no other elements that will interact with the elements where we listen

What else can we do?

As with many other web items we just scratched the surface of what we can do with keyboard events. In a full-fledged app, we can add keyboard shortcuts that use different keyboard combinations, just like desktop apps.

There are more ways to use the keyboard. Still thinking about it.

Links and Resources

Pointer Events

Having to code for touch, mouse and keyboard events is a pain. There has to be a way to make the code easier to work with events. The code will not be shorter (we have to work around the lack of pointer event support in mobile Safari) but it’ll be easier to reason through and, in time, will become shorter when mobile Safari implements the API.

In this post, we will discuss both pointer and keyboard events, how to use them together to provide a better user experience, and alternatives for browsers that don’t support the API (particularly Safari)

Pointer events

Pointer events provide a unified interface for different pointing devices in different devices. So instead of adding events for touch and mouse events, we can run a single pointer event that will work in all supported devices.

Current versions of Safari for both desktop and mobile do not support Pointer Events. caniuse.com indicates that Safari Technical Preview for desktop supports the API. That leaves mobile Safari as the only browser that doesn’t support the API now or in the projected future.

The events available to the API are listed in the table below.

event description
pointerover The pointer has entered the bounding box of the element. This happens immediately for devices that support hover, or before a pointerdown event for devices that do not.
pointerout The pointer has left the bounding box of the element or screen. Also after a pointerup, if the device does not support hover.
pointerenter Similar to pointerover, but does not bubble and handles descendants differently. Details on the spec.
pointerleave Similar to pointerout, but does not bubble and handles descendants differently. Details on the spec.
pointerdown The pointer has entered the active button state, with either a button being pressed or contact being established, depending on the semantics of the input device.
pointerup The pointer has left the active button state.
gotpointercapture Element has received pointer capture.
lostpointercapture Pointer which was being captured has been released.
pointermove The pointer has changed position.
pointercancel Something has happened and it’s unlikely the pointer will emit any more events. You should cancel any in-progress actions and go back to a neutral input state.

The idea is that we replace click or hover with the equivalent pointer events so that we code the event only once. If needed, we can create different event responses based on the pointerType attribute if we need different responses.

At the mmost basic the script to handle responses would look like this:

const myButton = document.getElementById('myButton');

if (window.PointerEvent) {
  myButton.addEventListener("pointerdown", function(evt) {
    // add the pointer down code here
  });
} else {
  // fall back on touch and mouse events
  myButton.addEventListener('touchstart', function(evt) {
      // prevent compatibility mouse events and click
      evt.preventDefault();
      // do what you need for touchstart
  });
  myButton.addEventListener('mousedown', function(evt){
    evt.preventDefault();
    // whatever you need to do with mousedown
  });
}

We can further refine the pointer event handler by adding custom code based on the type of pointer device that triggers the event.

const myButton = document.getElementById('myButton');

if (window.PointerEvent) {
  myButton.addEventListener("pointerdown", function(evt) {
    switch(evt.pointerType) {
      case "mouse":
        console.log('mouse input detected');
        break;
      case "pen":
        console.log('pen/stylus input detected');
        break;
      case "touch":
        console.log('touch input detected');
        break;
      default:
        console.log('pointerType is empty or could not be detected');
    }
  });
}

This level of detail may not always necessary but it’s nice to know that we have the flexibility of targeting different types of devices in the same code.

This barely scratches the surface of what you can do with Pointer Events. Until there is consistent support for the API across browsers I will hold off doing any further work other than replacing touch and mouse events.

Polyfill

Until Safari implements Pointer Events we have to polyfill the API to make sure that it works consistently in all our target browsers.

There are many polyfills for Pointer Events like PEP from the jQuery Foundation, Points from Rich Harris, and others.

I’ve chosen to work with PEP.

For the demo, I’ve chosen to link to the jQuery CDN. In a production site, I’d likely download it and link to it locally.

The first step is to link to the script so it’ll be available on the page.

<script src="https://code.jquery.com/pep/0.4.3/pep.js"></script>

We then set up the elements that we want to use the polyfill.

According to the spec, the touch-action CSS property controls whether an element will perform a “default action” such as scrolling, or receive a continuous stream of pointer events.

The polyfill uses a touch-action attribute instead of the CSS property. For PEP to work correctly, you will, therefore, need to include touch-action attributes in your HTML that mirror any touch-action properties in your CSS.

The button below has been configured not to accept any touch events.

The output element (id = o) will hold the results of the script.

<button id="b" touch-action="none">Test button!</button>
<p id="o"></p>

Finally, the script element will run all out code. The idea is that when a pointerdown event is triggered on the button, the browser inserts information about the event:

  • What type of device it was (pointerType)
  • The type of event it was (type)
  • The element that received the event (target.nodeName)
<script>
myButton = document.getElementById("b");

myButton.addEventListener( "pointerdown", (e) => {
  document.getElementById("o").innerHTML = "that was a " +
    e.pointerType + " " + e.type + " on a "+ e.target.nodeName;
} );
</script>

You can see the code in action in this pen

Links and Resources

Revisiting grids

I was looking at Brad Frost’s site to look at how he did the little circles at the top and bottom of the page. Hint: he does with a ton of SVG circles and some very interesting use of JavaScript to create the effect.

When I saw the CSS code I came across multiple definitions for grids and that got me thinking, again, about automation and how to use CSS properties to make the code easier to work with.

CSS properties and Houdini custom properties

One of the problems about regular CSS variables (also known as CSS custom properties) is that they are all represented as strings. This limits what we can do with them on JavaScript and how we use them in CSS.

CSS Properties and Values API Level 1 provides enhanced custom properties, defined in JavaScript, that overcome the deficiencies of the initial implementation of custom properties.

For more information about Houdini CSS Properties and Values see my other posts on the topic: CSS Houdini Properties & Values and CSS Houdini: Present and Future of CSS

We define three custom properties in JavaScript. We use feature detection to make sure that the browser supports Houdini Custom Properties before we add them.

For each property we register, we define four properties:

  • name defines the name of the property that we’ll use throughout the application
  • syntax refers to the syntax for the element as defined in the supported syntax name section of the specification
  • inherits tells the browser is the property should be inherited by the element’s children. The default is false
  • initial value gives the default value for the property

I place the definitions inside a feature query to see if the browser supports the registerProperty in the CSS object then we register the properties with the syntax below

if ('registerProperty' in CSS) {
  CSS.registerProperty({
    name: '--container-width',
    syntax: '<number>',
    inherits: false,
    initialValue: 44,
  });

  CSS.registerProperty({
    name: '--grid-cell-size',
    syntax: '<integer>',
    inherits: false,
    initialValue: 200,
  });

  CSS.registerProperty({
    name: '--grid-gap-size',
    syntax: '<number>',
    inherits: false,
    initialValue: 1.5,
  });
} else {
  console.log('Houdini custom properties not supported');
  // Add non-Houdini custom features here
}

The layout container

/* Layout container */
.container {
  margin: 0 auto;
  padding: 0 1rem;
  max-width: calc(--var(--container-width) * 1rem)
  position: relative;
  z-index: 1;
}

/* Wide layout container variation */
.container--wide {
  --container-wdith: 79.2;
  max-width: calc(--var(--container-width) * 1rem);
}

/* Narrow layout container variation */
.container--narrow {
  --container-wdith: 37.4;
  padding: 0 1rem;
  margin: 0 auto 1rem;
  max-width: calc(--var(--container-width) * 1rem);
}

Defining the grids

Rather than defining the whole size of the grid in its definition, we only need to define those values that are common to all different types of grid we want to have ready as default.

When researching the structure of the default grids, I came acrossAuto-Sizing Columns in CSS Grid: auto-fill vs auto-fit by Sara Soueidan.

In this case, I don’t want to add new column cells to fit the available space so auto-fit is not the right answer. So we use auto-fill to keep the number of columns constant while make them fill the available space.

Here we use another variable to change the value of the minimum size for the cells. If we don’t redefine the value of a variable then it uses the default value defined when we registered the variable.

/* Grid */
.grid {
  display: grid;
  grid-gap: var(--grid-gap-size);
}

.grid--small {
  grid-template-columns: repeat(auto-fill, minmax(var(--grid-cell-size), 1fr));
  grid-template-rows: auto;
}

.grid--med {
  --grid-cell-size: 320;
  grid-template-columns: repeat(auto-fill, minmax(var(--grid-cell-size), 1fr));
  grid-template-rows: auto;
}

.grid--loose {
  --grid-cell-size: 400;
  grid-template-columns: repeat(auto-fill, minmax(var(--grid-cell-size), 1fr));
  grid-template-rows: auto;
}

.grid--gap-large {
  --grid-gap-size: 2.5;
  grid-gap: calc(var(--grid-gap-size * 1rem));
}

Using grids areas

One of the more esoteric aspects of the grid is that you can use grid template areas to associate elements with it and then define those areas using an ASCII-like layout.

The first step is to associate elements with a grid-area element.

.header {
    grid-area: hd;
}
.footer {
    grid-area: ft;
}
.content {
    grid-area: main;
}
.sidebar {
    grid-area: sd;
}

Next, we define the grid. Working with template areas requires one additional step.

For each cell that we want to use we need to define what area we want to place it in by naming the area. In the example below, we associate each cell of our 9-column grid with one of the areas we defined earlier.

If we want to leave a cell blank then use a period (.) in the cell definition.

.wrapper {
  display: grid;
  grid-template-columns: repeat(9, 1fr);
  grid-auto-rows: minmax(100px, auto);
  grid-template-areas:
    "hd hd hd hd   hd   hd   hd   hd   hd"
    "sd sd sd main main main main main main"
    "sd sd sd main main main main main main"
    ".  .  .  ft   ft   ft   ft   ft   ft";
}

Once we’ve created the areas and assigned cells to them we can use them in regular HTML like the code below. this will automatically create the layout we specified with the grid-template-areas descriptor.

<div class="wrapper">
    <div class="header">Header</div>
    <div class="sidebar">Sidebar</div>
    <div class="content">Content</div>
    <div class="footer">Footer</div>
</div>

Using eleventy

We saw how to create a working templating system using one of many available templating engine (Nunjucks from Mozilla). But it takes a while to get that first step where the template building works as intended.

There are platforms that automate the process. The one that has been on my radar for a while, and was recommended to me, is eleventy

Unlike the previous two strategies, Eleventy requires a dedicated setup and is geared towards Node development rather than Gulp.

Rather than start from scratch I’ve taken the eleventy base blog template from Github as my starting point. I will also research other areas like creating sections for content other than a blog.

The first steps are to clone the base blog repository, change to the directory we just clones and installing the NPM packages:

git clone https://github.com/11ty/eleventy-base-blog.git
cd eleventy-base-blog
npm install

I use Python’s built-in server when testing static sites without Gulp or Node access:

python3 -m http.server 2500 --bind 127.0.0.1

When we talk about Gulp and whether integrating it with Eleventy makes sense or whether we should create new Evelenty plugins we’ll revisit the question of what to use to serve the content while on development.

With the software installed we can do exploration and analysis of the tool, how it works and whether it meets my needs given my workflows and needs.

What Eleventy gives out of the box

The following elements come with Eleventy out of the box:

  • Markdown content authoring and conversion to HTML
    • Extensible via Markdown parser plugins
  • Code syntax highlighting using Prism.js
  • Templating with Nunjucks; other engines available
    • Different template layouts
    • Customizable using the template engine’s infrastructure

In the next section I will take a look at what I need and want to add, if possible, and whether Eleventy itself or a third-party tool will best accomplish the tasks.

Tasks to add

There are several things I’d like to explore using in an environment provided by Eleventy.

Some of these are more conceptual and require thinking if I should use Eleventy alone or in conjunction with a Gulp-based build system.

Looking at sample sites like v8.dev, alexcarpenter.me, Zell Liew, duncan.dev, zachleat.com, and others in the list of sites using Eleventy (some of which also share their source code) makes it easier to figure out how to accomplish a task.

Adding additional types of content

Because it’s Markdown it shouldn’t be hard to add new directories with content and then just pass them to the processor.

We can also create collections of specific posts sorted however we need them to be to accomplish our goals. The code to create a collection of posts sorted by date looks like this:

eleventyConfig.addCollection('posts', collection => {
  return collection
    .getFilteredByGlob('src/blog/*.md')
    .sort((a, b) => b.date - a.date);
});

We can create collections for each type of content we want to build and we can group them in different ways. Look at the documentation for collections, and Working with Collections for more information of what they can do and how you can organize them.

Definition Lists in Markdown

Markdown-It provides a plugin (markdown-it-deflist) to create definition lists (dl, dt, and dd) elements

The code to configure the Markdown plugins looks like this inside the Eleventy build file (.eleventy.js) looks like this.

The Markdown section of the Eleventy build file that deals with Markdown, Markdown plugins and configuration looks like the block below.

const markdownIt = require('markdown-it');
const markdownItAnchor = require('markdown-it-anchor');
const markdownItAttrs = require('markdown-it-attrs');
const markdownItDefList = require('markdown-it-deflist');

const markdownItConfig = {
  html: true,
  breaks: true,
  linkify: true,
};

const markdownItAnchorConfig = {
  permalink: true,
  permalinkClass: 'bookmark',
  permalinkSymbol: '#',
};

const md = markdownIt(markdownItConfig)
  .use(markdownItDefList)
  .use(markdownItAttrs)
  .use(markdownItAnchor, markdownItAnchorConfig);

See the Pandoc definition lists for the syntax.

Service Worker

There is an Eleventy plugin that provides a way to create a precaching service worker. In order to use a more sophisticated service worker with custom routes, we would have to fork the plugin and make it do what we want or submit a PR for the code we want to use instead.

I’ll go with the first route. In looking at the code I discovered that it uses a very generic generateSW worker builder script. See builder.js in the Eleventy plugin repo to see how it’s built.

If I want to use a custom service worker I’d have to use injectManifest and have the customized service worker ready for the manifest to be injected. This allows me to use advanced features available to service workers later if I choose to. A solution may be possible with Gulp as the driver, as discussed in the next section.

Integrating Gulp functionality: Gulp tasks or Eleventy plugins?

Eleventy comes with a small set of plugins for basic blog-like functionality but, beyond that, the functionality of Eleventy is sparse. Gulp, on the other hand, has a rich ecosystem of pluggable tasks, like image compression, SASS and Babel transpilation, service worker generation and precaching injection, and other tasks.

Rather than build Eleventy-specific plugins I’ve chosen to implement the functionality as Gulp tasks using its ecosystem.

The tasks are listed below and described after the list

Tasks for ~/code/eleventy-blog/gulpfile.js
├── generate-sw
├── sass
├── processCSS
├── babel
├── imagemin
├── createResponsive
├── clean
├── serve
└── default

The descriptions are as follows:

  • generate-sw will inject the files specified in a configuration file into an sw.js template
  • sass transpiles SASS into CSS
  • processCSS uses PostCSS and Autoprefixer to add prefixes where needed
  • babel transpiles ES2015+ into Javascript supported by older browsers based on the browsers we want to support
  • imagemin compresses images using different libraries depending on the format. It supports PNG, JPG, WebP, and SVG
  • createResponsive creates a set of responsive images to be used later
  • clean deletes the destination directory
  • serve starts a local development server with the content of the destination directory
  • default runs the following tasks in the order specified: processCSS, imagemin, and generate-sw

Netlify CMS: Yay or nay?

There is an interesting option for Eleventy-based sites. Netlify provides a CMS package that creates a CMS Admin for your static site.

It also allows for integration with third-party tools like Cloudinary, Netlify Large Media or Uploadcare for image management and editing.

What I like is that it provides a graphical way to interact with the static site and manipulate images without having to work with content directly and eliminating some of the Gulp workflows we discussed in the last section.

On the other hand, this may be the best solution for client work where we want to hide the complexity of a static site when people are adding content.

Conclusion

Eleventy presents an interesting solution to the static site generation issue. It gives you enough flexibility to do things how you want them without getting on your way.

I’m pushing ahead with further analysis and explorations of what you can do with the platform and the technologies we discussed here. One thing in particular that I want to research further is how deeply tied are the Netlify CMS and the Netlify platform.

Serving two masters and serving neither master well

This is a response to a page that raised enough concenrts for me to want to write about it but not important enough to put into my regular post rotation

The page that started the research and this piece is: Frequently Raised Concerns. Like they did I will monologue and do my best to provide additional alternatives and try not to come too hard on the criticism.

TL;DR

Supporting a single rendering engine and OS is dangerous, particularly when such rendering engine lags behind in standards support and in communication towards developers, other browser makers and standards bodies, may not be the best solution. The fact that it has a predominant market share in mobile devices in the US doesn’t mean that’s the case everywhere or those predominant devices are as powerful everywhere.

Assuming that people will only read your books in the platform/browser combination that you support is disingenuous and likely to turn people away from your offering.

Readers preferences drive people to paper books, regardless of their profession or whether they evangelize the open web platform. Calling their efforts “yapping” makes people less inclined to take the argument of the person making it seriously.

If you can’t be bothered to provide a way to increase the font size of a “book” on your application then why should people bother reading books in your platform at all?

The web was never designed as a paginated medium. In its origins, the shared documents were as long as they needed to be. You’d have to ask your readers if they are bothered by the scrolling. Likewise, pigeonholing the web and the technologies it uses due to a vision that book-like content will make people read online is misguided at best.

We’ve also built different expectations for web content… We have different ways to paginate that don’t require content to be laid out as a book with page swipe animations every time we move around the content.

What are we making: a web app for reading or a reading app?

It’s tempting to try and duplicate the feel of an application when working on the same functionality as one. When we do we lose the flexibility of the web as a medium? When working online, more so than when we work in apps, we have an infinite canvas that we can use to creatively frame our content.

Most ebook readers are tied to the device’s rendering engine… Webkit in the case of iOS (browsers other than Safari don’t get the same features Safari does, just a web view), whatever specific rendering engine powers the webview for your Android app and whatever rendering engine powers the web browser you’re using (EdgeHTML for current versions of Edge, Gecko for Firefox, and Blink for Chrome, Opera, Brave, and, soon, Edge).

And the number of options is what makes the question of iPad first an interesting one… if we’re going to support Webkit in iOS as your primary platform (which is what saying you’re iPad first) means for other devices and renderers, particularly when other rendering engines support a wider palette of APIs and tools?

Most of our content doesn’t care what form factor we are in and what’s the size of the screen but all e-readers have a way for users to control the size of the text on the screen. The challenge comes when working with richer multimedia content. Then we need to make sure that related items work together as designed. Something web developers have been doing for decades.

What affordances are we offering? Which ones matter

Before we talk about affordances let’s define some terms so we start from a common base.

The word “affordance” was originally invented by the perceptual psychologist J. J. Gibson (1977, 1979) to refer to the actionable properties between the world and an actor (a person or animal). To Gibson, affordances are a relationship. They are a part of nature: they do not have to be visible, known, or desirable.

From Affordances and Design — Don Norman

When planning a new interface or product some of the questions I ask are:

  • What affordances are we offering our users?
  • Do the affordances match user expectations (and if they don’t why not)
  • How can we leverage the web as a platform to improve the reading experience?

The first affordance that I look for is navigation… How do we move between pages of content and how do we navigate our content.

But apps and the web are different. From any point in a document we can navigate in any direction or we can keep the navigation metaphor we bring along from physical books. Craig Mod asks the question about the iPad but I consider it equally valid for desktop and other mobile browsers:

Do we embrace the physicality of the device — a spineless page with a central axis of symmetry? Or do we embrace the device’s virtual physicality — an invisible spine defined by every edge of the device, signaling the potential of additional content just a swipe [or click or key press] away?

Craig Mod A Simpler Page

Most e-reader applications work on their own implementations of a given metaphor and you have to learn the way the different affordances work for the different combinations of devices and platforms.

The web reduces the number of affordances we need to provide by streamlining the ways we can provide them for both desktop and mobile devices.

APIs like Pointer Events allows both desktop and mobile devices to respond to native inputs using a single set of instructions…. It also helps smooth out inconsistencies between touch, mouse and other types of pointer devices as well

Because Safari, both desktop and mobile, consistently lags behind support for APIs, like Pointer events, so we may have to use polyfills like Points or Deeptissue to make sure the pointer will work regardless of what device we’re using. Read Apple’s criticism from Chris Love, Tim Kadlec, PPK and Jeremy Keith

Another affordance that is important is controlling the size of the font in our web readers. Regardless of what we read and where we read it we’ve always had the option of controlling the size of the text on screen to suit our needs either using the device’s native controls or with application-specific affordances that are easy to use and have clear clues about how to get to them and how to use them.

Even if we use the physical book as a metaphor. I read differently depending on how and where I’m reading. The iPad equivalents:

  • Close to face: Reading a novel on your stomach, lying in bed holding the iPad close to me or with the device laying down as I read. Decreasing the size of the font on a desktop or laptop to get a little more content per page
  • Medium distance from the face: Sitting on the couch or perhaps on the train on my way to work with the device at arm’s length or the device on my lap
  • Far from the face: The iPad propped up by the keyboard at an angle or the device sitting on my desk a little farther than it would when reading on the train

This is one aspect of how to address font sizing for web reading experiences. It does not address other typographical elements such as font selection (even when using the same measure condensed and expanded fonts will not look the same) line-height, kerning and others that are just as important. It also skips accessibility altogether which is also an important issue.

A related issue, for me, is what affordances are we providing developers when creating content using your tools. It may be the most wonderful tool but if I’m not comfortable using it I may not be using it for long.

The next question is whether the affordances we provide are meeting users needs and expectations.

If the users keep asking about aspects of your app then there’s something wrong with the app and not with the way your users look at it.

Pagination nightmares: Redefining how we navigate on the web

Providing a page animation to indicate page turning it’s not skeuomorphism when done right. But even when done right page turning animations become tiresome, particularly when we talk about long documents with hundreds of pages or when we try to navigate to specific portions of the content, especially towards the end of a book.

CSS Scroll Snap allows you to create your own pagination styles for the application we’re creating and also define the dimensions of the items you’re scrolling, in effect creating basic pagination.

Another possibility would be to use pagination

As with Pointer Events, lack of uniform support means that we need a CSS Scroll Snap polyfill for the time being.

The danger of uninformed criticism

From the Bubblin site:

Here’s a website (they call it a book) on Essentials of Image Optimizations by Addy Osmani.

Excellent write-up but it takes about ~90 (+/- 5) scroll actions using a mousewheel to reach the bottom of the essay while also maintaining the reading direction i.e. making sure I “saw” all of the content (am emulating experience of committing to and reading the book for real here) sequentially. The same website takes close to ~194 swipes to scroll down to the bottom on an iPhone X Safari and ~244 swipes on the Android Galaxy Express 3 while also ensuring that all of the content was seen by me. I don’t know about others, but I’d never scroll deeper than seven times for even the best blogpost of this decade on my mobile. A maximum of ten swipes if it’s really interesting content or an important one.

The criticism of Ady Osmani’s Essential Image Optimization book is poorly thought out and disrespectful. The book is laid out as a single page application but the critique makes the assumption that scrolling is the only way in which we navigate content. When I read the book I navigate to the table of contents swiping down twice or using keyboard navigation (built into the browser experience when HTML is coded properly) where I begin my exploration of the book which requires less scrolling. To return to the top I can either tap the top of the browser window or use command/control + up arrow to return me to the top of the document. So how is this inaccessible?

This is similar to the process of navigating a page using screen readers: you have the option of having the entire page read to you or navigating via landmarks.

But even if I had to navigate swiping down or using arrow keys or space bar or page up/down why would scrolling be a problem? The site provides a summary above the fold and the table of content below it so people will get the table of content only after they’ve decided they want to move forward with the site and find what they are looking for. We don’t always read technical books from beginning to end every time. These are not blog posts any more than the Bubblin “super books” are and they shouldn’t be treated as such just to make an argument.

Biggest question is why does he consider the book to be a blog post and talks so condescendingly about the content he didn’t author and that doesn’t use their technology? Would the authoring platform make a difference in the author’s evaluation?

Does it need pagination? I don’t think it does but perhaps it would have benefitted from being a multi-page application rather than a single page one or having links that would return you to the table of contents.

Where I have a strong issue is on the position the Bubblin author takes about people, who happen to work in technology, choosing to read a technical book as a paper book rather than using an online version. User preferences seem to not matter as much as the perceived shortcomings of the competing technology.

Readers of Sarah Drasner’s book on SVG Animations, however, we might be compelled to think, will prefer an e-book? Her fans are developers and tech evangelists who yap about digital coolness all day— about open web!— and it is clear that technology advances faster than a physical book could be doled out, so her book doesn’t even make sense in atomic form, but does PDF or DOC file with faux pagination or an ePub file or an HTML page with reflowable content fulfill the perception of books held by the developer community?

https://bubblin.io/concerns

I would say the answer is neither. I choose paperback technical books because they are easier to scan and the indexes are easier to navigate than a list of hyperlinks.

So now we “yap” about the open web? Does it mean we should dogfood your product to prove that the web is a good reading medium?

What most people are saying, and something the author of the article chooses to ignore is the fact that our relationship with printed books is much more than just a reading device. As Craig Mod points out in Future Reading:

Once bought by a reader, a book moves through a routine. It is read and underlined, dog-eared and scuffed and, most importantly, reread. To read a book once is to know it in passing. To read it over and over is to become confidants. The relationship between a reader and a book is measured not in hours or minutes but, ideally, in months and years.

Does the technology match the comfort level of a printed book? No, it doesn’t, at least not for me. Does the Bubblin technology change that? Most definitely not when I’m stuck on one font size throughout the whole reading experience and I get none of the affordances I get from a paper book (annotations, bookmarking and others). Even if I was able to change the size of the text from the application UI (which I’m not) it’s still a poor user experience when the size of the UI itself changes but not the size of the content inside, which is what I really wanted to change

And, before I’m reminded that this is an iPad first experience, this has far less to do with the device and platform we’re using to read digitally and more to do with the reader’s comfort and familiarity with the platform, whether online or offline.

Attention Economy

Bubblin presents a Chartbeat chart without any context to try and prove a point.

I’ve linked the chart as is from the Bubblin essay to give you an idea of what I mean:

Chart expressing percentage of users that scroll through content. Taken from Frequent Concerns

The chart on its own is missing something. There is no summary of the data or context for the results. What type of site is the data referring to? Are users accessing it in mobile or desktop devices? What countries are they accessing the content from?

There is a lot of context missing from the chart and I have to wonder if the full context of the chart would support the conclusion being drawn about it.

Likewise, when he uses Google’s search result page pagination as a model of what are the shortcomings of pagination on the web he forgets the primary difference between computer-generated pagination for the results of a search engine query with the pagination used for a book or other paginated long-form content.

How does this relate to long form content? Taken from https://bubblin.io/concerns

The search results are grouped in order of relevance where the results grow less relevant with each page you navigate to. How does this relate to paginated long-form content?

If the online content is properly laid out we have multiple ways of navigating it. We can do so sequentially or we can jump into a bookmark or we can jump to the page holding the content we just left (assuming we remember which one it is). But it’s not based on content relevance so the same rules that apply to Google’s search results don’t apply to content we create. If you author long-form content I’m guessing that all the content is equally relevant.

Why people prefer to read in print?

How do you extrapolate the small research studies Bubblin presents in their essay to a larger population?

In The Reading Brain in the Digital Age: The Science of Paper versus Screens, a much larger meta-study, the report indicates that:

Analyses revealed three significant moderators: (1) time frame: the paper-based reading advantage increased in time-constrained reading compared to self-paced reading; (2) text genre: the paper-based reading advantage was consistent across studies using informational texts, or a mix of informational and narrative texts, but not on those using only narrative texts; (3) publication year: the advantage of paper-based reading increased over the years.

So while researching further implications of the difference between reading online and reading physical books I came across this interview with Anne Mangen, chair of a research project about reading in an age of digital transformation.

How much time do we spend reading on screen and what are we reading?

The answer to this depends on how “reading” is defined. The research and statistics in this area vary depending on how the term is defined. Are we referring solely to the reading of textual material, or are we also including pictures, social media and hypertext containing links? If the latter definition is used, we can say that we are reading as never before and that the Internet has brought about an explosion of reading.[…]

The interviewer asks a followup question which, I think, is essential to this discussion:

When do we prefer a printed medium, such as a book?

There are many components, factors, and conditions that can come into play here, such as the reader, the material, the purpose, and the technology. Not only the reader’s proficiency, background, and expectations must be kept in mind, but also the type of material that is being referred to and the kind of screen that is being used. It is not a case of “one size fits all,” but patterns are beginning to emerge from empirical research into the subject. The length of the text seems to be the most critical factor. If the text is long, needs to be read carefully and perhaps involves making notes, then studies show that many people, including young people such as students, still often prefer a printed book, even if it is available as both an e-book and in electronic formats with options for making notes, enabling the user to search for and highlight the text digitally. This is not the case when it comes to shorter texts.

So is it really the technology that makes reading online less useful than reading a physical book or is it the user’s preferences themselves that make one more appealing over the other?

What I see as web reading experiences: Taking advantages of the online medium.

When I made a comment about needing to keep the browser’s built-in affordances I was thinking about font sizing and alternatives way to navigate their content. I was first thinking about Desktop browsers but then realized that these affordances are equally important for other form factors.

Epub books were designed with apps in mind as closed ecosystems where vendors have the last word into what makes it and what doesn’t make it into a product. It wasn’t until recently when frameworks like Readium and, I guess, Bubblin’s product, became open web toolkits that we could unpack an ebook and make the content on the web.

The web platform is as flexible and powerful as you need it to be; if it doesn’t provide some capabilities natively then you can create it or use a third party solution.

How can I annotate my content? Tools like Annotator allow you to annotate and share your content without having to “loan” your book.

Can I bookmark items inside the book? Annotations can serve the same purpose as bookmarks or, if you’re brave, you can code your own solution that bookmarks specific sections of your content.

Are we taking any built-in affordances away from our users? For each affordance that we take away, we should provide a polyfill or replacement for it.

I have seen good reading experiences on the web. They all move away from a straight book metaphor and move towards a more engaging multimedia user experience.

FF Meta Variable Font Demo by Jason Pamental was designed as a demonstration of variable fonts and how they work in CSS today. But I love the way that he plays with columns and the different text sizing and layout.

Scaling Everest (Washington Post) and Snow Fall (New York Times) present two different ways to present long-form content combining varying amounts of text, images, and video.

Art Space Tokyo and The Shape of Design present the content of printed books in a way more conducive to reading online.

Art Space Tokyo is not meant to be a replacement for the physical books but provides the same reference material in a way that is easier to engage with online. There is no physical equivalent to the Google Map, you’d have to list all the information for the locations which would make it longer and more tedious to read.

The Shape of Design, on the other hand, seeks to reproduce the text and the images of the book into an online environment. Each chapter in the physical book.

Whichever way you choose to read it’s beyond the book metaphor.

Bibliography and References