Using SVG as images

Most of the work I’ve done recently has been as inline SVG meaning that the SVG is inserted directly into the document; this has advantages and disadvantages. In this post we’ll discuss why we would use SVG as images, what are the disadvantages and disadvantages and a possible fallback using the picturefill polyfill.

SVG is a very powerful vector graphics format that can be used either as an inline element or as a format for images on web pages. Which one you use will depend on a few things:

  • What browsers do we need to support?
  • What are we using the graphics for?
  • What SVG features do we need for the individual graphics

For the following discussion, we’ll assume we need to support IE9 and newer plus all modern evergreen browsers; we won’t need animation baked into individual icons if we need to animate we’ll do so from CSS or using GSAP. We’ll use SVG to create a small set of social media icons to use on the page.

Advantages And Disadvantages Of SVG As An Image

Here are some advantages of working with SVG in images:

Smaller file size: SVG images are made of text describing the shape of the objects in the image so they will be consistently smaller than equivalent raster images.

Scale easier: Because they are vector graphics they scale up or down regardless of resolution. That means that you only have to load one image for all the resolutions and pixel densities you want to use on the page

Compresses better: SVG is text and, most of the time, the text will compress better than binary data

Not everything is rainbow and roses, there are a few disadvantages of working with SVG inside an image

Can not be formatted with CSS: Most of the time you can style SVG images with CSS either inside the element itself or through an external CSS. I can’t seem to do so with SVG images.

Does not work on older browsers: Not all browsers support SVG images, particularly IE9 and older. IE9 will support it but with a workaround.

Next, we’ll explore how to provide fallbacks for non-supported browsers and a polyfill for making the job easier.

Providing fallbacks

The simplest way for this to work is to use the picture element, part of the Responsive Images additions to the HTML specification

The example below shows one ideal way of providing a fallback for SVG images and providing a default image to render when neither source is supported. This is a first item matched is used algorithm, similar to what browsers do for the video and audio elements.

In this example, the browser tests support for SVG images and loads and render it if supporter; if not the browser checks if it can render WebP images and if it doesn’t then it falls back to the img element that should be rendered by all browsers. I’ve used a single src attribute for the image, we could also add srcset and sizes to the image to further enhance the responsiveness.

For larger line drawings or diagrams below the fold, we could also incorporate lazy loading (native and through polyfill).

<picture>
  <source   srcset="examples/images/large.svg"
            type="image/svg+xml">
  <source   srcset="examples/images/large.webp"
            type="image/webp">
  <img      src="examples/images/large.png"
            alt="Large Image Of Cats">
</picture>

Working with Picturefill

The problem is that older browsers are not likely to follow the ideal case. For browsers that don’t support the picture element, we’ll have to use a polyfill to make sure that the image will load regardless of the browser we’re using.

I’ve chosen to work with Picturefill polyfill for responsive images. It’s stable and works in the cases and for the browsers we wanted to tackle when defining the project.

To run the polyfill we first need to trick older versions of IE to accept the picture element before the polyfill has loaded and added the polyfill to the page using a script tag with the async attribute.

<script>
  // Picture element HTML5 shiv
  document.createElement( "picture" );
</script>
<script async src="picturefill.min.js"></script>

This makes all responsive images elements (picture) and attributes (srcset, and sizes) available to the page.

Now we move to the fallback solution for SVG images (finally!).

The final code looks pretty close to our ideal example, except for the IE-specific conditional comments that will only load the video element wrapper for IE9 (this addresses an issue with IE9 handling of source attributes inside a picture).

<picture>
  <!--[if IE 9]><video style="display: none;"><![endif]-->
  <source srcset="examples/images/large.svg" type="image/svg+xml">
  <source srcset="examples/images/large.webp" type="image/webp">
  <!--[if IE 9]></video><![endif]-->
  <img src="examples/images/large.png" alt="…">
</picture>

And that’s it. We have a way to display SVG images and provide multiple fallbacks for browsers that do not support them and a default image that will be supported everywhere.

Paginating or infinite scrolling web content

The web has always been a scrolling medium but there are reasons and motivations that will make people decide for one or the other. There is no perfect ‘one-size-fits-all’ solution and you will have to evaluate which solution works best for your project. In this post, we’ll discuss the advantages and disadvantages of pagination and scrolling and suggest one combined solution that may work well as a generic solution when working outside of frameworks.

In Pagination vs. Scrolling: The Great Website Debate ThriveHive presents the advantages and disadvantages of both pagination and scrolling for web sites. They come to the same conclusions most developers have: it depends.

But just like the Nielsen Norman Group reminds us that Infinite Scrolling Is Not for Every Website the same case can be made for pagination.

Scrolling

Scrolling, in this context, refers both to the regular scrolling of a web page and to infinite scrolling: a technique where the browser loads content as the user reaches the bottom of the screen so it appears as if the content will scroll continually as long as there is new content available to display.

Vue, React and Angular

All the frameworks I’ve reviewed have some sort of virtual/infinite scrolling tool available. These are the ones I found, I’m pretty sure there are more:

Web Platform

Surprisingly the web platform doesn’t provide built-in mechanisms for infinite scrolling and there several items necessary for successful infinite scrolling that are not part of the web platform yet.

One, incomplete, example is this pen from Werner Beroux that uses vanilla JavaScript to generate an infinite scrolling list.

At the 2018 Chrome Dev Summit, Gray Norton presented a new tool called the virtual scroller; a set of custom elements that will do the heavy work of creating virtual scrolling for you.

The Github repository has two different concepts for virtual scrolling. Check the project’s readme for more information about the branches and what they accomplish.

