Performance check: CBC’s logo as pure CSS, Data URI and simple PNG on the scale

There is always room for improvement. Period.

Think about the 100 meter men’s sprint. I am  amazed how it continues to be possible for human beings to still become faster and improve their performance.

I’m not Usain Bolt – I can’t run 100 meters in 9.58 seconds but I might be able to run (mobile) websites under 10 seconds.

Today, I want to focus on a technique I first heard about at the Velocity Conference in 2011 in Santa Clara and how to compare it with other ways to serve images in HTML pages.

Data URI is based on base64 encoding and basically encodes data (e.g. images) into bites. It can be used to improve performance.

Data URI as “Performance Enhancer”

Instead of requesting for example a PNG image, you could encode it as base64 and serve it inline with your HTML code. That way, you reduce one HTTP request – right there – 200ms saved. Instead putting it inline, you could also put it encoded in an external stylesheet.

Watch out for caching limitations though. Data URIs can’t be cached as they don’t have a standalone cache policy, they are part of the file that includes them. So they can only piggy-bag on other cacheable assets, e.g CSS or HTML files.

As Nicholas explains, if you put data URI images inline with HTML, they can only be cached as part of the HTML cache control header. If the HTML is not cached, the entire content of the HTML markup, including the inline data URI will be re-downloaded every single time. That approach, if the image is big in size, can slow down and weight down your page. Hence, one option is to to put it in stylesheets because they can have an aggressive cache while the HTML page can have no cache or a very limited cache.

Limitations and Browser Support

Think about the browser audience of the site you want to leverage data URIs for. If you target modern browsers, including new mobile devices because that’s where you really want to focus on performance the most, you might be able to ignore the following limitations and accept the little restricted list (thanks to Fire) of supported browsers.

  • Firefox 2+
  • Opera 7.2+ – data URIs must not be longer than 4100 characters
  • Chrome (all versions)
  • Safari (all versions)
  • Internet Explorer 8+ (data URIs must be smaller than 32KB)

Motivation for Comparison

I’ve been reading a lot about web performance techniques and for some reason the data URI practice got stuck with me. I started off by creating the CBC gem (logo) in CSS to verify if CSS performs better than serving images. While I was playing around with that, I thought why not adding another dimension to the test and check the performance of the CBC logo as data URI. Voilà, I had my basic scenario for the test:

Check the performance of the CBC logo as

  1. An image in pure css
  2. A plain PNG image as background image
  3. A data URI (in CSS and inline with HTML)

Setting up the Test

The purpose of the test was to figure out what kind of presentation for the CBC gem would be the fastest and slimmest.

Prerequisites and Conditions

  • All HTML and CSS files were minified and use the same markup (despite the logo in pure CSS which needed to have a few more div classes to render the circles)
  • Each HTML version was tested with empty cache
  • Performance results were performed with WebPagetest (10 runs on an 3G simulated browser) to find the Median.

1. Logo in pure CSS (30px x 30px)

Pure CSS 30x30purecss Screen Shot 2013-05-01 at 5.40.46 PM
Description: Thankfully, the CBC gem consists of circles, 1/2 and 1/4 circles, those shapes can easily be created with CSS. I used this page to help me get started. Instead of setting up a fixed size and color, I decided to use SASS to help me be more flexible with my settings for the logo. The scss file lets me define color and size of the gem.
Note: Maybe the pure CSS logo has a bit of issues with some of the 1/4 circles but that’s probably due to some math formulas I didn’t do right in the SASS, I believe this can be ignored. Hence, This version cannot be used as the official CBC gem.

2. Plain PNG Image (30px x 30px)

PNG Image 30x30
png
Screen Shot 2013-05-01 at 5.40.59 PM
Description: Simple PNG file  included in the CSS as a background image. CSS included in main HTML.

3. Data URI in CSS (30px x 30px )

30x30 data URI

datauri
Screen Shot 2013-05-01 at 5.41.04 PM
Description: I used Nicholas’ tool to create my CSS files including data URI. However there are many tools to help you create your own data URI encoded files.

You can see from the browser screenshots above that all logos look pretty much the same to the user.

Test Results

Screen shot 2013-07-23 at 2.33.49 PM

The results show that the median load times serving the logo as pure CSS in comparison to the Data URI solution are being almost the same whereas the logo as a background image in CSS took the longest.

I looked at the base64 string and thought how big it would be if I had used a bigger image. So I googled and found the following “It’s not worth to use data URIs for large images. A large image has a very long data URI string (base64 encoded string) which can increase the size of CSS file.” (source). I decided to test it out myself. So, my next question was “How would the test above turn out if I used a bigger CBC gem logo”. I picked a width and height of 300px. While I was preparing the 300px x 300px pages, I also decided to create another version of the Data URI, not part of the CSS but inline within the HTML.

1. Logo in pure CSS (300px x 300px )

