Lazy Loading of Web Fonts

Lazy Loading of Web Fonts

Browser support for automatic font download is finally catching on. But except for Firefox all other browsers will hide either the complete page or those parts of the page that require one of the web fonts. Such behavior can be desired to enforce layout consistency but it can also be a drag if one wants to have the page display as quickly as possible and then switch to the downloaded fonts as soon as they have been downloaded

Furthermore, IE will download fonts even if the web font is not even needed for displaying the page because all usages of the web font are overridden by higher-priority fonts which are locally available. In such a case IE’s proactive behavior wastes bandwith and increases page load time.

To work around these browser behaviors so that web fonts are loaded lazyly and only when really needed, the following piece of javascript code can be used. This code will load a desired web font and inject it into the page. (If you just look for a way to load fonts at all with Javascript but are not interested in lazy loading then you might want to take a look at Google’s Webfont Loader)

  <script type="text/javascript">
    jQuery(function(){ 
      autoLoadWebFont( ".customSerif", "/path/to/webfonts", "DejaVu-Serif-webfont" ) 
    });
  </script>

  <style type="text/css">
    .customSerif{
      font-family: "DejaVu Serif", 
        "Galatia SIL", 
        "DejaVu-Serif-webfont";
    }
  </style>


  <p class=".customSerif">
    This paragraph uses DejaVu Serif or Galatia SIL if available on the Client,
    otherwise the web font is loaded from 
    /path/to/webfont/DejaVu-Serif-webfont.xxx
  </p>

The main function to be called requires three arguments – the name of an HTML element, the path for web fonts and the name of a web font. The javascript code will then try to detect which fonts are applied to the HTML element by the CSS cascade. If one of the fonts with higher priority than the web font is available then nothing needs to be done. If the fonts with higher priority are not locally available then the web font will be loaded. The script expects that the file name of the web font will exactly match the font name that is used in the CSS and Javascript code, for example “/path/to/webfonts/DejaVu-Serif-webfont.woff”. The font must be available in EOT, WOFF and TTF format. For generating webfont versions of an existing font you may want to take a look at the outstanding webfont generator by fontsquirrel.com (of course, you should make sure that you are allowed to use that font in the way you intend to…). The Javascript code injects a piece of CSS code to load the font and touches the given CSS element in order to force a repaint of the section. It is recommended to activate Webserver compression for the TTF fonts and if your EOT fonts are uncompressed then also for the EOT fonts.

The actual implementation uses jQuery and looks as follows:

  function autoLoadWebFont(htmlElement, fontPath, webFontName) {
    /* 
     * Look at the list of assigned fonts for the main content area 
     * by getting the computed CSS style.
     * If a local font higher priority is present 
     * then use that font and don't load the web font; 
     * Otherwise, load the web font by injecting a small bit of css.
     */
    var i,
        fontName,
        fontPath,
        fonts = $(htmlElement).css("font-family").split(",");

    if(fonts.length <= 1 ) {
      //if the computed style does not contain the list 
      //  of all fonts from the style sheet 
      //  then we just load the desired web font 
      //  without being able to check
      //  whether a "better" local font is available.
      //  This is mostly targeted at opera and partially 
      //  at safari which have hiccups with their computed style for fonts. 
      //
      //  This workaround is no problem since all browsers 
      //   other than IE will only *download* a
      //  font that is actually needed and will ignore 
      //  downloading those fonts 
      //  which are superseded by higher-priority font declarations.
      fonts = [webFontName];
    }

    for(i=0;i<fonts.length;i++) {
      //adjust font names by striping quotes and trimming whitespace
      fontName = fonts[i].replace(/^\s*["']*(.*?)["']*\s*$/g,"$1"); 

      if(isFontAvailable(fontName)) {
        //we found a local font that is has higher priority 
        //  than the desired web font. Stop here
        return;
      }

      if(fontName == webFontName) {
        fontPath = fontPath + fontName;

        /*Paul Irish's smiley method for font loading - 
          http://paulirish.com/2009/bulletproof-font-face-implementation-syntax/ */
        $("head").append('<style type="text/css">\n'+
          "@font-face{\n"+
            "font-family: '"+fontName+"';"+
            "src: url('"+fontPath+".eot');"+
            "src: local('☺'), "+
            "url('"+fontPath+".woff') format('woff'), "+
            "url('"+fontPath+".ttf') format('truetype');"+
          "}\n"+
        "</style>");

        //apply this font to the body, this helps IE8 
        //  to actually use the font after it was lazy-loaded
        setTimeout(function(){$(document.body).css("font-family",webFontName)},500);
        return;
      }

    }
  }


  /* Detect if a certain font is locally installed*/
  function isFontAvailable(fontName) {
    var sansSerifDiv, 
        testFontDiv, 
        fontAvailable,
        testCodePart1 = '<div style="font-family:',
        testCodePart2 = 'sans-serif;visibility:hidden;position:absolute;">mmmmlil</div>';
  


    sansSerifDiv = $(testCodePart1+testCodePart2).appendTo($bdy);
    testFontDiv  = $(testCodePart1 +"'"+fontName+"',"+testCodePart2).appendTo($bdy);

    fontAvailable = (sansSerifDiv.width()!=testFontDiv.width() || sansSerifDiv.height()!=testFontDiv.height());

    sansSerifDiv.remove();
    testFontDiv.remove();

    return fontAvailable;
  }