Pagination

Pagination takes a different approach. Rather than provide infinite scrolling, it breaks the content into “pages” and provides means to navigate the content.

According to the Interaction Design Institute:

Pagination is the process of splitting the contents of a website, or a section of contents from a website, into discrete pages. This user interface design pattern is what we designers use to save site visitors from being overwhelmed by a mass of data on one page – we take that ‘continental’ chunk and splinter it sensibly into ‘islands’, literally distinct pages which users will be able to devote their attention to without sighing in exasperation.

Depending on the type of content that you’re working with, pagination may or may not be the best solution. In 2013 Jakob Nielsen warned us that “listings might need pagination by default, but if users customize the display to View All list items, respect that preference.”

Working with long-form content works differently. We definitely want to paginate books or long essays… but we still need to be mindful of how we organize the pagination for this type of content.

Frameworks

Someone has thought about pagination for the frameworks I look at regularly so there shouldn’t be a problem with implementing the pattern in any of these frameworks.

Web Platform

A possibility is to use CSS scroll snap as a way to navigate between sections of content.

An example of scroll snap shows how it works with mouse events. A next step would be to convert the mouse events into pointer events to make sure we cover both desktop and mobile devices.

Something worth researching is whether it’s possible to copy the pagination tools from frameworks like Foundation or Bootstrap 4 without having to use the whole framework.

So which one do we use

As with many things dealing with the web, it depends. It depends on the type and quantity of material that we want to display and what our target devices are.

For book-like content it would be ideal to paginate the content at the chapter level and, inside the chapter, let the content scroll as necessary. This will give us the best of both worlds in situations where physical books would just add pages.

Native lazy loading in Chrome

Addy Osmani posted a note on twitter about native lazy loading support that will, hopefully, appear in Chrome 75 (canary builds as I write this). This is awesome and I hope that other browsers will implement this as a native feature but it introduces complexities that we need to evaluate before we implement this in development.

Before we start

Native lazy loading works for both images and iframes but it’s behind two flags. Surprisingly neither of them is Experimental Web Platform features.

Go into chrome://flags and enable the following flags if you want both elements to work with lazy loading:

  • enable-lazy-image-loading
  • enable-lazy-frame-loading

Restart your browser and you’re good to go.

The basics

  • loading="lazy" Lazy loads an offscreen image when the user scrolls near it
  • loading="eager" Loads an image right away instead of lazy-loading.
    This is the same as not using the attribute at all
  • loading="auto" lets the browser decides whether or not to lazy load the element
<img src="building1.jpg" loading="lazy" alt=".."/>
<img src="building1.jpg" loading="eager" alt=".."/>
<img src="building1.jpg" loading="auto" alt=".."/>

The lazy loading feature will also work with picture elements as long as you add the loading attribute to the fallback image element. If I understand it correctly the img element drives the display of any image inside the picture element so, if you add loading to it, whatever image loads will be lazy loaded.

<picture>
  <source media="(min-width: 40em)" srcset="big.jpg 1x, big-hd.jpg 2x">
  <source srcset="small.jpg 1x, small-hd.jpg 2x">
  <img src="fallback.jpg" loading="lazy">
</picture>

The same thing happens if the image has srcset attributes. As long as the image has the loading attribute set to lazy then the image will be lazy loaded.

<!-- Lazy-load an image that has srcset specified -->
<img src="small.jpg"
     srcset="large.jpg 1024w, medium.jpg 640w, small.jpg 320w"
     sizes="(min-width: 36em) 33.3vw, 100vw"
     alt="A rad wolf" loading="lazy">

The one example I haven’t seen elsewhere is for iframes. The example below shows a Youtube iframe embed set up for lazy loading. The same values apply here as they apply for images.

<iframe   loading="lazy"
          width="560" height="315"
          src="https://www.youtube.com/embed/yY1FSsUV-8c"
          frameborder="0"
          allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
          allowfullscreen></iframe>

Feature Detection and Lazy Loading Cross browser

I’m using the yall lazy loading library in this example and initialize it when we determine the browser doesn’t support native lazy loading. We need to load the library using something like the following command

<script defer src="scripts/yall.js"></script>

This will load the library every time we load the page. I am willing to take the extra 2Kb (and potentially the additional weight of an intersection observer polyfill) if it means that page will load faster because not all images are loaded when the page loads.

Once we’ve loaded yall.js, we check if the browser supports native lazyloading ('loading' in HTMLImageElement.prototype).

If it does, we store all the images we want to work with (the ones with a lazyload class) in a variable and then, for each of those images, we copy the data-src attribute to the source (src) attribute. This will lazy load the images.

If the browser doesn’t support native lazy loading then we load the Lazysizes script and initialize it. The script will take the data-src attribute and use it to lazy load the images instead.

(async () => {
  if ('loading' in HTMLImageElement.prototype) {
    const images = document.querySelectorAll('img.lazy');
    images.forEach(img => {
        img.src = img.dataset.src;
    });
  } else {
    // Make sure the library is already loaded
    // Initialize yall
    document.addEventListener("DOMContentLoaded", yall);
  }
})();

Using the script above we can use the following to load an image above the fold immediately.

<!-- Let's load this in-viewport image normally -->
<img src="hero.jpg" alt=""/>

And this is the code we use to lazy load an image. Note how it doesn’t have a src attribute because we’ll change it programmatically.

<!-- Let's lazy-load the rest of these images -->
<img  data-src="image1.jpg"
      loading="lazy" alt=""
      class="lazy"/>

I’ve got a working example in this pen.

References

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