There was not much of a different in terms of markup and setup for the pure CSS and PNG in CSS version. I updated the SASS for the cbcgem.scss to accomodate a logo of 300px x 300px instead of 30px x 30px. The file size didn’t change much because it is all based on math calculations

2. Plain PNG Image (300px x 300px )

Instead of loading gem-30.png, I created a version gem-300.png and updated the CSS.

3a. Data URI in CSS (300px x 300px)

Screen shot 2013-05-01 at 9.09.15 PMI noticed that the size of the Data URI encoding as expected increased dramatically from a 30px x 30px encoded image to a 300px x 300px image (almost 10 times, see full view of screenshot on the left).

3b. Data URI inline within HTML (300px x 300px)

Screen shot 2013-05-01 at 9.31.58 PMInstead of pasting the long base64 string into the CSS, I added it as an img src to the HTML page (see full view of screenshot on the left)

I used WebPagetest again to run 10 tests to find the Median.

Screen shot 2013-07-23 at 2.33.57 PMThe links to the WebPagetest results can be found at the bottom of this post.

Observations & Take-Aways

  • Creating simple shapes in CSS (via SASS) is highly scalable because it doesn’t influence the size of the CSS file significantly. The size of the CSS file won’t change much if I choose to produce a 300px x 300px logo or a 10px x 10px logo. For all tests performed this solution seems to be the most efficient and fastest one.  
  • I didn’t  find the observation true that if the encoded image is bigger than 1-2kB it wouldn’t be worth using Data URI to improve performance. When looking at the last test round (300px x 300px), we can see in the results that the page with the encoded image is still faster than the page with a 300px x 300px PNG image.
  • It is interesting to note that the inline data URI version is faster than the data URI CSS version (and almost as fast as the pure CSS version).  Having to serve 2 HTTP requests with a total size of 4kB, the median load time was faster than the one serving the data URI via CSS.

Further Readings and References

WebPagetest Results

CBC Gem 30px x 30px

CBC Gem 300px x 300px

  15 comments for “Performance check: CBC’s logo as pure CSS, Data URI and simple PNG on the scale

  1. Blake
    May 2, 2013 at 7:04 pm

    I’m curious. Are the size values you are reporting the uncompressed or compressed size? I ask because in most cases the web server will gzip compress the output before sending it to the browser.

    You’d want to compare the compressed size, since that is the true object size being delivered over the network.

    • May 2, 2013 at 7:15 pm

      Blake, when you look at any of the WebPagetest results (e.g. http://www.webpagetest.org/result/130429_0J_WZM/8/details/ and here http://www.webpagetest.org/performance_optimization.php?test=130429_0J_WZM&run=8&cached=0) you will see they all seem to be set to gzip and compressed.

      • Blake
        May 2, 2013 at 7:26 pm

        Ahh yes..

        So.. if you are ever curious to know the file size (excluding HTTP headers), look at the content-length header. For example. The actual size of /m/0/tools/perf/datauri/css/cbcpng300.css is only 258 bytes (compressed). The headers comprise of 391 (uncompressed) bytes.

        Therefore the total data returned is 649 bytes (or 0.6 Kbytes as webpagetest indicates).

        It sucks that in this case the overhead of delivering this object via HTTP is bigger than the object itself.

        Great post 😀

        • May 2, 2013 at 7:40 pm

          Good to know.

          Thanks.

  2. May 6, 2013 at 1:22 pm

    Did you try SVG? Seems like that logo would be quite simple and probably quite a bit less code than using CSS. But also worth considering the memory footprint and rendering performance. Although if you’re gzipping, resources are transferred at compressed size, the browser still fills memory with the full uncompressed size. And doing the layout for the CSS version will place different demands on the browser vs the PNG or the SVG. It’s an almost endless topic for analysis and optimisation, and this post is a great place to start. Thanks for posting.

    • May 7, 2013 at 6:46 pm

      Thanks Andrew for your comment. No I haven’t tried SVG but it’s probably worth putting this into the equation as well. It’s true there are so many variables you can tweak and play with to evaluate performance of those different implementations. I will post more results once I run some further tests on a SVG version. The CSS version for sure has more markup and selectors yet being the fastest. The idea for the test originally was to prove CSS is faster than any PNG. The secrets and behaviors of browser engines…to be continued.

  3. Simple Test
    October 28, 2014 at 4:30 pm

    Re: The second test (with 300px images)

    Can you explain how the HTML+Inline Data URI page consumes only 4KB while the DataURI-in-CSS consumes 19KB? WebPageTest reports HTML pageload time as hardly unchanged (~1.1s) in BOTH cases, while there should be a transfer of ~19KB MORE to the inline HTML page size (due to the large DataURI-in-HTML).

    Re-doing the test (http://www.webpagetest.org/result/141028_1E_XPB/1/details/) clearly shows 18.0KB downloaded in the HTML page. So I’d call your results a fluke, maybe due to Akamai caching. Please double check and reply.

    • Barbara Bermes
      October 28, 2014 at 11:12 pm

      Good catch! Indeed, this seems strange, almost as if there was something wrong with WPT. I re-ran it myself and got similar results than you.
      I don’t think this was an Akamai thing. Nevertheless, the load time is still pretty similar to the pure CSS results, and so yes, the file size should be updated in my table. The load time probably could be tweaked for repeating visits if there is an aggressive HTML caching policy. Thanks

      • Simple Test
        October 29, 2014 at 1:18 pm

        Rerun the tests. DataURI-in-HTML is *slower* (http://www.webpagetest.org/result/141029_XC_NC8/) than DataURI-in-CSS (http://www.webpagetest.org/result/141029_YF_NCQ/) while transferring the same number of bytes (~19KB). I’d say these results are flukes and mean nothing.

        As for the separate PNG image, that should be the *fastest* of the lot (no base64 decoding overhead, and the PNG can be fetched in parallel with the CSS). However, your PNG file is HUGE—27460 bytes! Just think about it—you convert the PNG to base64 (with the attendant 37% *increase* in size), then you transmit as text, gzipped over HTTP. How does it become 19KB!!!??!! IF anything, DataURI encoding bloat should *increase* the size of a compressed binary file like PNG! Seems like your initial PNG is (very) poorly compressed. Considering that PNG compression is lossless, there is simply no reason to serve up shoddily compressed files, eating up users’ bandwidth as well as your own. This, by itself, renders the test flawed. To be sure, I downloaded http://www.cbc.ca///m/0/tools/perf/datauri/img/gem-300.png and unleashed Trimage on it—and the file size reduced from 26KB to 19KB! Your DataURI-conversion tools are also apparently optimising your PNGs behind your back. My recommendation is to properly compress your PNG (a fully loaded Trimage is all you need), and THEN re-run all tests. I’d like to see your (new) results!

        • Barbara Bermes
          October 31, 2014 at 12:11 pm

          Here is a new set of tests. I used the 30×30 image this time.

          WPT Test scenario: 9 runs with repeat view, results show median of those 9 runs, pages hosted on Github, and PNG image optimized with smush.it.

          Result: From a start to render and speed index perspective, data URI external and inline are both faster than PNG. Data URI external is the fastest.

          http://www.webpagetest.org/video/compare.php?tests=141031_PC_JTT,141031_KR_KP4,141031_6W_JH1

          I’m curious to see your results and how you can optimize the PNG version to be faster. Please share.

          • Simple Test
            November 2, 2014 at 7:40 pm

            There was no problem with the 30×30 tests. For images ~1KB, the 200-or-so-byte overhead added by an additional request for image, as well as the greater delay associated with it, tilts the results heavily in favour of Data URI. Using smush.it on small images doesn’t result in much gain.

            This is clearly borne out by your results for png-css (CSS in 69ms, then PNG in 78ms) vs datauri-external (CSS+DataURIPNG both in 74ms for the same bytes transferred—implying that the additional request time for separate PNG is what bloats that result).

            However, in the (original) 300×300 test, the additional size (~19KB) and transfer time for the image skews in favour of binary image (that doesn’t have to be base64 decoded). The larger the image, the more insignificant the additional request delay becomes, and the datauri-decoding becomes more prominent. For my tests, I used Trimage (http://trimage.org/) to compress the image, as I found it sometimes compresses better than smush.it. When using the fully compressed PNG, the data transferred in all three cases were about the same size—19KB. This was the main problem with your 300×300 test (Inline HTML reported only 4KB, while PNG was hobbled by unoptimised size). In this case, the request times became almost insignificant and the results became too close to call for the first load. For large images, the request overhead for separate image is rendered insignificant by the huge data-transfer that is required.

            The reason optimised binary PNG comes out fast in single runs is that the page rendering starts before the image is downloaded. However, including it as DataURI-in-CSS stops page rendering till the now-large CSS is fully downloaded. So large binary PNG is faster than DataURI-in-CSS. DataURI-in-HTML has the least delay (apart from base64 overhead), but does not start render until the entire image is parsed, hence is slower. However, second- and subsequent runs on, DataURI-in-HTML is the worst performer as it completely bypasses all image and CSS caching, unless one is running an HTML-page cache (highly unlikely). So, for first runs with large, optimised, image: (1) Binary PNG, (2) DataURI-in-CSS, and (3) DataURI-in-HTML. For repeat runs, DataURI-in-HTML should come last.

            This is also borne out by Time-to-Start-Render in your new tests (CSS and PNG are almost equally fast, HTML is slow at first, but faster to finish than PNG). Even with such a small image, Times-to-Start-Render are very close for PNG and CSS but delayed for HTML, while in Speed index both are again close (PNG is marred by the extra request delay that is significant compared to the very small transfer time needed for the image) but HTML is significantly slower. Note that these (your new results) are in contrast to earlier results for 300x300px images, where DataURI-in-HTML seemed to shine.

  4. Simple Test
    October 30, 2014 at 6:10 pm

    Well, did you run the tests again? Please reply!

Comments are closed.