<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ArtzStudio</title>
	<atom:link href="http://www.artzstudio.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.artzstudio.com</link>
	<description>Dave Artz and his discoveries in web design and development.</description>
	<lastBuildDate>Mon, 05 Mar 2012 14:47:42 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
<xhtml:meta xmlns:xhtml="http://www.w3.org/1999/xhtml" name="robots" content="noindex" />
		<item>
		<title>Web Font Performance: Weighing @font-face Options and Alternatives</title>
		<link>http://www.artzstudio.com/2012/02/web-font-performance-weighing-fontface-options-and-alternatives/</link>
		<comments>http://www.artzstudio.com/2012/02/web-font-performance-weighing-fontface-options-and-alternatives/#comments</comments>
		<pubDate>Tue, 28 Feb 2012 03:38:40 +0000</pubDate>
		<dc:creator>ArtzStudio</dc:creator>
				<category><![CDATA[Performance]]></category>

		<guid isPermaLink="false">http://www.artzstudio.com/?p=156</guid>
		<description><![CDATA[Web fonts are a key ingredient in today's website designs; at my employer (AOL) it is a given redesigns will feature downloadable fonts. The days of maintaining a sprite full of graphic text headlines are behind us. We&#8217;ve moved on&#8212;but what approach yields the best performance? The goal of this article is to look at [...]]]></description>
			<content:encoded><![CDATA[<style>

pre,
code{
    font-family:"Inconsolata", "Monaco", "Consolas", "Courier New", Courier, monospace;
    font-size: 12px;
background-color: transparent;
}
.font-subset-data td, .font-subset-data > tbody th {
    text-align: right;
}
.font-subset-data td {
    padding: 3px 6px;
}
.font-bars {
    width: 300px;
}
.glyphs {
    color: green;
}
.bytes {
    color: blue;
}
.font-glyph-bar {
    background-color: green;
    height: 8px;
    width: 100%;
}
.font-byte-bar {
    background-color: blue;
    height: 8px;
    width: 100%;
}
.font-benchmark-data {
    border-spacing: 6px;
    margin: 20px 0;
}
.font-benchmark-data a {
    color: #000;
}
.font-benchmark-data caption {
    font-size: 18px;
    font-weight: bold;
}
.font-benchmark-data td {
    padding: 3px 6px;
    text-align: center;
}
.font-benchmark-data th {
    text-align: right;
    padding: 3px 6px;
}
.font-browser-benchmarks img {
    width: 600px;
}
.tabs h4 {
    border: 1px solid #000;
    display: inline-block;
    padding: 3px 6px;
    cursor: pointer;
}
.tabs h4.active-tab {
    color: white;
    background-color: #000;
}
.tabs img {
    display: none;
}
.tabs img.active-panel {
    display: block;
}
.max {
    background-color: #F79383;
}
.min {
    background-color: #8FF783;
}
#flowplayer {
    width: 600px;
    height: 358px;
}
</style>

<p>Web fonts are a key ingredient in today's website designs; at
my employer (AOL) it is a given redesigns will feature downloadable fonts. The days
of maintaining a sprite full of graphic text headlines are behind us.
We&rsquo;ve moved on&mdash;but what approach yields the best
performance?</p>
<p>The goal of this article is to look at the various web font implementation options
available, benchmark their performance, and arm you with some useful tips
in squeezing the most bang for your font byte. I will even throw in a new
font loader as a special bonus!</p>

<h2>Font Hosting Services vs. Rolling Your Own</h2>

<p>There are two approaches you can take to get licensed, downloadable
fonts on to your web pages: font hosting services and do-it-yourself
(DIY).</p>

<p><b>Font Hosting Services</b> like Typekit, Fonts.com, Fontdeck,
etc., provide an easy interface for designers to manage fonts
purchased, and generate a link to a dynamic CSS or JavaScript file that
serves up the font. Google even provides this service <em>for free</em>.
Typekit is the only service to provide additional font hinting to ensure
fonts occupy the same pixels across browsers.</p>

<p><b>The DIY approach</b> involves purchasing a font licensed for web
use, and (optionally) using a tool like FontSquirrel's generator to
optimize its file size. Then, a <a href="http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax/">cross-browser
implementation</a> of the standard @font-face CSS is used to enable the
font(s). This approach ultimately provides the best performance.</p>

<p>Both approaches make use of the standard @font-face CSS3 declaration,
even when injected via JavaScript. JS font loaders like the one used by
Google and Typekit (i.e. <a href="https://developers.google.com/webfonts/docs/webfont_loader">WebFont loader</a>) provide CSS classes and callbacks to help manage the "FOUT" that may occur, or response timeouts when downloading the font.</p>

<h2>What the FOUT?</h2>

<p>FOUT, or &ldquo;Flash of Unstyled Text,&rdquo; was coined by <a href="http://paulirish.com/2009/fighting-the-font-face-fout/">Paul Irish</a>
and is the brief display of the fallback font before the web
font is downloaded and rendered. This can be a jarring user experience,
especially if the font style is significantly different. </p>

<!--<h3><a
        href="http://www.artzstudio.com/files/font-performance/fout-demo.html">Flash
        of Unstyled Text Demo</a></h3>

<p><img id="fout-before" src="fout-before.jpg" style="display: none;" height="253"> <img id="fout-after" src="fout-after.jpg" height="253"></p>

<script>
(function(){
    var d = document,
        foutBefore = d.getElementById("fout-before"),
        foutAfter = d.getElementById("fout-after");

    if ( foutAfter.style.display === "none" ) {
        foutBefore.style.display = "none";
        foutAfter.style.display = "block";
    } else {
        foutBefore.style.display = "block";
        foutAfter.style.display = "none";
    }
    setTimeout( arguments.callee, 3000 );
})();
</script>
<h3><a
        href="http://www.artzstudio.com/files/font-performance/fout-demo.html">Flash
        of Unstyled Text Demo</a></h3>-->
<p>FOUT of some form exists in all versions of Internet Explorer and
Firefox 3.6 and lower. Check out the video of
<a href="http://www.artzstudio.com/files/font-performance/fout-demo.html">my demo</a>
below (preferably in full screen mode)
at the 1.6 second mark to see it in action:
</p>


<div id="flowplayer"></div>
<script src="http://www.artzstudio.com/files/font-performance/flowplayer/flowplayer-3.2.6.min.js"></script>
<script>
flowplayer("flowplayer", {
        src: "http://www.artzstudio.com/files/font-performance/flowplayer/flowplayer-3.2.7.swf",
        cachebusting: false,
        version: [9, 115]
    }, {
        clip:  {
            scaling: "fit"
        },
        playlist: [
            { url: "http://www.artzstudio.com/files/font-performance/fout-demo.png" },
            { url: 'http://www.artzstudio.com/files/font-performance/fout-demo.mp4', autoPlay: false, autoBuffering: false }
        ],
        plugins: {
            controls: {
                volume: false,
                mute: false,
                stop: true,
                tooltips: {
                    buttons: true,
                    fullscreen: 'Enter fullscreen mode'
                }
            }
        },
        canvas: {
            backgroundColor: '#000000',
            backgroundGradient: 'none'
        }
    });
</script>
<p><i>You'll notice in Internet Explorer 9, <a href="http://www.webpagetest.org/video/compare.php?tests=120108_PQ_2SH9D-r:1-c:0">the
content is blocked until the image has downloaded</a>. Your guess is as good as
mine.</i></p>

<p>Here are my recommendations for avoiding the FOUT:</p>

<ul>
  <li>Host the fonts on a <a href="http://en.wikipedia.org/wiki/Content_delivery_network">CDN</a></li>
  <li><a href="http://www.phpied.com/gzip-your-font-face-files/">GZIP
all font files</a> except .woff (already compressed)</li>
  <li>Cache all font files for 30+ days by adding a <a href="http://www.askapache.com/htaccess/apache-speed-cache-control.html">future expires
      cache header</a> </li>
  <li>Remove excess glyphs (characters) from the font files</li>
  <li>Ensure @font-face is the first rule of the first stylesheet on the
page (IE)</li>
  <li>Still have a FOUT? Read on, a JavaScript font loader may be in
order.</li>
</ul>

<h2>Removing Excess Font Glyphs</h2>

<p>Font Squirrel has an <a href="http://www.fontsquirrel.com/fontface/generator">awesome tool</a> that lets you take a desktop font file
and generate its web counterparts. It also allows you to take a subset of
the font, significantly reducing file size.</p>


<p>To show just how significant, I added Open Sans and tried all three
settings:</p>

<table class="font-subset-data">
  <thead> <tr>
    <th>


</th>
<th>Glyphs</th>
<!--<th>EOT</th>--> <th>Size</th>
<th class="font-bars">


</th>
  </tr>
  </thead> <tbody>
    <tr>
      <th>Basic</th>
<td class="glyphs">940</td>
<!--<td>113662</td>--> <td class="bytes">66.9 KB</td>
<td><div class="font-glyph-bar"></div><div class="font-byte-bar"></div>


</td>
    </tr>
    <tr>
      <th>Optimal</th>
      <td class="glyphs">239</td>
<!--<td>32574</td>--> <td class="bytes">20.9 KB</td>
<td><div style="width: 25.4%;" class="font-glyph-bar"></div><div style="width: 31.2%;" class="font-byte-bar"></div>


</td>
    </tr>
<!--    <tr>
      <td>Kit</td>
<td>20878</td> <td>14.0 KB</td>
      <td>133</td>
    </tr>-->
    <tr>
      <th>Expert</th>
      <td class="glyphs">119</td>
<!--<td>19602</td>--> <td class="bytes">13 KB</td>
<td><div style="width: 12.7%;" class="font-glyph-bar"></div><div style="width: 19.4%;" class="font-byte-bar"></div>


</td>
    </tr>
  </tbody>
</table>

<p>From the table above, it should be obvious that the byte size is
directly correlated to the # of glyphs (characters) in the font file.

</p><p><em>I suggest you follow along with me at
    <a href="//www.fontsquirrel.com/generator">www.fontsquirrel.com/generator</a>!</em></p>

<p>The <b>Basic</b> setting leaves the characters untouched. <b>Optimal</b>
reduces the characters to around 256, the Mac Roman character set. We are able
to see the greatest savings by selecting <b>Expert</b> mode and only including
the <i>Basic Latin</i> set, then manually adding in characters we need.</p>

<p id="recommended-expert-settings">Here are my recommended <b>Expert</b>
    FontSquirrel settings (<a href="http://www.artzstudio.com/files/font-performance/fontsquirrel-generator-settings.png">screenshot</a>):

</p><ul>
    <li>Under <i>Rendering</i>, uncheck <i>Fix Vertical Metrics</i>
    </li><li>Under <i>Subsetting</i>, check <i>Custom Subsetting...</i>
    </li><li>Under <i>Unicode Tables</i>, check <b>only</b> <i>Basic Latin</i><br><i><b>Note:</b> This assumes the fonts will only use English characters; for other languages add the characters you need.</i></li> 
</li><li>If you are typography nerd, copy and paste &#8216; &#8217; &#8220;
&#8221; into the <i>Single Characters</i> field</li>
    <li>Verify your <i>Subset Preview</i>; adjust if needed<br>
<img src="http://www.artzstudio.com/files/font-performance/fontsquirrel-latin-subset.png">
    </li><li>Under <i>Advanced Options</i>, give your font a suffix based on the
    subset (i.e. <i>latin</i>)
</li></ul>
<h2>JavaScript Font Loaders</h2>
<p>Typekit and Google joined forces to create an open source <a href="https://developers.google.com/webfonts/docs/webfont_loader">WebFont Loader</a>
that provides CSS and JavaScript hooks indicating a font's status as it
downloads. This can be useful in <a
    href="http://24ways.org/2010/using-the-webfont-loader-to-make-browsers-behave-the-same">normalizing
    the FOUT across browsers</a>
by hiding the text and adjusting CSS properties so that both fonts occupy the same width.</p>
<p>The three states it tracks are loading, active, and inactive (timeout).
Corresponding CSS classes (<code>wf-loading</code>, <code>wf-active</code>, and <code>wf-inactive</code>)
can be used to control the FOUT by first hiding headings
and then showing them when once downloaded:</p>
<pre class="style"><code>h1 {
    visibility: hidden;
}
.wf-active h1 {
    visibility: visible;
}</code></pre>

<p>JavaScript hooks for these same events are also available via callbacks in the
configuration object:
<!--<pre><code>&lt;script type="text/javascript"&gt;
try {
    Typekit.load({
        loading: function() {
            // JavaScript to execute when fonts start loading
        },
        active: function() {
            // JavaScript to execute when fonts become active
        },
        inactive: function() {
            // JavaScript to execute when fonts become inactive
        }
    })
} catch(e) {}
&lt;/script&gt;
</code></pre>-->
<pre class="script"><code>WebFontConfig = {
    google: {
        families: [ 'Tangerine', 'Cantarell' ] // Google example
    },
    typekit: {
        id: 'myKitId' // Typekit example
    },
    loading: function() {
        // JavaScript to execute when fonts start loading
    },
    active: function() {
        // JavaScript to execute when fonts become active
    },
    inactive: function() {
        // JavaScript to execute when fonts become inactive (time out)
    }
};</code></pre>

<p>The WebFont loader also includes callbacks for <code>fontactive</code>, <code>fontloading</code>, and <code>fontinactive</code>
that is fired each time a font updates, giving you control at a font level. For more information, check out the <a href="https://developers.google.com/webfonts/docs/webfont_loader">WebFont Loader documentation</a>.</p>
<h3>Introducing Boot.getFont, a fast and tiny Web Font Loader</h3>
<p>I haven't seen one out there <i>(leave a comment if I missed it)</i> so I wrote a little
font loader that provides the same hooks for loading fonts called
<code>getFont</code> as part of my <a
    href="https://github.com/artzstudio/Boot">Boot library</a>.</p>
<p>It weighs in at 1.4 K after GZIP (vs. 6.4 KB Google, 8.3 KB  Typekit) and
easily fits into your existing library. Simply change the <code>"Boot"</code>
string at the end of the file to update the namespace (i.e.,
<code>jQuery</code>).</p>
<p>Fonts are loaded via a JavaScript function, and a callback can be supplied
that executes once the font has finished rendering.
<pre class="script"><code>Boot.getFont("opensans", function(){
    // JavaScript to execute when font is active.
});</code></pre>
<p><code>Boot.getFont</code> provides similar CSS classes to the WebFont Loader but at a
font level, affording precise control:</p>
<pre class="style"><code>.wf-opensans-loading {
    /* Styles to apply while font is loading. */
}
.wf-opensans-active {
    /* Styles to apply when font is active. */
}
.wf-opensans-inactive {
    /* Styles to apply if font times out. */
}</code></pre>

<p>You can easily configure it to grab fonts based on your directory structure
by loading a configuration object:</p>

<pre class="script"><code>// Global
Boot.getFont.option({
    path: "/fonts/{f}/{f}-webfont" // {f} is replaced with the font name
});

// Font-specific
Boot.getFont({ path: "http://mycdn.com/fonts/{f}/{f}-wf" }, "futura" );</code></pre>
<p>I haven&rsquo;t had time to document all the goods, but the library is
available here if you are interested.</p>
<ul><li>Development: <a
        href="https://raw.github.com/artzstudio/Boot/master/src/standalone/boot.getfont.js">boot.getfont.js</a></li>
    <li>Production: <a
        href="https://raw.github.com/artzstudio/Boot/master/src/standalone/boot.getfont.min.js">boot.getfont.min.js</a></li>
</ul>

<h2>Gentlefonts, start your engines!</h2>

<p>Now that we are armed with the knowledge needed to ensure fast-loading
fonts, let us take a look at the performance of the implementation options.</p>

<p>I set up the following test pages, loading the same web font (Open Sans),
spanning DIY and various hosting options at Typekit and Google:</p>

<ul>
    <li><a
        href="http://www.artzstudio.com/files/font-performance/benchmark-system.html">System</a>:
    Our control test; this page does not load any fonts and uses Arial.
    <li><a
        href="http://www.artzstudio.com/files/font-performance/benchmark-fontsquirrel-optimal.html">FontSquirrel
        Optimal</a>: FontSquirrel generator&rsquo;s
    recommended &lsquo;Optimal&rsquo; setting and <a href="http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax/">FontSpring&rsquo;s
        cross-browser @fontface declaration</a>.  Fonts hosted on the
    same server as the web page like most small websites.
    <li><a href="http://www.artzstudio.com/files/font-performance/benchmark-fontsquirrel-latin.html">FontSquirrel Expert</a>:
    Used <a href="#recommended-expert-settings">recommended
        tips above</a> to trim font file size using the FontSquirrel Generator,
    I replaced the &lsquo;Optimal&rsquo; font kit in the above test with a
    minimal &lsquo;Basic Latin&rsquo; character set.
    <li><a href="http://www.artzstudio.com/files/font-performance/benchmark-fontsquirrel-latin-cdn.html">FontSquirrel Expert
        (CDN)</a>: Same as the above test, however fonts are hosted from a CDN on
    a different domain.
    <li><a href="http://www.artzstudio.com/files/font-performance/benchmark-getfont.html">Boot.getFont</a>: This test updated
    the &lsquo;FontSquirrel Expert&rsquo; test to use my Boot.getFont
    JavaScript library.
    <li><a href="http://www.artzstudio.com/files/font-performance/benchmark-getfont-cdn.html">Boot.getFont (CDN)</a>: Same as
    Boot.getFont test, except font files are hosted from a CDN on a different
    domain.
    <li><a href="http://www.artzstudio.com/files/font-performance/benchmark-google-standard.html">Google Web Fonts Standard</a>:
    I chose Google to represent a free font hosting service, and since this
    <em>is</em> a speed test, and Google is all about speed, I figured they should
    be in the race. Google provides 3 implementation options, this being the
    default&mdash;a <code>&lt;link&gt;</code> element pointing to a dynamic stylesheet that loads the font(s).
    <i>Note: I left out the &lsquo;Import&rsquo; option as results were nearly
        identical to &lsquo;Standard&rsquo; option.</i>
    <li><a href="http://www.artzstudio.com/files/font-performance/benchmark-google-import.html">Google Web Fonts JavaScript</a>:
    This option includes the WebFont loader discussed above to load the fonts,
    hosted from Google&rsquo;s servers.
    <li><a href="http://www.artzstudio.com/files/font-performance/benchmark-typekit.html">Typekit</a>: Here, I created a kit
    at Typekit and used the options that provided the smallest font file.
</ul>
<p>I used <a href="http://webpagetest.org">webpagetest.org</a> and loaded each test page 10 times in Chrome, Firefox
7, IE7, IE8, and IE9 over a 1.5 mbps DSL connection. We are comparing implementation, so I took the fastest test
to weed out network latency issues and other causes of variance in the data.</p>

<p>Here is how they stack up, ranked by the fastest time (ms) across browsers:</p>
<table class="font-benchmark-data">
  <caption>Fastest Load Times (ms) by Implementation and Browser</caption> <tbody>
    <tr>
      <th></th>
      <th>IE9</th>
      <th>IE8</th>
      <th>IE7</th>
      <th>Firefox</th>
      <th>Chrome</th>
      <th>Fastest</th>
    </tr>
    <tr>
      <th>System</th>
      <td><a href="http://www.webpagetest.org/result/111231_PV_2PMPE/">373</a></td>
      <td class="min"><a href="http://www.webpagetest.org/result/111231_8C_2PMP9/">358</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_81_2PMPF/">370</a></td>
      <td class="max"><a href="http://www.webpagetest.org/result/111231_AE_2PMPH/">506</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_E5_2PMPG/">398</a></td>
      <td><b>358</b></td>
    </tr>
    <tr>
      <th>Boot.getFont (CDN)</th>
      <td><a href="http://www.webpagetest.org/result/111231_2H_2PTMX/">692</a></td>
      <td class="max"><a href="http://www.webpagetest.org/result/111231_VG_2PTMV/">697</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_EJ_2PTMT/">696</a></td>
      <td class="min"><a href="http://www.webpagetest.org/result/111231_3F_2PTMZ/">652</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_FJ_2PTN1/">680</a></td>
      <td><b>652</b></td>
    </tr>
    <tr>
      <th>FontSquirrel Expert (CDN)</th>
      <td class="max"><a href="http://www.webpagetest.org/result/111231_BX_2PND5/">710</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_NV_2PND4/">697</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_W1_2PND3/">681</a></td>
      <td class="min"><a href="http://www.webpagetest.org/result/111231_0D_2PNDA/">667</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_XZ_2PND8/">681</a></td>
      <td><b>667</b></td>
    </tr>
    <tr>
      <th>Boot.getFont</th>
      <td class="max"><a href="http://www.webpagetest.org/result/111230_2X_2PME8/">812</a></td>
      <td><a href="http://www.webpagetest.org/result/111230_9W_2PMCN/">698</a></td>
      <td><a href="http://www.webpagetest.org/result/111230_XF_2PMF0/">798</a></td>
      <td class="min"><a href="http://www.webpagetest.org/result/111230_EP_2PMEC/">693</a></td>
      <td><a href="http://www.webpagetest.org/result/111230_DM_2PMEF/">704</a></td>
      <td><b>693</b></td>
    </tr>
    <tr>
      <th>FontSquirrel Expert</th>
      <td class="max"><a href="http://www.webpagetest.org/result/111231_HV_2PMQ1/">822</a></td>
      <td class="min"><a href="http://www.webpagetest.org/result/111231_17_2PMPY/">704</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_RX_2PMQ8/">784</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_ZX_2PMQ4/">802</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_1N_2PMQ3/">792</a></td>
      <td><b>704</b></td>
    </tr>
<!--<tr>
      <th>Typekit (Blocked Logo)</th>
      <td class="min"><a href="http://www.webpagetest.org/result/111231_0A_2PV67/">794</a></td>
      <td class="max"><a href="http://www.webpagetest.org/result/111231_8G_2PV65/">996</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_JA_2PV64/">859</a></td>
      <td></td>
      <td></td>
      <td><b>794</b></td>
    </tr>-->
    <tr>
      <th>Typekit</th>
      <td><a href="http://www.webpagetest.org/result/111231_4S_2PNEH/">798</a></td>
      <td class="max"><a href="http://www.webpagetest.org/result/111231_QZ_2PNEG/">999</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_64_2PNEF/">959</a></td>
      <td class="min"><a href="http://www.webpagetest.org/result/111231_2K_2PNEM/">795</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_E2_2PNEK/">815</a></td>
      <td><b>795</b></td>
    </tr>
    <tr>
      <th>FontSquirrel Optimal</th>
      <td class="max"><a href="http://www.webpagetest.org/result/111231_B7_2PPBP/">997</a></td>
      <td class="min"><a href="http://www.webpagetest.org/result/111231_Z9_2PNC4/">800</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_D1_2PNC3/">803</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_MF_2PNC7/">933</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_CD_2PNC6/">925</a></td>
      <td><b>800</b></td>
    </tr>
    <tr>
      <th>Google Web Fonts JavaScript</th>
      <td><a href="http://www.webpagetest.org/result/111231_TH_2PNDS/">1096</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_RD_2PNDQ/">1097</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_2J_2PNDP/">1126</a></td>
      <td class="max"><a href="http://www.webpagetest.org/result/111231_13_2PNDW/">1254</a></td>
      <td class="min"><a href="http://www.webpagetest.org/result/111231_FW_2PNDV/">801</a></td>
      <td><b>801</b></td>
    </tr>
    <tr>
      <th>Google Web Fonts Standard</th>
      <td><a href="http://www.webpagetest.org/result/111231_CJ_2PNE6/">896</a></td>
      <td class="min"><a href="http://www.webpagetest.org/result/111231_RN_2PNE5/">850</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_KJ_2PNE4/">870</a></td>
      <td class="max"><a href="http://www.webpagetest.org/result/111231_Q3_2PNE9/">1003</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_QR_2PNE7/">899</a></td>
      <td><b>850</b></td>
    </tr>
<!--    <tr>
      <th>Google Web Fonts Import</th>
      <td><a href="http://www.webpagetest.org/result/111231_YQ_2PNE0/">897</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_4A_2PNDZ/">896</a></td>
      <td class="min"><a href="http://www.webpagetest.org/result/111231_HA_2PNDY/">891</a></td>
      <td class="max"><a href="http://www.webpagetest.org/result/111231_6B_2PNE2/">1039</a></td>
      <td><a href="http://www.webpagetest.org/result/111231_HQ_2PNE1/">903</a></td>
      <td><b>891</b></td>
  </tr>-->
<!--<tr>
  <th>Fastest</th>
  <td><b>373</b></td>
  <td class="min"><b>358</b></td>
  <td><b>370</b></td>
  <td class="max"><b>506</b></td>
  <td><b>398</b></td>
  <td><b>358</b></td>
 </tr>-->
  </tbody>
</table>
<p>Take some time to digest the data. To better compare implementations across browsers, check out these charts:</p>

<!--
 <table class="font-benchmark-data">
 <tr>
  <th></th>
  <th>IE9</th>
  <th>IE8</th>
  <th>IE7</th>
  <th>Firefox</th>
  <th>Chrome</th>
  <th>Grand Total</th>
 </tr>
 <tr>
  <th>System</th>
  <td>445</td>
  <td class="min">377</td>
  <td>385</td>
  <td class="max">542</td>
  <td>412</td>
  <td>432</td>
 </tr>
 <tr>
  <th>Boot.getFont (CDN)</th>
  <td class="max">719</td>
  <td>715</td>
  <td>711</td>
  <td class="min">693</td>
  <td>703</td>
  <td>708</td>
 </tr>
 <tr>
  <th>FontSquirrel Expert (CDN)</th>
  <td>765</td>
  <td class="min">720</td>
  <td>732</td>
  <td>721</td>
  <td class="max">828</td>
  <td>753</td>
 </tr>
 <tr>
  <th>FontSquirrel Expert</th>
  <td class="max">851</td>
  <td class="min">724</td>
  <td>799</td>
  <td>827</td>
  <td>818</td>
  <td>804</td>
 </tr>
 <tr>
  <th>Boot.getFont</th>
  <td class="max">835</td>
  <td class="min">724</td>
  <td>832</td>
  <td>807</td>
  <td>828</td>
  <td>805</td>
 </tr>
 <tr>
  <th>Google Web Fonts Standard</th>
  <td>922</td>
  <td class="min">867</td>
  <td>883</td>
  <td class="max">1057</td>
  <td>921</td>
  <td>930</td>
 </tr>
 <tr>
  <th>Google Web Fonts Import</th>
  <td>902</td>
  <td>904</td>
  <td class="min">895</td>
  <td>1076</td>
  <td>932</td>
  <td class="max">942</td>
 </tr>
 <tr>
  <th>FontSquirrel Optimal</th>
  <td class="max">1092</td>
  <td>891</td>
  <td class="min">827</td>
  <td>949</td>
  <td>962</td>
  <td>944</td>
 </tr>
 <tr>
  <th>Typekit</th>
  <td class="min">935</td>
  <td class="max">1259</td>
  <td>1193</td>
  <td>970</td>
  <td>1003</td>
  <td>1072</td>
 </tr>
 <tr>
  <th>Typekit (Blocked Logo)</th>
  <td class="max">1228</td>
  <td>1097</td>
  <td class="min">949</td>
  <td></td>
  <td></td>
  <td>1091</td>
 </tr>
 <tr>
  <th>Google Web Fonts JavaScript</th>
  <td>1169</td>
  <td>1100</td>
  <td>1189</td>
  <td class="max">1285</td>
  <td class="min">836</td>
  <td>1116</td>
 </tr>
 <tr>
  <th>Grand Total</th>
  <td class="max">897</td>
  <td>852</td>
  <td>854</td>
  <td>893</td>
  <td class="min">824</td>
  <td>864</td>
 </tr>
 </table>

 <table class="font-benchmark-data">
 <tr>
  <th></th>
  <th>IE9</th>
  <th>IE8</th>
  <th>IE7</th>
  <th>Firefox</th>
  <th>Chrome</th>
  <th>Grand Total</th>
 </tr>
 <tr>
  <th>Boot.getFont (CDN)</th>
  <td>32</td>
  <td>30</td>
  <td class="min">26</td>
  <td class="max">44</td>
  <td>33</td>
  <td>34</td>
 </tr>
 <tr>
  <th>FontSquirrel Expert</th>
  <td class="max">32</td>
  <td>19</td>
  <td class="min">17</td>
  <td>25</td>
  <td>18</td>
  <td>49</td>
 </tr>
 <tr>
  <th>Google Web Fonts Standard</th>
  <td>16</td>
  <td>14</td>
  <td class="min">10</td>
  <td class="max">46</td>
  <td>24</td>
  <td>72</td>
 </tr>
 <tr>
  <th>Google Web Fonts Import</th>
  <td>8</td>
  <td>7</td>
  <td class="min">6</td>
  <td class="max">50</td>
  <td>30</td>
  <td>73</td>
 </tr>
 <tr>
  <th>Boot.getFont</th>
  <td class="min">31</td>
  <td>43</td>
  <td>47</td>
  <td class="max">113</td>
  <td>112</td>
  <td>86</td>
 </tr>
 <tr>
  <th>System</th>
  <td class="max">147</td>
  <td class="min">14</td>
  <td>15</td>
  <td>41</td>
  <td>15</td>
  <td>90</td>
 </tr>
 <tr>
  <th>FontSquirrel Optimal</th>
  <td>79</td>
  <td class="max">85</td>
  <td>35</td>
  <td class="min">12</td>
  <td>28</td>
  <td>104</td>
 </tr>
 <tr>
  <th>FontSquirrel Expert (CDN)</th>
  <td>41</td>
  <td class="min">30</td>
  <td>107</td>
  <td>51</td>
  <td class="max">310</td>
  <td>150</td>
 </tr>
 <tr>
  <th>Google Web Fonts JavaScript</th>
  <td class="max">46</td>
  <td class="min">5</td>
  <td>25</td>
  <td>30</td>
  <td>20</td>
  <td>156</td>
 </tr>
 <tr>
  <th>Typekit</th>
  <td>79</td>
  <td class="max">298</td>
  <td>154</td>
  <td>191</td>
  <td class="min">76</td>
  <td>216</td>
 </tr>
 <tr>
  <th>Typekit (Blocked Logo)</th>
  <td class="max">906</td>
  <td>149</td>
  <td class="min">141</td>
  <td></td>
  <td></td>
  <td>530</td>
 </tr>
 <tr>
     <th>Grand Total</th>
  <td class="max">340</td>
  <td>253</td>
  <td>225</td>
  <td>221</td>
  <td class="min">192</td>
  <td>253</td>
 </tr>
</table>
-->

<div class="font-browser-benchmarks tabs">
    <h4>IE 9</h4>
    <img src="http://www.artzstudio.com/files/font-performance/webfont-benchmarks-ie9.png" alt="Font Implementation Benchmarks: Internet Explorer 9">
    <h4>IE 8</h4>
    <img src="http://www.artzstudio.com/files/font-performance/webfont-benchmarks-ie8.png" alt="Font Implementation Benchmarks: Internet Explorer 8">
    <h4>IE 7</h4>
    <img src="http://www.artzstudio.com/files/font-performance/webfont-benchmarks-ie7.png" alt="Font Implementation Benchmarks: Internet Explorer 7">
    <h4>Firefox</h4>
    <img src="http://www.artzstudio.com/files/font-performance/webfont-benchmarks-firefox.png" alt="Font Implementation Benchmarks: Firefox">
    <h4>Chrome</h4>
    <img src="http://www.artzstudio.com/files/font-performance/webfont-benchmarks-chrome.png" alt="Font Implementation Benchmarks: Chrome">
</div>
<script src="http://www.artzstudio.com/files/font-performance/js/jquery-1.7.1.min.js"></script>
<script src="http://www.artzstudio.com/files/font-performance/js/jquery.tabs.min.js"></script>
<script>
(function($){
    $("div.font-browser-benchmarks").tabs({
        ui: {
            tabs: "h4",
            panels: "img"
        }
    });
})(jQuery);
</script>
<h3>My Observations</h3>
<p>The Do-It-Yourself implementations were consistently the fastest, especially when
combined with a CDN. This is due to physics&mdash;less bytes, requests, and CPU overhead
are required to serve the font.</p>
<p>It is interesting to compare Google Web Fonts (GWF) to Typekit since they use the same core
loader, but that is where the similarities end:
<h4>Google Web Fonts in Firefox (1254ms): JS &raquo; CSS &raquo; Font</h4>
<p><img src="http://www.artzstudio.com/files/font-performance/waterfall-gwf-javascript.png"></p>
<h4>Typekit in Firefox (795ms): JS &raquo; CSS Data URIs</h4>
<p><img src="http://www.artzstudio.com/files/font-performance/waterfall-typekit-javascript.png"></p>

In browsers that support them, <a href="http://www.webpagetest.org/result/111231_2K_2PNEM/10/details/">Typekit uses Data URIs
    in the CSS</a> to load the font, whereas <a href="http://www.webpagetest.org/result/111231_13_2PNDW/9/details/">GWF
    first loads the JS, then the CSS, and finally the font</a>. Typekit <a href="http://www.webpagetest.org/result/111231_QZ_2PNEG/4/details/">uses this approach in IE 8 and lower</a> where Data URIs are not supported, ending up with slower load times in those browsers.</p>
<p>Google is also slower because of their multiple DNS lookups; Typekit rightly uses one domain for all assets.</p>
<!--<p>Typekit&rsquo;s most <a href="http://www.webpagetest.org/result/111231_4S_2PNEH/10/details/">impressive performance came in IE9</a>, finding itself on par with the non-CDN DIY options. This shows the power of a CDN, which Typekit is certainly using.</p>-->
<p>I was impressed by the performance of Boot.getFont, which ended up being faster (sometimes by a hair, sometimes more) than
the standard @font-face CSS in all cases. My hypothesis is that somehow the JS triggers a
reflow/repaint that forces the fonts to download sooner in all browsers.</p>
<h2>Final Thoughts</h2>
<p>While this article could probably be split into several, I wanted a single place to
document implementation choices, tips for optimizing them, and have some reference benchmarks.
If other font providers want to hook me up with a free account (and host Open Sans, for consistency), I&rsquo;d be happy to
include them in another study at another time.</p>
<p>I was again dissappointed to see Google turn out <a href="http://www.artzstudio.com/2011/06/googles-button-is-slow-and-so-is-facebooks/">another</a> slow service. Google friends, take some notes from Typekit!</p>
<p>I am looking forward to hearing your thoughts and observations on this experiment,
and to your recommendations for speeding up web fonts. Thanks for reading!</p>
<link rel="stylesheet" href="http://www.artzstudio.com/files/font-performance/js/jquery.snippet.min.css"></link>
<script src="http://www.artzstudio.com/files/font-performance/js/jquery.snippet.min.js"></script>
<script>
(function($){
$("pre.style").snippet("css", {style: "bright", showNum: false});
$("pre.script").snippet("javascript", {style: "bright", showNum: false});
})(jQuery);
</script>
</body></html>]]></content:encoded>
			<wfw:commentRss>http://www.artzstudio.com/2012/02/web-font-performance-weighing-fontface-options-and-alternatives/feed/</wfw:commentRss>
		<slash:comments>23</slash:comments>
<enclosure url="http://www.artzstudio.com/files/font-performance/fout-demo.mp4" length="384691" type="video/mp4" />
		</item>
		<item>
		<title>Google&#8217;s Button is Slow&#8230;And so is Facebook&#8217;s.</title>
		<link>http://www.artzstudio.com/2011/06/googles-button-is-slow-and-so-is-facebooks/</link>
		<comments>http://www.artzstudio.com/2011/06/googles-button-is-slow-and-so-is-facebooks/#comments</comments>
		<pubDate>Fri, 03 Jun 2011 04:48:38 +0000</pubDate>
		<dc:creator>ArtzStudio</dc:creator>
				<category><![CDATA[Performance]]></category>

		<guid isPermaLink="false">http://www.artzstudio.com/?p=143</guid>
		<description><![CDATA[There, I said it! Yes Google, who built speed into its core values, has an entire website dedicated to making the web faster, developed PageSpeed, invents protocols named &#8220;SPDY&#8221;, and makes bad ass videos showing how quickly their browser loads web pages from the local disk, has their own take on the Like Button. And [...]]]></description>
			<content:encoded><![CDATA[<p>There, I said it!</p>
<p>Yes Google, who built speed into its <a href="http://www.google.com/about/corporate/company/tenthings.html" target="_self">core values</a>, has an entire website dedicated to <a href="http://code.google.com/speed/">making the web faster</a>, developed <a href="http://code.google.com/speed/page-speed/">PageSpeed</a>, invents <a href="http://www.chromium.org/spdy">protocols</a> named &#8220;SPDY&#8221;, and makes bad ass <a href="http://www.youtube.com/watch?v=nCgQDjiotG0">videos</a> showing how quickly their browser loads web pages from the <a href="http://itsalltech.com/2010/05/05/google-posts-chrome-speed-test-video-turns-out-it-was-a-fake/">local disk</a>, has their own take on the Like Button.</p>
<p>And it&#8217;s slow. Not only is it slow, it is slower than Facebook&#8217;s Like Button, which I didn&#8217;t think was possible.</p>
<p>Check out the painful WebPageTest results here:</p>
<ul>
<li> Google +1: <a href="http://www.webpagetest.org/result/110603_09_R97J/">http://www.webpagetest.org/result/110603_09_R97J/</a></li>
<li> Facebook Like: <a href="http://www.webpagetest.org/result/110603_MC_R97K/">http://www.webpagetest.org/result/110603_MC_R97K/</a></li>
</ul>
<p>Let&#8217;s assume it&#8217;s the first time we get to experience the joy of these buttons (clear cache):</p>
<table cellspacing="10">
<tbody>
<tr>
<th></th>
<th>Google +1</th>
<th>Facebook Like</th>
</tr>
<tr>
<th>Load Time</th>
<td>2.2 sec.</td>
<td>1.8 sec.</td>
</tr>
<tr>
<th>Bytes</th>
<td>66 KB</td>
<td>92 KB</td>
</tr>
<tr>
<th>Requests</th>
<td>8</td>
<td>9</td>
</tr>
</tbody>
</table>
<p>2 seconds to render a button?<em> <strong>A button.</strong></em> Really.</p>
<p>It&#8217;s okay, +1 will soon be everywhere, so they are sure to be cached super-well? No. Repeat view (cached) results:</p>
<table cellspacing="10">
<tbody>
<tr>
<th></th>
<th>Google +1</th>
<th>Facebook Like</th>
</tr>
<tr>
<th>Load Time</th>
<td>1.8 sec.</td>
<td>0.8 sec.</td>
</tr>
<tr>
<th>Bytes</th>
<td>25 KB</td>
<td>4 KB</td>
</tr>
<tr>
<th>Requests</th>
<td>4</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>The worst part is that this button will <a href="http://adage.com/article/digital/google-adds-button-foray-social-search/149645/">almost certainly</a> impact SEO ranking in Google, making it essential for most websites. And speed is <a href="http://googlewebmastercentral.blogspot.com/2010/04/using-site-speed-in-web-search-ranking.html">also a ranking factor</a>. I&#8217;m confused.</p>
<p>Facebook&#8217;s Like button is also required for maximizing traffic. Let&#8217;s see what they both look like &#8212; <a href="http://www.webpagetest.org/result/110603_5B_R9C4/">together</a>:</p>
<p><a href="http://www.artzstudio.com/files/2011/06/waterfall.png"><img class="alignnone size-full wp-image-144" title="google facebook buttons" src="http://www.artzstudio.com/files/2011/06/waterfall.png" alt="" width="600" height="476" /></a></p>
<p>Our bare minimum hope for first impression load time is <strong>2.5 seconds</strong>. According to Google, entire pages should load this fast!</p>
<p>Google, Facebook: I don&#8217;t think you need me to make recommendations on how to fix it. I know <a href="http://code.google.com/speed/page-speed/docs/rules_intro.html">you can do it</a> &#8211; please make it a priority.</p>
<p>Please also provide an asynchronous JS snippet as a recommended option in your <a href="http://code.google.com/apis/+1button/">instructions</a> like you did with <a href="http://code.google.com/apis/analytics/docs/tracking/asyncTracking.html">Google Analytics</a>.</p>
<p>You hurt me today Google, and you&#8217;re hurting the web. I thought you and me were like <a href="http://www.artzstudio.com/files/2011/06/peascarrots.mp3">THIS</a>:</p>
<p><a href="http://www.artzstudio.com/files/2011/06/2926997_orig.png"><img class="alignnone size-full wp-image-146" title="peas and carrots" src="http://www.artzstudio.com/files/2011/06/2926997_orig.png" alt="" width="300" height="291" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.artzstudio.com/2011/06/googles-button-is-slow-and-so-is-facebooks/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
<enclosure url="http://www.artzstudio.com/files/2011/06/peascarrots.mp3" length="49110" type="audio/mpeg" />
		</item>
		<item>
		<title>Foreground &lt;img&gt; Sprites &#8211; High Contrast Mode Optimization</title>
		<link>http://www.artzstudio.com/2010/04/img-sprites-high-contrast/</link>
		<comments>http://www.artzstudio.com/2010/04/img-sprites-high-contrast/#comments</comments>
		<pubDate>Tue, 20 Apr 2010 20:00:57 +0000</pubDate>
		<dc:creator>ArtzStudio</dc:creator>
				<category><![CDATA[Accessibility]]></category>
		<category><![CDATA[Performance]]></category>

		<guid isPermaLink="false">http://www.artzstudio.com/?p=127</guid>
		<description><![CDATA[An issue that has stressed the relationship between web performance and accessibility is the little known fact that CSS image sprites, a technique used to reduce image HTTP requests, dissappear in Microsoft Windows&#8217; high contrast mode. This is because they are typically created using the background-image CSS property. To demonstrate this issue, let&#8217;s take a [...]]]></description>
			<content:encoded><![CDATA[<p>An issue that has stressed the relationship between web performance and accessibility is the little known fact that <a href="http://www.alistapart.com/articles/sprites">CSS image sprites</a>, a <a href="http://www.websiteoptimization.com/speed/tweak/css-sprites/">technique used to reduce image HTTP requests</a>, dissappear in Microsoft Windows&#8217; high contrast mode. This is because they are typically created using the <code>background-image</code> CSS property.</p>
<p>To demonstrate this issue, let&#8217;s take a look at some popular websites in High Contrast mode.</p>
<p>In <strong>Google Video</strong>, the next and previous buttons dissappear:</p>
<p><img src="http://www.artzstudio.com/files/img-sprites/google-video.png" width="350" height="400" alt="screen shot in high contrast mode showing arrows dissappear in google video" /></p>
<p>In <strong>Yahoo Finance</strong>, the navigational tabs and buttons dissappear:</p>
<p><img src="http://www.artzstudio.com/files/img-sprites/yahoo-finance.png" width="600" alt="screen shot of yahoo finance tabs and buttons going away in high contrast mode" /></p>
<p>In sites like <strong>Facebook</strong>, <strong>Amazon</strong> and <strong>AOL Music</strong>,  logos vanish from thin air&#8230;err, screen:</p>
<p><img src="http://www.artzstudio.com/files/img-sprites/facebook.png" width="519" height="400" alt="facbook logo dissappear in high contrast" /></p>
<p><img src="http://www.artzstudio.com/files/img-sprites/amazon.png" width="300" height="193" alt="amazon logo dissapear in high contrast" /><img src="http://www.artzstudio.com/files/img-sprites/aol-music.png" width="214" height="300" alt="aol music dissappear in high contrast" /></p>
<p>Popular content sharing service <strong>AddThis</strong> also incorporates CSS sprites for its toolbox sharing buttons:</p>
<p><img src="http://www.artzstudio.com/files/img-sprites/addthis.png" width="221" height="360" alt="addthis screen shot" /></p>
<p>It is great more sites are using sprites to deliver a faster user experience, however we need to recognize (myself included) we are damaging the user experience for High Contrast  users.</p>
<h2>Introducing &lt;img&gt; Sprites</h2>
<p>While noodling over a new design for AOL.com that featured graphical headers using our new corporate identity font, I decided to prototype something I had thought about a couple years ago but never got around to doing. </p>
<p>Since <code>&lt;img&gt;</code> elements show up in High Contrast mode, why not try to crop the image to show what we want?</p>
<p>Our example HTML for graphic headers in this case look like this:</p>
<pre lang="javascript">&lt;h2 class=&quot;popular&quot;&gt;&lt;img src=&quot;img-sprite.png&quot; alt=&quot;&quot; /&gt;Featured&lt;/h2&gt;
&lt;h2 class=&quot;featured&quot;&gt;&lt;img src=&quot;img-sprite.png&quot; alt=&quot;&quot; /&gt;Popular&lt;/h2&gt;</pre>
<p>We set the <code>alt</code> attribute to <code>&quot;&quot;</code> so screen readers skip over it. We include the &quot;Featured&quot; text so search engines have an understanding of what this section is about (more powerful than alt text).</p>
<p>The following CSS is then applied to crop the parts of the image we want:</p>
<pre lang="css">
h2 {
	overflow: hidden;
	position: relative;
	height: 50px;
	width: 200px;
}
h2 img {
	position: relative;
}
h2.popular img {
	top: -100px;
}
h2.featured img {
	top: -200px;
}</pre>
<p>Simply set the height (and width if needed) on the outer container (in this case, the <code>&lt;h2&gt;</code>) to the size of the image you want to crop, and play around with top (and left if needed) to move the image into place.</p>
<h2>Verifying Your Implementation</h2>
<p><img style="float:right;margin-left:24px;" src="http://www.artzstudio.com/files/img-sprites/high-contrast.png" width="383" height="195" alt="high contrast accessibility panel" />To enable High Contrast mode in Windows:</p>
<ol>
<li>Start Menu&#8230; Control Panel</li>
<li>Open Accessibility Options </li>
<li>Click on the Display tab </li>
<li>Ceck the High Contrast checkbox</li>
<li>Click Apply to see the effect.</li>
</ol>
<p>Or&#8230;</p>
<ol>
<li>Alt + Shift + Printscreen</li>
</ol>
<h2 style="clear:left">&lt;img&gt; Sprite Working Demos</h2>
<p>Check out our CSS Sprites Demo, and then turn on High Contrast mode. Then, visit the &lt;img&gt; Sprite page to see the difference.</p>
<ul>
<li><a href="http://www.artzstudio.com/files/img-sprites/css-sprite-site.html">CSS Sprites Demo Page</a></li>
<li><a href="http://www.artzstudio.com/files/img-sprites/img-sprite-site.html">&lt;img&gt; Sprites Demo Page</a></li>
</ul>
<h2>Known Limitations</h2>
<p>For image cropping to work, it must be inside a <em><a href="http://en.wikipedia.org/wiki/HTML_element#Block_elements">block</a></em><a href="http://en.wikipedia.org/wiki/HTML_element#Block_elements"> element</a> or an <em><a href="http://en.wikipedia.org/wiki/HTML_element#Inline_elements">inline</a></em><a href="http://en.wikipedia.org/wiki/HTML_element#Inline_elements"> element</a> with the CSS property <code>display: block</code>.</p>
<p>Chris Blouch, AOL&#8217;s resident accessibility expert tested this technique out on various HTML elements and found that we <strong>cannot</strong> crop <code>&lt;img&gt;</code> elements inside the following elements:</p>
<ul>
<li>Fail to crop:  <code>&lt;fieldset&gt;</code>, <code>&lt;legend&gt;</code>, <code>&lt;input&gt;</code>, <code>&lt;button&gt;</code>, <code>&lt;table&gt;</code>, <code>&lt;tr&gt;</code>, <code>&lt;td&gt;</code>, <code>&lt;th&gt;</code></li>
</ul>
<p>All other tags should work, please leave a comment if you find otherwise.</p>
<p>This solution has been tested to work in IE6+, Firefox 3.5+, Chrome and Safari 4+ and it is expected to work in all future browsers.</p>
<h2>Detecting High Contrast Mode</h2>
<p>Chris Blouch also created a High Contrast detector as part of the AXS Accessibility JavaScript Library. It should come in handy if you are really having trouble getting your site to look good in High Contrast mode.</p>
<ul>
<li>http://dev.aol.com/downloads/axs1.2/readme.html#hd</li>
</ul>
<h2>More Information on High Contrast Mode</h2>
<p>This video gives a nice overview of the challenges facing people with a low vision disability. At 19:38 the host goes through some of the accessibility tools available in Windows like High Contrast mode:</p>
<p>
  <object width="480" height="385"><param name="movie" value="http://www.youtube.com/v/fw91hsVrf40&#038;hl=en_US&#038;fs=1&#038;color1=0x5d1719&#038;color2=0xcd311b&#038;start=1178"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param>
    <embed src="http://www.youtube.com/v/fw91hsVrf40&#038;hl=en_US&#038;fs=1&#038;color1=0x5d1719&#038;color2=0xcd311b&#038;start=1178" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"></embed></object></p>
<h2>Bonus! Printable Image Sprites!</h2>
<p>A couple commenters pointed out background images don&#8217;t print by default and this technique solves that. Here&#8217;s a print preview of my demo pages in Firefox for some evidence.</p>
<h3>CSS Sprites Printed</h3>
<p><img width="680" height="555" src="http://www.artzstudio.com/files/img-sprites/css-sprite-print.png" alt="screen shot of css sprite printed, images not showing up" /></p>
<h3>&lt;img&gt; Sprites Printed</h3>
<p><img width="680" height="555" src="http://www.artzstudio.com/files/img-sprites/img-sprite-print.png" alt="screen shot of img sprite printed, images showing up" /></p>
<p>I guess I shouldn&#8217;t assume it looks good in IE too, let me know if it doesn&#8217;t.</p>
<h2>Further Reading</h2>
<p>I wanted to call out Thierry Koblentz, he kindly informed me (see comments) that he wrote about <a href="http://tjkdesign.com/articles/how-to_use_sprites_with_my_Image_Replacement_technique.asp">this exact technique</a> save for me going the relative positioning route. Turns out I&#8217;m not as original as I thought &#8212; nice job Thierry.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.artzstudio.com/2010/04/img-sprites-high-contrast/feed/</wfw:commentRss>
		<slash:comments>25</slash:comments>
		</item>
		<item>
		<title>Web Performance Impacts Revenue: Velocity &#8217;09 Highlights</title>
		<link>http://www.artzstudio.com/2009/06/web-performance-impact-on-revenue-velocity-09-highlights/</link>
		<comments>http://www.artzstudio.com/2009/06/web-performance-impact-on-revenue-velocity-09-highlights/#comments</comments>
		<pubDate>Fri, 26 Jun 2009 22:02:17 +0000</pubDate>
		<dc:creator>ArtzStudio</dc:creator>
				<category><![CDATA[ArtzStudio]]></category>

		<guid isPermaLink="false">http://www.artzstudio.com/?p=114</guid>
		<description><![CDATA[I just got back from Velocity, a new-ish (2nd one) performance &#38; scalability/availability conference. Last year, a common complaint was a lack of presentations around &#8220;why speed matters&#8221; and this year they did not disappoint. First, Google and Microsoft teamed up to present the results from experiments on the impact of performance on their search [...]]]></description>
			<content:encoded><![CDATA[<style type="text/css">.entry img.frame {border:5px solid #CEE1EF}</style>
<p>I just got back from <a href="http://en.oreilly.com/velocity2009">Velocity</a>, a new-ish (2nd one) performance &amp; scalability/availability conference. Last year, a common complaint was a lack of presentations around &#8220;why speed matters&#8221; and this year they did not disappoint. </p>
<p> First, <a href="http://radar.oreilly.com/2009/06/bing-and-google-agree-slow-pag.html">Google and Microsoft</a> teamed up to present the results from experiments on the impact of performance on their search business:</p>
<p><img class="frame" src="http://www.artzstudio.com/files/velocity-09/bing-search-delay.png" alt="bing search delay impact on performance" width="400" height="300" /></p>
<p>	The Bing graph shows that just a 1/2 sec. delay before sending the search results had a -1.2% negative impact on revenue. A 2 second delay had a -2.1% impact.</p>
<p>	<img class="frame" src="http://www.artzstudio.com/files/velocity-09/google-server-delay.png" alt="google search delay impact on performance" width="400" height="300" /></p>
<p>Google slowed down their load progressively, and found that delaying 400ms after their header (logo, etc.) resulted in -0.59% less daily searches per users.</p>
<p> The next day, Marissa Meyer (VP, Google User Experience) gave a talk and revealed their historic findings:</p>
<p><img class="frame" src="http://www.artzstudio.com/files/velocity-09/google-historical-speed-impact.png" alt="google historical delay  impact on speed" width="400" height="300" /></p>
<p>
	They delayed results 400ms and searches/user decreased -0.44%, then to -0.76% after six weeks. After removing the delay, users did -0.21% fewer searches, hinting at an after-effect to speed. She noted this was &ldquo;millions&rdquo; in potential revenue that could be lost.</p>
<p> Shopzilla.com (shopping comparison search) made an optimization (much like we have done at AOL) to move their static images off the &#8220;*.shoppzilla.com&#8221; domain to avoid cookie pollution from the top level domain.<br />
	This tweak earned them a 0.5% top line revenue increase. </p>
<p>They went on to improve their performance from 4-6 seconds to 1.5 seconds, and had the following results to share:</p>
<p>	<img class="frame" src="http://www.artzstudio.com/files/velocity-09/shopzilla-performance-summary.png" alt="shopzilla performance results from speed redesign" width="400" height="300" /></p>
<p>	Conversion rates increased 7 to 12%. Page views increased 25%. They found Google rewarded them in search results for SEM (speed is an SEM quality factor) 8% increase in SEM visits in the U.S., and 120% in the U.K.</p>
<p> Dave Artz (yours truly) gave a talk on <a href="http://assets.en.oreilly.com/1/event/29/The Secret Weapons of the AOL Optimization Team Presentation.pdf">AOL&#8217;s secret weapons in optimization</a>, and shared some new research he&#8217;s done with their internal tracking system&#8217;s data.</p>
<p> We took a sample of visits over the course of a day on various AOL sites, and calculated the average load time of the pages on each visit. We then broke the visits into percentile groups, the top 10th percentile being the fastest 10% of visits and the bottom 100th being the slowest 10%, and examined the Page Views within each visit group (fyi, refreshing photo galleries were not counted as page views):</p>
<p><img class="frame" src="http://www.artzstudio.com/files/velocity-09/aol-pv-visit-speed-correlation.png" alt="aol speed impact on page views visit" width="400" height="300" /></p>
<p>The data showed that the visits with the fastest load times delivered the most page views, and degraded (sometimes quickly) as load times decreased. We found a clear correlation between faster/slower visits and page views.</p>
<p>Now you may be wondering, how do I get faster? IM me, we&#8217;ll talk about it. <img src='http://www.artzstudio.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.artzstudio.com/2009/06/web-performance-impact-on-revenue-velocity-09-highlights/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>jQuery Performance Rules</title>
		<link>http://www.artzstudio.com/2009/04/jquery-performance-rules/</link>
		<comments>http://www.artzstudio.com/2009/04/jquery-performance-rules/#comments</comments>
		<pubDate>Thu, 09 Apr 2009 01:37:50 +0000</pubDate>
		<dc:creator>ArtzStudio</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[jquery]]></category>

		<guid isPermaLink="false">http://www.artzstudio.com/?p=83</guid>
		<description><![CDATA[Once upon a time, all we needed to worry about was reducing Bytes and Requests and playing around with load order to make things faster. Nowadays, we are increasingly impacting one more major component in performance &#8211; CPU utilization. Using jQuery and other frameworks that make selecting nodes and DOM manipulation easy can have adverse [...]]]></description>
			<content:encoded><![CDATA[<p>Once upon a time, all we needed to worry about was reducing Bytes and Requests and playing around with load order to make things faster. Nowadays, we are increasingly impacting one more major component in performance &#8211; <strong>CPU utilization</strong>. Using jQuery and other frameworks that make selecting nodes and DOM manipulation easy can have adverse affects if you&#8217;re not careful and follow some simple practices for reducing the work the browser has to do.</p>
<ol>
<li><a href="#descend-from-id">Always Descend From an #id</a></li>
<li><a href="#use-tags-before-classes">Use Tags Before Classes</a></li>
<li><a href="#cache-jquery-objects">Cache jQuery Objects</a></li>
<li><a href="#harness-chaining">Harness the Power of Chaining</a></li>
<li><a href="#use-sub-queries">Use Sub-queries</a></li>
<li><a href="#limit-dom-manipulation">Limit Direct DOM Manipulation</a></li>
<li><a href="#leverage-event-delegation">Leverage Event Delegation</a> (a.k.a. Bubbling)</li>
<li><a href="#eliminate-query-waste">Eliminate Query Waste</a></li>
<li><a href="#defer-to-window-load">Defer to $(window).load</a></li>
<li><a href="#compress-your-js">Compress Your JS</a></li>
<li><a href="#learn-the-library">Learn the Library</a></li>
</ol>
<p><span id="more-83"></span></p>
<h2 id="descend-from-id">1. Always Descend From an #id</h2>
<p>The fastest selector in jQuery is the ID selector (<code>$('#someid')</code>). This is because it maps directly to a native JavaScript method, <code>getElementById()</code>.</p>
<h3>Selecting Single Elements</h3>
<pre>&lt;div id="content"&gt;
	&lt;form method="post" action="/"&gt;
		&lt;h2&gt;Traffic Light&lt;/h2&gt;
		&lt;ul id="traffic_light"&gt;
			&lt;li&gt;&lt;input type="radio" class="on" name="light" value="red" /&gt; Red&lt;/li&gt;
			&lt;li&gt;&lt;input type="radio" class="off" name="light" value="yellow" /&gt; Yellow&lt;/li&gt;
			&lt;li&gt;&lt;input type="radio" class="off" name="light" value="green" /&gt; Green&lt;/li&gt;
		&lt;/ul&gt;
		&lt;input class="button" id="traffic_button" type="submit" value="Go" /&gt;
	&lt;/form&gt;
&lt;/div&gt;</pre>
<p>Selecting the button like this is slower:</p>
<pre>var traffic_button = $('#content .button');</pre>
<p>Instead, select the button directly:</p>
<pre>var traffic_button = $('#traffic_button');</pre>
<h3>Selecting Multiple Elements</h3>
<p>Once we start talking about selecting multiple elements, we are really  talking about DOM traversal and looping, something that is slow.  To minimize the performance hit, <strong>always descend from the closest parent ID</strong>:</p>
<pre>var traffic_lights = $('#traffic_light input');</pre>
<h2 id="use-tags-before-classes">2. Use Tags Before Classes</h2>
<p>The second fastest selector in jQuery is the Tag selector (<code>$('head')</code>). Again, this is because it maps to a native JavaScript method, <code>getElementsByTagName()</code></p>
<p>.</p>
<pre>&lt;div id="content"&gt;
	&lt;form method="post" action="/"&gt;
		&lt;h2&gt;Traffic Light&lt;/h2&gt;
		&lt;ul id="traffic_light"&gt;
			&lt;li&gt;&lt;input type="radio" class="on" name="light" value="red" /&gt; Red&lt;/li&gt;
			&lt;li&gt;&lt;input type="radio" class="off" name="light" value="yellow" /&gt; Yellow&lt;/li&gt;
			&lt;li&gt;&lt;input type="radio" class="off" name="light" value="green" /&gt; Green&lt;/li&gt;
		&lt;/ul&gt;
		&lt;input class="button" id="traffic_button" type="submit" value="Go" /&gt;
	&lt;/form&gt;
&lt;/div&gt;</pre>
<p>Always prefix a class with a tag name (and remember to descend from an ID):</p>
<pre>var active_light = $('#traffic_light input.on');</pre>
<p><em>Note: The class selector is among the slowest selectors in jQuery; in IE it loops through the entire DOM. Avoid using it whenever possible.</em> Never prefix an ID with a tag name. For example, this is slow because it will loop through all <code>&lt;div&gt;</code> elements looking for the ‘content’ ID:</p>
<pre>var content = $('div#content');</pre>
<p>Along the same lines, it is redundant to descend from multiple IDs:</p>
<pre>var traffic_light = $('#content #traffic_light');</pre>
<h2 id="cache-jquery-objects">3. Cache jQuery Objects</h2>
<p>Get in the habit of saving your jQuery objects to a variable (much like our examples above).  For example, never (eeeehhhhver) do this:</p>
<pre>$('#traffic_light input.on).bind('click', function(){...});
$('#traffic_light input.on).css('border', '3px dashed yellow');
$('#traffic_light input.on).css('background-color', 'orange');
$('#traffic_light input.on).fadeIn('slow');</pre>
<p>Instead, first save the object to a local variable, and continue your operations:</p>
<pre>var $active_light = $('#traffic_light input.on');
$active_light.bind('click', function(){...});
$active_light.css('border', '3px dashed yellow');
$active_light.css('background-color', 'orange');
$active_light.fadeIn('slow');</pre>
<p><em>Tip: Since we want to remember that our local variable is a jQuery wrapped set, we are using $ as a prefix.</em> Remember, <strong>never repeat a jQuery selection operation</strong> more than once in your application.</p>
<h3>Bonus Tip &#8211; Storing jQuery results for later</h3>
<p>If you intend to use the jQuery result object(s) in another part of your program, or should your function execute more than once, cache it in an object with a <a href="http://www.webdevelopersnotes.com/tutorials/javascript/global_local_variables_scope_javascript.php3">global scope</a>.  By defining a global container with jQuery results, we can reference them from within other functions:</p>
<pre>// Define an object in the global scope (i.e. the window object)
window.$my =
{
	// Initialize all the queries you want to use more than once
	head : $('head'),
	traffic_light : $('#traffic_light'),
	traffic_button : $('#traffic_button')
};

function do_something()
{
	// Now you can reference the stored results and manipulate them
	var script = document.createElement('script');
	$my.head.append(script);

	// When working inside functions, continue to save jQuery results
	// to your global container.
	$my.cool_results = $('#some_ul li');
	$my.other_results = $('#some_table td');

	// Use the global functions as you would a normal jQuery result
	$my.other_results.css('border-color', 'red');
	$my.traffic_light.css('border-color', 'green');
}</pre>
<h2 id="harness-chaining">4. Harness the Power of Chaining</h2>
<p>The previous example can also be accomplished like this:</p>
<pre>var $active_light = $('#traffic_light input.on');$active_light.bind('click', function(){...})
	.css('border', '3px dashed yellow')
	.css('background-color', 'orange')
	.fadeIn('slow');</pre>
<p>This allows us to write less code, making our JavaScript more lightweight.</p>
<h2 id="use-sub-queries">5. Use Sub-queries</h2>
<p>jQuery allows us to run additional selector operations on a wrapped set. This reduces performance overhead on subsequent selections since we already grabbed and stored the  parent object in a local variable.</p>
<pre>&lt;div id="content"&gt;
	&lt;form method="post" action="/"&gt;
		&lt;h2&gt;Traffic Light&lt;/h2&gt;
		&lt;ul id="traffic_light"&gt;
			&lt;li&gt;&lt;input type="radio" class="on" name="light" value="red" /&gt; Red&lt;/li&gt;
			&lt;li&gt;&lt;input type="radio" class="off" name="light" value="yellow" /&gt; Yellow&lt;/li&gt;
			&lt;li&gt;&lt;input type="radio" class="off" name="light" value="green" /&gt; Green&lt;/li&gt;
		&lt;/ul&gt;
		&lt;input class="button" id="traffic_button" type="submit" value="Go" /&gt;
	&lt;/form&gt;
&lt;/div&gt;</pre>
<p>For example, we can leverage sub-queries to grab the active and inactive lights and cache them for later manipulation.</p>
<pre>var $traffic_light = $('#traffic_light'),
	$active_light = $traffic_light.find('input.on'),
	$inactive_lights = $traffic_light.find('input.off');</pre>
<p><em>Tip: You can declare multiple local variables by separating them with commas – save those bytes!</em></p>
<h2 id="limit-dom-manipulation">6. Limit Direct DOM <a href="http://docs.jquery.com/Manipulation">Manipulation</a></h2>
<p>The basic idea here is to create exactly what you need in memory, and <strong>then</strong> update the DOM. This is not a jQuery best practice, but a must for efficient JavaScript. <a href="http://www.tvidesign.co.uk/blog/improve-your-jquery-25-excellent-tips.aspx#tip6">Direct DOM manipulation is slow</a>.  For example, if you need to dynamically create a list of elements, do not do this:</p>
<pre>var top_100_list = [...], // assume this has 100 unique strings
	$mylist = $('#mylist'); // jQuery selects our &lt;ul&gt; element

for (var i=0, l=top_100_list.length; i&lt;l; i++)
{
	$mylist.append('&lt;li&gt;' + top_100_list[i] + '&lt;/li&gt;');
}</pre>
<p>Instead, we want to create the entire set of elements in a string before inserting into the DOM:</p>
<pre>var top_100_list = [...], // assume this has 100 unique strings
	$mylist = $('#mylist'), // jQuery selects our &lt;ul&gt; element
	top_100_li = ""; // This will store our list items

for (var i=0, l=top_100_list.length; i&lt;l; i++)
{
	top_100_li += '&lt;li&gt;' + top_100_list[i] + '&lt;/li&gt;';
}
$mylist.html(top_100_li);</pre>
<p>Even faster, we should <strong>always wrap many elements</strong> in a single parent node before insertion:</p>
<pre>var top_100_list = [...], // assume this has 100 unique strings
	$mylist = $('#mylist'), // jQuery selects our &lt;ul&gt; element
	top_100_ul = '&lt;ul id="#mylist"&gt;'; // This will store our entire unordered list

for (var i=0, l=top_100_list.length; i&lt;l; i++)
{
	top_100_ul += '&lt;li&gt;' + top_100_list[i] + '&lt;/li&gt;';
}
top_100_ul += '&lt;/ul&gt;'; // Close our unordered list

$mylist.replaceWith(top_100_ul);</pre>
<p>If you do the above and are still concerned about performance:</p>
<ul>
<li>Give jQuery’s <code>clone()</code> method a try. This creates a copy of the node tree, which you can manipulate “off-line” and then insert back in when you are ready.</li>
<li>Use <a href="http://www.devguru.com/technologies/xmldom/quickref/obj_documentFragment.html">DOM DocumentFragments</a>. As the <a href="http://ejohn.org/blog/dom-documentfragments/">creator of jQuery points out</a>, they perform much better than direct DOM manipulation. The idea would be to create what you need (similar to what we did above with a string), and use the jQuery <a href="http://docs.jquery.com/Manipulation">insert or replace methods</a>.</li>
</ul>
<h2 id="leverage-event-delegation">7. Leverage Event Delegation (a.k.a. Bubbling)</h2>
<p>Unless <a href="http://docs.jquery.com/Events/jQuery.Event#event.stopPropagation.28.29">told otherwise</a>, every event (e.g. click, mouseover, etc.) in JavaScript “bubbles” up the DOM tree to parent elements. This is incredibly useful when we want many elements (nodes) to call the same function.  Instead of binding an event listener function to many nodes—very inefficient—you can <strong>bind it once</strong> to their parent, and have it figure out which node triggered the event.  For example, say we are developing a large form with many inputs, and want to toggle a class name when selected.  A binding like this is inefficient:</p>
<pre>$('#myList li).bind('click', function(){
	$(this).addClass('clicked');
	// do stuff
});</pre>
<p>Instead, we should listen for the click event at the parent level:</p>
<pre>$('#myList).bind('click', function(e){
	var target = e.target, // e.target grabs the node that triggered the event.
		$target = $(target);  // wraps the node in a jQuery object
	if (target.nodeName === 'LI') {
		$target.addClass('clicked');
		// do stuff
	}
});</pre>
<p>The parent node acts as a dispatcher and can then do work based on what <a href="http://docs.jquery.com/Events/jQuery.Event#event.target">target element</a> triggered the event.  If you find yourself binding one event listener to many elements, you are doing something wrong (and slow).</p>
<h2 id="eliminate-query-waste">8. Eliminate Query Waste</h2>
<p>Although jQuery fails nicely if it does not find any matching elements, it still takes  time to look for them. If you have one global JavaScript for your entire site, it may be tempting to throw every one of your jQuery functions into <code>$(document).ready(function(){ // all my glorious code })</code>.  Don&#8217;t you dare.  Only run functions that are applicable to the page. The most efficient way to do this is to use inline initialization functions so your template has full control over when and where JavaScript executes.  For example, in your “article” page template, you would include the following code before the body close:</p>
<pre>&lt;script type="text/javascript&gt;
mylib.article.init();
&lt;/script&gt;
&lt;/body&gt;</pre>
<p>If your page template includes any variety of modules that may or may not be on the page, or for visual reasons you need them to initialize sooner, you could place the initialization function immediately after the module.</p>
<pre>&lt;ul id="traffic_light"&gt;
	&lt;li&gt;&lt;input type="radio" class="on" name="light" value="red" /&gt; Red&lt;/li&gt;
	&lt;li&gt;&lt;input type="radio" class="off" name="light" value="yellow" /&gt; Yellow&lt;/li&gt;
	&lt;li&gt;&lt;input type="radio" class="off" name="light" value="green" /&gt; Green&lt;/li&gt;
&lt;/ul&gt;
&lt;script type="text/javascript&gt;
mylib.traffic_light.init();
&lt;/script&gt;</pre>
<p>Your Global JS library would look something like this:</p>
<pre>var mylib =
{
	article_page :
	{
		init : function()
		{
			// Article page specific jQuery functions.
		}
	},
	traffic_light :
	{
		init : function()
		{
			// Traffic light specific jQuery functions.
		}
	}
}</pre>
<h2 id="defer-to-window-load">9. Defer to <code>$(window).load</code></h2>
<p>There is a temptation among jQuery developers to hook everything into the <code>$(document).ready</code> pseudo event. After all, it is used in most examples you will find.  Although <code>$(document).ready</code> is incredibly useful, it occurs during page render while objects are still downloading. If you notice your page stalling while loading, all those <code>$(document).ready</code> functions could be the reason why.  You can reduce CPU utilization during the page load by binding your jQuery functions to the <code>$(window).load</code> event, which occurs after all objects called by the HTML (including <code>&lt;iframe&gt;</code> content) have downloaded.</p>
<pre>$(window).load(function(){
	// jQuery functions to initialize after the page has loaded.
});</pre>
<p>Superfluous functionality such as drag and drop, binding visual effects and animations, pre-fetching hidden images, etc., are all good candidates for this technique.</p>
<h2 id="compress-your-js">10. Compress Your JS</h2>
<p>Okay, this isn&#8217;t jQuery related, but I had to include it. There is a tendency to make JavaScript functions and variables overly descriptive, which is essential for developers but irrelevant to users.  No more excuses, it&#8217;s time to build JS compression into our workflows. Comment the heck out of your code, and run it through a compression tool before launching to production.  Use <a href="http://www.julienlecomte.net/yuicompressor/">YUICompressor</a> to squeeze out wasteful bytes from your code. In our experience, it safely compresses JavaScript as small as it can possibly get without a CPU penalty (such as Base62 encoding with <a href="http://dean.edwards.name/packer/">Packer</a>).  <em>Tip: For maximum compression in YUICompressor, always declare your variables (e.g. var my_long_variable_name;).</em></p>
<h2 id="learn-the-library">11. Learn the Library</h2>
<p>Print out this <a href="http://acodingfool.typepad.com/blog/jquery-13-cheat-sheet.html">jQuery 1.3 cheat sheet</a>, and make it a goal to eventually understand what each function does. If you find yourself repeating yourself repeating, there is probably an easier (and more efficient) way.  <a href="http://www.artzstudio.com/files/jquery-rules/jquery_1.3_cheatsheet_v1.pdf"><img src="http://www.artzstudio.com/files/jquery-rules/jquery_13_cheatsheet_thumb.jpg" alt="jquery cheat sheet" width="336" height="212" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.artzstudio.com/2009/04/jquery-performance-rules/feed/</wfw:commentRss>
		<slash:comments>64</slash:comments>
		</item>
		<item>
		<title>Bulk Image Compression with Photoshop Droplets</title>
		<link>http://www.artzstudio.com/2008/08/bulk-image-compression-with-photoshop-droplets/</link>
		<comments>http://www.artzstudio.com/2008/08/bulk-image-compression-with-photoshop-droplets/#comments</comments>
		<pubDate>Mon, 11 Aug 2008 02:14:30 +0000</pubDate>
		<dc:creator>ArtzStudio</dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[graphics]]></category>
		<category><![CDATA[image compression]]></category>
		<category><![CDATA[photos]]></category>
		<category><![CDATA[photoshop]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.artzstudio.com/?p=72</guid>
		<description><![CDATA[I recently exported a bunch of photos from iPhoto for an article I am working on, and discovered there was very little compression applied. Even at a lower 640 by 480 dimension size, the 30 images totaled 5.4 MB in size! I needed a way to quickly compress these, and then I remembered Photoshop&#8217;s ability [...]]]></description>
			<content:encoded><![CDATA[<p><img class="doodle" src="/files/sfw-droplet/dropletron.png" width="190" height="97" />
<p>I recently exported a bunch of photos from iPhoto for an article I am working on, and discovered there was very little compression applied. Even at a lower  640 by 480 dimension size, the 30 images totaled 5.4 MB in size!</p>
<p>I needed a way to quickly compress these, and then I remembered Photoshop&#8217;s ability to create Droplets. A Droplet is an icon created by Photoshop that launches Actions on files that you drag on top of it. The resulting file is then saved in a folder of your choice.</p>
<p>This allows me to drag all 30 images on to the Droplet, and have Photoshop compress the entire batch automatically. </p>
<p>For those of you that learn by watching, I created a 5 minute screencast showing how it&#8217;s done.</p>
<div id="sfw-droplet-screencast"><a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.</div>
<p><script type="text/javascript">swfobject.embedSWF("/i/jlflv4.swf", "sfw-droplet-screencast", "640", "500", "9.0.0", {}, {file: "/files/sfw-droplet/sfw-droplet-screencast.flv", image: "/files/sfw-droplet/sfw-droplet-easel.png", fullscreen: true}, {allowfullscreen: true, allowscriptaccess: "always"});</script></p>
<p>For those of you that learn by reading, read on!</p>
<h3>Step 1: Open a Test Image</h3>
<p>In Photoshop, open up any image. The image is not important, you simply want something you can record your actions on.</p>
<h3>Step 2: Record a New Action</h3>
<p>In Photoshop, open the <i>Window&#8230;Actions</i> panel.</p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/actions-panel.png" alt="actions panel" width="278" height="172" /></p>
<p>Click the <i>Create new action</i> button.</p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/new-action.png" alt="create new action in actions panel" width="464" height="184" /></p>
<p>Give the Action a name descriptive of what it does. We will name ours <i>JPEG50</i>, because this Action will save out a JPEG at 50 Quality.</p>
<p>Click <i>Record</i>.</p>
<h3>Step 3: Save For Web</h3>
<p>Your Action is now recording, so be careful from here on out!</p>
<p><i>Tip: If you wanted to resize the image, or apply Filters before saving, you could do that and Photoshop will record these steps!</i></p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/file-sfw.png" alt="file save for web" width="317" height="65" /></p>
<p>Click <i>File&#8230;Save for Web &amp; Devices</i>.</p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/sfw-jpeg.png" alt="save for web jpeg settings" width="229" height="259" /></p>
<p>Set the compression type to <i>JPEG</i>, and choose a Quality setting you like. We recommend <i>50 Quality</i> for optimum visual quality to file size.</p>
<p>Unless these files will be used in Flash, always use the <i>Progressive</i> option. It enables your JPEGs to render progressively in your user&#8217;s browser.</p>
<p>Click <i>Save</i>.</p>
<h3>Step 4: Choose a Location for Compressed Images</h3>
<p>You will need to create a folder for the compressed images, so when the Action or Droplet is run you know where the resulting files go.</p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/new-save-folder.png" alt="compressed images save folder" width="345" height="218" /></p>
<p>We will create a new folder on our Desktop called <i>Compressed JPEG 50 Progressive</i>, descriptive so we know what it&#8217;s for.</p>
<p><i>Create</i> the folder and click <i>Save</i>.</p>
<h3>Step 5: Stop Recording Actions</h3>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/stop-recording.png" alt="stop recording the action" width="257" height="220" /></p>
<p>Click the <i>Stop</i> button on the Actions panel to stop recording.</p>
<h3>Step 6: Create a Droplet</h3>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/create-droplet.png" alt="create droplet menu" width="532" height="97" /></p>
<p>To create your Droplet, click <i>File&#8230;Automate&#8230;Create Droplet&#8230;</i></p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/save-droplet-in.png" alt="save droplet in a new folder" width="467" height="67" /></p>
<p>Choose a location to save your droplet that is easy to get to, like the Desktop. </p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/droplet-choose-action.png" alt="choose action for droplet" width="434" height="180" /></p>
<p>Choose the  <i>Set</i> and <i>Action</i> you just created for the Droplet.</p>
<p>Ensure <i>Suppress File Open Options Dialogs</i> and <i>Suppress Color Profile Warnings</i> are both checked.</p>
<p>When finished, click <i>OK</i>.</p>
<h3>Step 7: Try it!</h3>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/drag-droplet.png" alt="drag files on to the droplet" width="715" height="426" /></p>
<p>Drag the images you want to compress on to the Droplet.</p>
<p>If all goes well, the resulting optimized JPEGs will be in the Compressed folder you created in Step 4.</p>
<h3>Step 8: Review Results</h3>
<p>Let&#8217;s check out the before and after in terms of quality and file size.</p>
<p>Before at <b>234 KB</b>:</p>
<p> <img src="http://www.artzstudio.com/files/sfw-droplet/images-original/IMG_6861.jpg" alt="photo before optimization" width="640" height="480" /></p>
<p>After at <b>84 KB</b>:</p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/images-optimized/IMG_6861.jpg" alt="optimized photo at 50 quality" width="640" height="480" /></p>
<p>We had a savings of <b>150 KB</b>, 64% of the original size! The quality is also quite good. </p>
<p>If you happen to be viewing this page in Safari, you will notice that the colors are different than the original. This is because Safari supports color management, and we should address this. </p>
<p>For you non-Safari users, here is an image showing the original (top) against the optimized (bottom) version:</p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/color-grass.jpg" alt="grass needing color correction" width="366" height="177" /></p>
<p>Notice how the grass in the original is much richer than the optimized version. See what you are missing out on? This is because I don&#8217;t have Photoshop configured to automatically convert Color Profile mismatches to the Working Space.</p>
<h3>Color Correction in Photoshop</h3>
<p>To fix this, go to <i>Edit&#8230;Color Settings</i>.</p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/color-settings.png" alt="color management in photoshop" width="405" height="287" /></p>
<p>When working on the web (RGB), you always want to use your Monitor&#8217;s profile to ensure your images look the same across browsers. In my case, it is the Color LCD profile.</p>
<p>Under Color Management Policies, ensure RGB is set to <i>Convert to Working RGB</i> and all checkboxes are off. This way you won&#8217;t be bothered again.</p>
<p>Finally, run your images through the Droplet again. The colors should more closely match the originals now.</p>
<p>After Color Correction (84 KB):</p>
<p><img src="http://www.artzstudio.com/files/sfw-droplet/images-corrected/IMG_6861.jpg" alt="after color correction" width="640" height="480" /></p>
<p>Much better, as it was meant to be seen. Good thing we checked for quality!</p>
<h2>Image Compression Impact on Page Load Times</h2>
<p>Altogether, we were able to quickly optimize 30 images from 5.4 MB to 1.9 MB, a savings of 3.5 MB or 65%. Let&#8217;s see how this plays out in page load times.</p>
<p>I created two test pages, one with our <a href="http://www.artzstudio.com/files/sfw-droplet/images-original/">original photos</a> and one with our <a href="http://www.artzstudio.com/files/sfw-droplet/images-optimized/">optimized photos</a>, and ran them through <a href="http://www.webpagetest.org">Pagetest</a> to see the difference.</p>
<p><a href="http://performance.webpagetest.org:8080/result/735/">Original Photos &#8211; Speed Test Results</a></p>
<ul>
<li>Average Load Time: 33 seconds</li>
<li>Bytes In: 5439 KB</li>
</ul>
<p><a href="http://performance.webpagetest.org:8080/result/734/">Optimized Photos &#8211; Speed Test Results</a></p>
<ul>
<li>Average Load Time: 13 seconds</li>
<li>Bytes In: 1865 KB</li>
</ul>
<p><b>Savings</b></p>
<ul>
<li>Load Time: 20 seconds (60%)</li>
<li>Bytes In: 3574 KB (66%)</li>
</ul>
<p>The results are in amigo &#8211; the optimized images loaded 20 seconds faster!</p>
<h2>Final Thoughts</h2>
<p>Droplets can be a nice way of getting your optimization work done quickly, but at the cost of missing opportunities where you might save even more bytes by saving at lower a Quality &#8211; or the reverse, compromising quality at the cost of lower bytes. Always experiment and push to find a balance between low KB and image quality.</p>
<p>Did you know that <a href="http://www.webpagetest.org">Pagetest</a> has an image compression check? It tests all JPEGs to see if they are saved at the equivalent of 50% Quality in Photoshop. Use the Pagetest Optimization Report (<a href="http://performance.webpagetest.org:8080/results/735/1_optimization.txt">sample of our test here</a>) to help you spot areas of your site where you might need to share our JPEG 50 Droplet with those responsible for the heavy images.</p>
<p> By loading images faster, you are helping your users consume them faster and thus giving them more reason to stay around.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.artzstudio.com/2008/08/bulk-image-compression-with-photoshop-droplets/feed/</wfw:commentRss>
		<slash:comments>18</slash:comments>
		</item>
		<item>
		<title>Using mod_concat to Speed Up Start Render Times</title>
		<link>http://www.artzstudio.com/2008/08/using-modconcat-to-speed-up-render-start/</link>
		<comments>http://www.artzstudio.com/2008/08/using-modconcat-to-speed-up-render-start/#comments</comments>
		<pubDate>Fri, 01 Aug 2008 14:43:57 +0000</pubDate>
		<dc:creator>ArtzStudio</dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[operations]]></category>
		<category><![CDATA[server]]></category>

		<guid isPermaLink="false">http://www.artzstudio.com/?p=45</guid>
		<description><![CDATA[The most critical part of a page&#8217;s load time is the time before rendering starts. During this time, users may be tempted to bail, or try a different search result. For this reason, it is critical to optimize the &#60;head&#62; of your HTML to maximum performance, as nothing will be visible until it finishes loading [...]]]></description>
			<content:encoded><![CDATA[<p><img class="doodle" src="http://www.artzstudio.com/files/mod_concat/concat-js.png" alt="" /></p>
<p>The most critical part of a page&#8217;s load time is the time before rendering starts. During this time, users may be tempted to bail, or try a different search result. For this reason, it is critical to optimize the <code>&lt;head&gt;</code> of your HTML to maximum performance, as nothing will be visible until it finishes loading the objects inside.</p>
<p>One easy way to speed up rendering during this crucial time is to <a href="http://www.websiteoptimization.com/speed/tweak/http/">combine your CSS and JavaScript</a>, saving the performance tax associated with every outbound request. While easy in theory,  in practice this can be difficult, especially for large organizations.</p>
<p>For example, say your ad provider wants you to include their script in a separate file so they can make updates whenever they choose. So much for combining it into your site&#8217;s global JS to reduce the request, eh?</p>
<p><a href="http://code.google.com/p/modconcat/">mod_concat</a> makes combining shared libraries easy by providing a way to dynamically concatenate many files into one.</p>
<h2>See mod_concat in Action</h2>
<p>We created a couple test pages to show the benefits here. In our <a href="http://lime.holsman.net:8001/concat/without.html">first example without mod_concat</a>, we see a typical large scale website with many shared CSS and JavaScript files loaded in the <code>&lt;head&gt;</code> of the HTML. There are scripts for shared  widgets (two of them video players), ad code, and more that typically plague many major web sites.</p>
<p>You can check out the <a href="http://pagetest.patrickmeenan.com:8080/results/62Q/">Pagetest results</a> here, and check out the time to start render (green bar):</p>
<p><img src="http://www.artzstudio.com/files/mod_concat/without-concat.png" alt="pagetest waterfall with mod concat disabled" width="631" height="290" /></p>
<p>In the test page, we have 12 JavaScript files and 2 CSS files, a total of 14 HTTP requests in the <code>&lt;head&gt;</code>. I have seen worse. The green vertical bar is our Start Render time, or the time it took for the user to see something, at <strong>4 seconds!</strong></p>
<p>We can see that the time spent downloading is typically the green time, or the time to first byte. This happens on every object, simply for existing! A way to make this not happen, is to combine those files into one, larger file. Page weight (bytes) stay the same, but Requests are reduced significantly.</p>
<p>Let&#8217;s take a look at our <a href="http://pagetest.patrickmeenan.com:8080/results/62P/">Pagetest results</a> of a <a href="http://lime.holsman.net:8001/concat/with.html">second example with mod_concat enabled</a>.</p>
<p><img src="http://www.artzstudio.com/files/mod_concat/with-concat.png" alt="pagetest waterfall of music page with modconcat enables" width="614" height="130" /></p>
<p>Notice our the number of Requests went from 14 to 5, and we saved <strong>1.5 seconds</strong>! We probably could have made an even faster example by moving to just 2 requests (one for CSS and one for JS), but the speed win here is clear.</p>
<h2>How mod_concat Works</h2>
<p><a href="http://code.google.com/p/modconcat/">mod_concat</a> is a module for Apache built by <a href="http://feh.holsman.net/">Ian Holsman</a>, my manager at AOL and a <a href="http://people.apache.org/~ianh/">contributor to Apache</a>. Ian gives credit in the <a href="http://modconcat.googlecode.com/files/mod_concat.pdf">mod_concat documentation</a> to David Davis, who did it while working at Vox, and perlbal.</p>
<p>The idea is straightforward, and you can pretty much figure out how it works by viewing the <a href="http://lime.holsman.net:8001/concat/with.html">source code of our second example</a>:</p>
<p><!-- .example b {background-color:#ffc;font-weight:normal} --></p>
<pre class="example">&lt;link rel="stylesheet" type="text/css" media="screen" ←
	href="<strong>http://lemon.holsman.net:8001/cdn/??music2.css,common.css</strong>" /&gt;
&lt;script type="text/javascript"  ←
	src="<strong>http://lemon.holsman.net:8001/cdn/??music2.js,mp.js,dalai_llama.js,ratings_widget.js,widget_config.js,common.js</strong>"&gt;&lt;/script&gt;
&lt;script language="javascript" type="text/javascript" ←
	src="<strong>http://tangerine.holsman.net:8001/o/??journals_blog_this.js,adsWrapper.js,flashtag.js,feeds_subscribe.js</strong>"&gt;&lt;/script&gt;
&lt;script type="text/javascript"  ←
	src="<strong>http://orange.holsman.net:8001/digital/??dm_client_aol.js,cannae.js</strong>"&gt;&lt;/script&gt;</pre>
<p>You can see in the highlighted code above that a single request is referencing multiple files, and the server is returning the concatenated version. The URL takes the following format:</p>
<p><!-- .example b {font-weight:normal}  --></p>
<pre class="example"><strong style="background-color:#FFCCFF">http://www.yourdomain.com/</strong><strong style="background-color:#CCCCFF">optional/path/</strong><strong style="background-color:#FFFF99">??</strong><strong style="background-color:#99FF99">filename1.js,</strong><strong style="background-color:#FFCC99">directory/filename2.js,</strong><strong style="background-color:#CCFF66">filename3.js</strong></pre>
<p>Let&#8217;s break it down.</p>
<pre class="example"><strong style="background-color:#FFCCFF">http://www.yourdomain.com/</strong><strong style="background-color:#CCCCFF"></strong></pre>
<p>The first bit should be straight forward, it&#8217;s the host name.</p>
<pre class="example"><strong style="background-color:#FFCCFF">http://www.yourdomain.com/</strong><strong style="background-color:#CCCCFF">optional/path/</strong><strong style="background-color:#FFFF99"></strong></pre>
<p>Next comes the optional path to the files. This is important, because you can&#8217;t concatenate files <strong> above this directory</strong> if you include it. However, it allows you to optimize a bit so you don&#8217;t need to keep referencing the same path for files below this directory.</p>
<pre class="example"><strong style="background-color:#FFCCFF">http://www.yourdomain.com/</strong><strong style="background-color:#CCCCFF">optional/path/</strong><strong style="background-color:#FFFF99">??</strong><strong style="background-color:#99FF99"></strong></pre>
<p>The ?? then triggers the magic for the files that come next. It&#8217;s a special signal to Apache that it&#8217;s time to combine files!</p>
<pre class="example"><strong style="background-color:#FFCCFF">http://www.yourdomain.com/</strong><strong style="background-color:#CCCCFF">optional/path/</strong><strong style="background-color:#FFFF99">??</strong><strong style="background-color:#99FF99">filename1.js,</strong><strong style="background-color:#FFCC99"></strong></pre>
<p>If the file is in the current directory, you can simply include it  next, followed by a comma &#8220;,&#8221;.</p>
<pre class="example"><strong style="background-color:#FFCCFF">http://www.yourdomain.com/</strong><strong style="background-color:#CCCCFF">optional/path/</strong><strong style="background-color:#FFFF99">??</strong><strong style="background-color:#99FF99">filename1.js,</strong><strong style="background-color:#FFCC99">directory/filename2.js,</strong><strong style="background-color:#CCFF66"></strong></pre>
<p>If you need to go a bit further in the directory hierarchy, you can do that too.</p>
<pre class="example"><strong style="background-color:#FFCCFF">http://www.yourdomain.com/</strong><strong style="background-color:#CCCCFF">optional/path/</strong><strong style="background-color:#FFFF99">??</strong><strong style="background-color:#99FF99">filename1.js,</strong><strong style="background-color:#FFCC99">directory/filename2.js,</strong><strong style="background-color:#CCFF66">filename3.js</strong></pre>
<p>You can include as many files as you wish as long as they fall within the same server directory path defined early on in your <code style="background-color:#CCCCFF">optional/path/</code>.</p>
<h2>Performance and Caching Considerations</h2>
<p>mod_concat uses the Last-Modified date of the most recently modified file when it generates the concatenated version. It should honor any max-age or expires Cache Control headers you set for the path in your server or htaccess configuration.</p>
<p>If you have a far future expires or max-age header, to bust the cache  you will need to rename one of the files or directory names in the string, and then the user will download the entire concatenated version again.</p>
<p>Because mod_concat is an Apache module, performance is near instantaneous. Performance is improved further still if the server happens to be an origin point for a CDN, as it gets cached on the edge like an ordinary text file for as long as you tell it to, rarely hitting your servers.</p>
<h2>Same Idea, Different Platforms</h2>
<p>For regular folks like myself who don&#8217;t have the ability to install Apache modules with their hosting provider (cough, Lunarpages, cough), mod_concat is not the best option. The idea of concatenating JavaScript and CSS has been implemented on other platforms, and I will briefly call out those I found in my brief Googling &#8211; feel free to list more that you know of.</p>
<h3>Rakaz&#8217;s PHP Combine Solution</h3>
<p>Niels Leenheer of rakaz.nl has a nice solution for PHP. Niels writes:</p>
<blockquote><p>Take for example the following URLs:</p>
<ul>
<li>http://www.creatype.nl/javascript/prototype.js</li>
<li>http://www.creatype.nl/javascript/builder.js</li>
<li>http://www.creatype.nl/javascript/effects.js</li>
<li>http://www.creatype.nl/javascript/dragdrop.js</li>
<li>http://www.creatype.nl/javascript/slider.js</li>
</ul>
<p>You can combine all these files to a single file by simply changing the URL to:</p>
<ul>
<li>http://www.creatype.nl/javascript/prototype.js,builder.js,effects.js,dragdrop.js,slider.js</li>
</ul>
</blockquote>
<p>Niels takes advantage of Apache&#8217;s Rewrite rules as such to make the <a href="http://rakaz.nl/projects/combine/combine.phps">combine PHP script</a> transparent to the template designer:</p>
<pre>RewriteEngine On
RewriteBase /
RewriteRule ^css/(.*\.css) /combine.php?type=css&amp;files=$1
RewriteRule ^javascript/(.*\.js) /combine.php?type=javascript&amp;files=$1</pre>
<p>This is nice because it keeps the PHP script and HTML template separate from each other, just like mod_concat.</p>
<h3>Ed Elliot&#8217;s PHP Combine Solution</h3>
<p><a href="http://www.ejeliot.com/blog/72">Ed&#8217;s solution for combining CSS and JavaScript</a> is less flexible from a front-end template designer&#8217;s perspective, as you&#8217;ll need to touch PHP code to update the files being merged together. However, the advantages I see to his take on the problem are:</p>
<ul>
<li>He masks the actual file names being combined, and</li>
<li>A new version number is automatically generated to <strong>automatically bust the cache</strong></li>
</ul>
<p>For folks who don&#8217;t mind digging into PHP, the above benefits may be worth the effort. I especially like the cache-busting, as it allows me to put a far future expires header without worrying if my users will get the update or not.</p>
<h3>PHPSpeedy</h3>
<p>Finally among the PHP scripts I found is <a href="http://aciddrop.com/php-speedy/">PHPSpeedy</a>. Also available as a <a href="http://aciddrop.com/2008/03/07/php-speedy-wordpress-plugin-preview-release/">plug-in for WordPress</a>, PHPSpeedy appears to get the job done like the others, with the added benefit of <strong>automatic minification</strong>.</p>
<p>This might be useful for folks, but I&#8217;m the obfuscator type and promote that for production build processes. I&#8217;d love to see a safe obfuscator like <a href="http://developer.yahoo.com/yui/compressor/">YUICompressor</a> written in C so we could turn it into a module for Apache.</p>
<h3>Lighthttpd and mod_magnet</h3>
<p>For users of Lighthttpd, <a href="http://trac.lighttpd.net/trac/wiki/Docs:ModMagnet">mod_magnet</a> can be used to do the concatenation. It appears similar in nature to Rakaz&#8217;s solution, though I will leave it to you to dig in further as it seems to be fairly involved. This <a href="http://www.cakephp.nu/faster-page-loads-bundle-your-css-and-javascript-lighttpd-mod_magnet-lua">blog post by Christian Winther</a> should help get you started.</p>
<h3>ASP.Net Combiner Control</h3>
<p>Cozi has <a href="http://blogs.cozi.com/tech/2008/04/combining-js-an.html">developed an ASP.net control to combine multiple JS and CSS</a> into a single file, and includes a cool versioning feature much like Ed Elliot&#8217;s script. It&#8217;s very easy to use; you simply wrap the script with the control tag in the template:</p>
<pre>&lt;WebClientCode:CombinerControl ID="CombineScript" runat="server"&gt;&lt;script src=" ←
	script/third-party/jquery.js" type="text/javascript"&gt;&lt;/script&gt;&lt;script src=" ←
	script/third-party/sifr.js" type="text/javascript"&gt;&lt;/script&gt;&lt;script src=" ←
	script/third-party/soundmanager.js" type="text/javascript"&gt;&lt;/script&gt;&lt;script src=" ←
	script/cozi_date.js" type="text/javascript"&gt;&lt;/script&gt;&lt;/WebClientCode:CombinerControl&gt;</pre>
<p>It then outputs the following code at runtime:</p>
<pre>&lt;script src="../Combiner/Combiner.ashx?ext=js ←
	&amp;ver=59169b00 ←
	&amp;type=text%2fjavascript ←
	&amp;files=!script'third-party*jquery*sifr*soundmanager*!script*cozi_date*" ←
	type="text/javascript"&gt;&lt;/script&gt;</pre>
<p>The only problem I see with their approach is that since the output file has query parameters, Safari and Opera <strong>won&#8217;t honor cache control headers </strong>as it assumes it is a dynamic file. This is why simply adding ?ver=123 to bust the cache is not a good idea for those browsers.</p>
<h3>Java JSP Taglib &#8211; pack:tag</h3>
<p>Daniel Galán y Martins developed a <a href="http://www.galan.de/projects/packtag">combine solution for Java called packtag</a>. It follows in the spirit of PHPSpeedy and provides additional optimizations such as minification, GZIP, and caching.</p>
<p>It&#8217;s not obvious from the documentation what the output of the combined script looks like, but in a <a href="http://www.galan.de/files/overview640.png">flow graphic</a> it seems to include a version number, which would be cool.</p>
<p>The code  to do the combination goes right in the JSP template, and looks like this:</p>
<pre>&lt;pack:script&gt;
&lt;src&gt;/js/validation.js&lt;/src&gt;
&lt;src&gt;/js/tracking.js&lt;/src&gt;
&lt;src&gt;/js/edges.js&lt;/src&gt;
&lt;/pack:script&gt;</pre>
<p>CSS can be combined too. The syntax appears to be quite flexible:</p>
<pre>&lt;pack:style&gt;
&lt;src&gt;/main.css&lt;/src&gt;
&lt;src&gt;../logout/logout.css&lt;/src&gt;
&lt;src&gt;/css/**&lt;/src&gt;
&lt;src&gt;http://www.example.com/css/browserfixes.css&lt;/src&gt;
&lt;src&gt;/WEB-INF/css/hidden.css&lt;/src&gt;
&lt;/pack:style&gt;</pre>
<p>As you can see this idea has been implemented in many languages, some with additional innovations worth considering, so if you can&#8217;t leverage mod_concat, at least use something similar as the benefits are well worth it.</p>
<h2>Final Thoughts</h2>
<p>mod_concat is a performant, cross-language, high-scale way to build concatenation into your build process while maintaining files separately. While it lacks automatic versioning (<em>Ian, can we do this?</em>), it provides a clean way to dynamically merge JS and CSS together without touching a bit of server-side code, and it works across server-side languages.</p>
<p>One feature I&#8217;d like to see added is a  debug mode. For example, if the code throws an error it may not be apparent based on line number what file is having issues. Perhaps the filename could be included in comments at the start.</p>
<p>Remember, improving the time to start rendering the page is critical and you should focus on this first. With tools like mod_concat and the others mentioned here, there should be little excuse to implement this into your routine. Little pain, a lot to gain.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.artzstudio.com/2008/08/using-modconcat-to-speed-up-render-start/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>PNG Alpha Transparency &#8211; No Clear Winner</title>
		<link>http://www.artzstudio.com/2008/07/png-alpha-transparency-no-clear-winner/</link>
		<comments>http://www.artzstudio.com/2008/07/png-alpha-transparency-no-clear-winner/#comments</comments>
		<pubDate>Fri, 25 Jul 2008 21:58:36 +0000</pubDate>
		<dc:creator>ArtzStudio</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[compression]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[graphics]]></category>

		<guid isPermaLink="false">http://www.artzstudio.com/?p=20</guid>
		<description><![CDATA[As a long time user of Adobe Photoshop, I missed the boat on a very important discovery in image optimization &#8211; PNG-8 supports full alpha transparency! Alex Walker wrote a great article on PNG and included a nice example on creating PNG-8 images with a full alpha transparency layer with Fireworks &#8211; yes, Fireworks. Stoyan [...]]]></description>
			<content:encoded><![CDATA[<style type="text/css">img { *ie6_-: [png]transparent; }</style>
<p><img class="doodle" src="http://www.artzstudio.com/files/png-alpha-transparency/png-no-winner.png" alt="" width="193" height="169" /></p>
<p>As a long time user of Adobe Photoshop, I missed the boat on a very important discovery in image optimization &#8211; <b>PNG-8 supports full alpha transparency!</b></p>
<p>Alex Walker wrote a <a href="http://www.sitepoint.com/blogs/2007/09/18/png8-the-clear-winner/">great article on PNG</a> and included a nice example on creating PNG-8 images with a full alpha transparency layer with Fireworks &#8211; yes, <b>Fireworks</b>.  Stoyan Stefanov  points out this ability in his <a href="http://www.slideshare.net/stoyan/image-optimization-7-mistakes/">image optimization mistakes presentation</a> as well. Thanks to you both for enlightening me!</p>
<p>Before Stoyan and Alex, I like probably many other thousands of Photoshop users believed, or still believe, that PNG-8 is identical to GIF, i.e. an <i>all or nothing</i> scenario when it comes to transparent pixels. In Photoshop, we are left with the usually bloated, heavy PNG-24 format that I typically steer folks away from.</p>
<p>However, in applying PNG-8 to my favorite PNG transparency techniques, I came to a <b>different conclusion</b> than Alex and Stoyan. This article shows there is <b>no silver bullet</b> when it comes to saving out PNGs (<i>are you listening, Adobe?</i>).</p>
<h2>PNG Transparency Text Effects</h2>
<p>One cool technique we can do with alpha PNGs are <a href="http://www.webdesignerwall.com/tutorials/css-gradient-text-effect/">text effects, as detailed here by Nick La</a>. The technique involves layering an empty element containing the horizontally tiled background gradient over system text.</p>
<ul>
<li>Download the <a href="http://www.artzstudio.com/files/png-alpha-transparency/glossy-text.psd">Glossy Text  PSD</a> (189 KB)</li>
</ul>
<p>Using this CSS and HTML, we can pull off the desired effect:</p>
<pre>&lt;style type=&quot;text/css&quot;&gt;
.glossy-text
{
	font: 45px 'arial rounded mt bold';
	margin: 0;
	position: relative;
	color: #f30;
}

.glossy-text b
{
	background: url(glossy-text-photoshop.png) repeat-x;
	position: absolute;
	width: 100%;
	height: 27px;
	top: 4px;
	display: block;
	_background: none;
	_filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='glossy-text-photoshop.png', sizingMethod='scale');
}
&lt;/style&gt;

&lt;div class=&quot;example&quot;&gt;
&lt;h2 class=&quot;glossy-text&quot;&gt;&lt;b&gt;&lt;/b&gt;PNG Can Overlay Text&lt;/h2&gt;
&lt;/div&gt;</pre>
<p>Here are the results&#8230;</p>
<style type="text/css">
.glossy-text {font:45px 'arial rounded mt bold';margin:0;position:relative;color:#f30;}
.glossy-text b {background:url(http://www.artzstudio.com/files/png-alpha-transparency/glossy-text-photoshop.png) repeat-x; position:absolute;width:100%;height:27px;top:4px;display:block;_background:none;
_filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='http://www.artzstudio.com/files/png-alpha-transparency/glossy-text-photoshop.png', sizingMethod='scale');}
.example .cold {color:#06f;} 
</style>
<div class="example">
<h2 class="glossy-text"><b></b>PNG Can Overlay Text</h2>
</div>
<p>And another using the same image, with blue text&#8230;</p>
<div class="example">
<h2 class="glossy-text cold"><b></b>One Image for Every Heading!</h2>
</div>
<p>The optimization win here is clear &#8211; use <b>only 1 image</b> across multiple  headers to pull off a polished design for headings!</p>
<h3>The PNG Image</h3>
<p>I created the PNG image in Photoshop using a <i>Gradient Fill</i> layer. This gives us a fine deal of control over the gradient and the level of transparency to apply at each point. We can visually see how much transparency is applied by looking at the shade of the <i>Opacity Stops</i>. White is 0% Opacity (invisible), while black is 100% Opacity, or fully visible.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/photoshop-gradient-fill.png" width="625" height="282" alt="Photoshop Gradient Fill"></p>
<p>Now, we will head on over to our trustworthy Save For Web tool, and notice how our PNG-8 doesn&#8217;t support full alpha, as usual.</p>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<th>PNG-8</th>
<th>PNG-24</th>
</tr>
<tr>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-text-photoshop-png8.png" width="162" height="162" alt="Glossy Text Photoshop Png8">&nbsp;</td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-text-photoshop-png24.png" width="162" height="162" alt="Glossy Text Photoshop Png24">&nbsp;</td>
</tr>
</table>
<p>I will save out the PNG-24 version, which comes out to <b>156 bytes</b>, not too bad at all. Let&#8217;s see if Fireworks and its PNG-8 format can do better.</p>
<p>Now, if you are new to Fireworks (like me), the workflow is a bit different than Photoshop. Let&#8217;s start by opening up our PNG-24 image saved out of Photoshop, and switching to the export <b>Preview</b> view.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/fireworks-export-preview-view.png" width="710" height="369" alt="Fireworks Export Preview View"></p>
<p>The <i>Export Preview</i> is essentially the same thing as Photoshop&#8217;s <i>Save for Web</i> tool. Look over to the right in the above graphic at the <i>Optimize and Align</i> panel &#8211; those are the settings it&#8217;s using. Let&#8217;s update that to <b>PNG-8</b>, and the <b>Alpha Transparency</b> option.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/fireworks-optimize-align-png.png" width="710" height="369" alt="Fireworks Optimize Align Png"></p>
<p>To export the image, we can go to <i>File&#8230;Export</i>, but we can also see the expected KB in the lower-left corner of the panel, a file size of 248 bytes. After exporting, we see it was actually 238 bytes. <i>(Adobe, why can&#8217;t this be completely accurate?)</i></p>
<table cellpadding="0" cellspacing="10" border="0" style="background-color:#123b8d; color:#fff;">
<tr>
<td align="right">Photoshop PNG-24, <b>156 bytes</b></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-text-photoshop.png" alt="glossy text from photoshop"></td>
</tr>
<tr>
<td align="right">Fireworks PNG-8, <b>238 bytes</b></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-text-fireworks-png8.png" alt="glossy text from fireworks"></td>
</tr>
<tr>
<td align="right">Fireworks PNG-8 (dithered), <b>278 bytes</b></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-text-fireworks-png8-dither.png" alt="glossy text dithered from fireworks"></td>
</tr>
</table>
<p>Now, this brings me pause, because the PNG-24 I saved out of Photoshop was a mere 156 bytes &#8211; <b>37% smaller in size</b>! You can also clearly see that the Fireworks image is banding, which I would not expect to happen on such a low color image. I also dithered it, and it got larger and the pattern was still noticable.</p>
<p>It would seem that for this design purpose, the glossy text overlay, <b>Photoshop&#8217;s PNG-24 is the better choice</b>. My luck indeed!</p>
<h2>Gradient Header Backgrounds</h2>
<p>Similar in design to the text overlay image, the same finding is bound to hold true for  gradient background techniques, right? <i>Read on&#8230;</i></p>
<ul>
<li>Download the <a href="http://www.artzstudio.com/files/png-alpha-transparency/glossy-background.psd">Glossy Background  PSD</a> (40 KB)</li>
</ul>
<p>I&#8217;m going to create another<i> Gradient Fill </i>layer, and overlay a fade to white, and a fade to black over a layer filled with red. Notice I added a <i>Color Stop</i> in the center to ensure one side is white, and the other is black.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-background-gradient-fill.png" width="705" height="284" alt="Glossy Background Gradient Fill"></p>
<p>Just to be fancy, I will go ahead and add some fully opaque rounded corners using our selection tool. First, I&#8217;ll make a circle selection with a 10px diameter, giving us a 5px corner radius.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/rounded-corners-step-1.png" alt="circle selection at the top left corner of the header" width="349" height="200"></p>
<p>Next, we&#8217;ll add to the selection along the top and left hand sides of the circle selection.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/rounded-corners-step-3.png" alt="same thing on the left hand side" width="349" height="200"></p>
<p>Next, we&#8217;ll need to use <i>Select&#8230;Inverse</i> to flip the selection so we can fill it in.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/rounded-corners-step-4-inverse.png" alt="menu select inverse" width="217" height="195"></p>
<p>Using our pencil tool with at least a 5px radius, we fill in our rounded corner with white.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/rounded-corners-step-6.png" alt="filled in with white" width="349" height="200"></p>
<p>Notice that it also nicely anti-aliases against the layer below. For the other side, we&#8217;ll simply make a selection around it, and copy it over to the other side of the header.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/rounded-corners-step-7.png" alt="copying the rounded corner" width="200" height="190"></p>
<p>Move it over to the other side and do <i>Edit&#8230;Transform&#8230;Flip Horizontal</i>.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/rounded-corners-step-8-flip-horizontal.png" alt="menu flip horizontal" width="435" height="308"></p>
<p>And finally, position it in the right spot.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/rounded-corners-step-9.png" alt="position the right corner" width="180" height="127"></p>
<p>It is time to save out our PNG. Let&#8217;s go ahead and disable our layers and crop the image.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/rounded-corners-step-10.png" alt="before diabling layers" width="537" height="50"></p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/rounded-corners-step-11.png" alt="after disabling the layers" width="537" height="50"></p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/rounded-corners-step-12.png" alt="cropped" width="537" height="50"></p>
<p>And now for the PNG-24 vs. PNG-8 test.</p>
<table cellpadding="0" cellspacing="10" border="0" style="background-color:#123b8d; color:#fff;">
<tr>
<td align="right">Photoshop PNG-24, <b>399 bytes</b></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-background-photoshop-png24.png" width="518" height="32" alt="Glossy Background Photoshop Png24" /></td>
</tr>
<tr>
<td align="right">Fireworks PNG-8, <b>397 bytes</b></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-background-fireworks-png8.png" width="518" height="32" alt="Glossy Background Fireworks Png8" /></td>
</tr>
<tr>
<td align="right">Fireworks PNG-8 (dithered), <b>411 bytes</b></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-background-fireworks-png8-dither.png" width="518" height="32" alt="Glossy Background Fireworks Png8" /></td>
</tr>
</table>
<p>The tables have turned &#8211; <b>PNG-8 wins by 2 bytes</b>! However, notice again there is <b>banding</b> going on, which is bothersome to me for such a low color image. I  played with all the settings I could in Fireworks to no avail, and the dither is larger in size and looks worse, so again I will have to hand this one to Photoshop&#8217;s PNG-24.</p>
<h2>ImageOptim, a  GUI PNG  Tool</h2>
<p>And then, I thought about messing around with some programs <a href="http://www.sitepoint.com/blogs/2007/09/18/png8-the-clear-winner/">Alex mentioned in his article</a>, the programs <b>PNGQuant</b> and  <b>PNGNQ</b>. <a href="http://www.libpng.org/pub/png/apps/pngquant.html">PNGQuant</a> and <a href="http://pngnq.sourceforge.net/">PNGNQ</a> take 32-bit or 24-bit PNG images and &quot;quantize&quot; them down to 8-bit, or PNG-8. Now, what sucks is that the tools are command line, although PNGQuant has a <a href="http://jedisthlm.com/2006/03/16/manfred-a-pngquant-gui/">GUI version for Windows</a>, it is difficult to install and doesn&#8217;t help OS X fans like myself and many other designers.</p>
<p>I couldn&#8217;t get either of these to work on OS X, because I am <a href="http://www.urbandictionary.com/define.php?term=chobo">chobo</a> and didn&#8217;t want to spend more then the 30 minutes I did trying to compile the source code on OS X.</p>
<p>In my Googling for GUIs, I discovered <a href="http://pornel.net/imageoptim">ImageOptim</a>. Now, I have no clue what language the developer speaks, or what the tool does exactly, but if you want to help me translate for my readers, be my guest:</p>
<p> <img src="http://www.artzstudio.com/files/png-alpha-transparency/imageoptim-homepage.png" alt="screen shot of imageoptim homepage with non english text" width="489" height="250"></p>
<p>If I had to guess, it appears to try various PNG algorithms until it gets one that compresses the best. The tool is very user-friendly, and would fit nicely into any process as you simply drag and drop your files into its window, and it takes care of the rest.</p>
<p>To see how we fare, let&#8217;s take our full quality Photoshop PNG-24, and drop it in.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/imagoptim-drag-png24.png" alt="dragging png24 into imageoptim window" width="717" height="239"></p>
<p>Viola! ImageOptim crunched our PNG-24 down to <b>355 bytes</b>, a savings of 11%. Recall this is also smaller than our Fireworks PNG-8 (397 bytes).</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/imageoptim-results.png" alt="imageoptim results window savings of 11%" width="423" height="209"></p>
<p>The resulting file was smaller and identical to the original:</p>
<table cellpadding="0" cellspacing="10" border="0" style="background-color:#123b8d; color:#fff;">
<tr>
<td align="right">Photoshop PNG-24, <b>399 bytes</b></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-background-photoshop-png24.png" width="518" height="32" alt="Glossy Background Photoshop Png24" /></td>
</tr>
<tr>
<td align="right">Fireworks PNG-8, <b>397 bytes</b></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-background-fireworks-png8.png" width="518" height="32" alt="Glossy Background Fireworks Png8" /></td>
</tr>
<tr>
<td align="right">ImageOptim PNG, <b>355 bytes</b></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-background-imageoptim.png" width="518" height="32" alt="image optim png"></td>
</tr>
</table>
<p>Let&#8217;s go back and see if we can save anything from our Glossy Text image.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/glossy-text-results.png" alt="glossy text results showing no gain" width="423" height="209"></p>
<p>Looks like we didn&#8217;t gain anything, oh well.</p>
<p>My one beef with ImageOptim is that I have no clue what it did. Did it throw away information? What program did it use, OptiPNG, PNGCrush, AdvPNG? And why is their logo of a man getting impaled by credit cards?</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/imageoptim-logo.png" alt="imageoptim logo" width="100" height="106"></p>
<p>Okay, with that we&#8217;ll use the ImageOptim version of the PNG to complete our design, along with the following CSS and HTML, for those interested.</p>
<pre>
&lt;style type=&quot;text/css&quot;&gt;
div.glossybg
{
	width: 250px;
	font-family: verdana;
	margin-bottom: 1em;
}
div.wide
{
	width:500px;
}
div.glossybg h2
{
	color: #fff;
	height: 32px;
	font: 18px/30px verdana;
	margin: 0;
	padding-left: 12px;
	background: #f30 url(glossy-background-imageoptim.png) repeat-x;
	text-align: center;
}
div.glossybg h2 b
{
	display: block;
	background: url(glossy-background-imageoptim.png) top right; /* Tricky bit! */
	background-color: #f30;
	padding-right: 12px;
	font-weight: normal;
}
div.glossybg h2.cold, div.glossybg h2.cold b
{
	background-color: #0066b3;
}
div.glossybg p
{
	border: 2px solid #ccc;
	border-width: 0 2px 2px;
	margin: 0;
	padding: 10px;
}
&lt;/style&gt;

&lt;div class=&quot;glossybg&quot;&gt;
&lt;h2&gt;&lt;b&gt;A Red Header&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Some text inside the skinny box.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;glossybg wide&quot;&gt;
&lt;h2 class=&quot;cold&quot;&gt;&lt;b&gt;A Blue Header&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Some text inside the fat box.&lt;/p&gt;
&lt;/div&gt;
</pre>
<style type="text/css">
div.glossybg
{
	width: 250px;
	font-family: verdana;
	margin-bottom: 1em;
}
div.wide 
{
	width:500px;
}
div.glossybg h2 
{
	color: #fff;
	height: 32px;
	font: 18px/30px verdana;
	margin: 0;
	padding-left: 12px;
	background: #f30 url(http://www.artzstudio.com/files/png-alpha-transparency/glossy-background-imageoptim.png) repeat-x;
	_background-image:none
	text-align: center;
}
div.glossybg h2 b
{
	display: block;
	background: url(http://www.artzstudio.com/files/png-alpha-transparency/glossy-background-imageoptim.png) top right;
	_background-image:none;
	background-color: #f30;
	padding-right: 12px;
	font-weight: normal;
}
div.glossybg h2.cold, div.glossybg h2.cold b
{
	background-color: #0066b3;
}
div.glossybg p 
{
	border: 2px solid #ccc;
	border-width: 0 2px 2px;
	margin: 0;
	padding: 10px;
}
</style>
<div class="glossybg">
<h2><b>A Red Header</b></h2>
<p>Some text inside the skinny box.</p>
</div>
<div class="glossybg wide">
<h2 class="cold"><b>A Blue Header</b></h2>
<p>Some text inside the fat box.</p>
</div>
<p>Notice the tricky bit of CSS indicated above. We are layering the image in <code>&lt;b&gt;</code> element of the header to pull off a rounded corner on the right side, allowing us to stretch the image to various widths, a <a href="http://www.alistapart.com/articles/slidingdoors/">favorite technique</a> of mine.</p>
<h2>Transparent Image Overlays</h2>
<p>For my final experiment, I will create a banner header with a logo overlay, to demonstrate a more complex application of PNG.</p>
<ul>
<li>Download the <a href="http://www.artzstudio.com/files/png-alpha-transparency/obama-spirit.psd">Image Overlay PSD</a> (185 KB)</li>
<li>Download the <a href="http://www.artzstudio.com/files/png-alpha-transparency/glossy-banner.psd">Banner Artwork PSD</a> (1.13 MB)</li>
</ul>
<p>We&#8217;re going to create a website banner for a fan site of a well known American politician. I downloaded some free artwork from his campaign website.</p>
<p>For our transparent overlay, I will need to cut him out of his poster and <i>copy and paste</i> him on a <b>black background</b>.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-step-1.jpg" alt="obama cut out" width="470" height="250"></p>
<p>Then, we  switch our document to <i>Lab color</i> mode. </p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-step-2.png" alt="lab color mode switch" width="342" height="190"></p>
<p>This gives us a nice Lightness channel which we can use to create our overlay. In the <i>Channels</i> panel, select the <i>Lightness</i> channel, we&#8217;ll then make a selection using <i>Command + Click</i> (Windows <i>Ctrl + Click</i>).</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-step-3.png" alt="select lightness channel and make a selection from it" width="436" height="242"></p>
<p>This selects the light areas of the image, and also includes transparency information. Visually, you will see anything greater than 50% white with a marquee around it. If we wanted the dark pixels, we could simply select the inverse to obtain it. </p>
<p>This is also why we created him on a <b>black background</b>, to maintain the outline (transparent pixels are counted as white).</p>
<p>Now that I have my selection, I am going to switch back to RGB mode, create a new layer, and fill the selection in with white. I disabled the color layer to show the end result.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-step-4.png" alt="filled in the selection with white" width="436" height="242"></p>
<p>We now have a layer with white transparency information in the shape of our political figure. <i>Disable the black background</i>, and save it out as  Photoshop PNG-24. Export it through  Fireworks and ImageOptim as outlined above.</p>
<table cellpadding="0" cellspacing="10" border="0" style="background-color:#123b8d; color:#fff;">
<tr>
<td align="center">Photoshop PNG-24<br /><b>16764 bytes</b></td>
<td align="center">Fireworks PNG-8<br /><b>4542 bytes (73%)</b></td>
<td align="center">Fireworks PNG-8 (dithered)<br /><b>5265 bytes (69%)</b></td>
</tr>
<tr>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-photoshop-png24.png" width="200" height="194" alt="Obama Photoshop Png24"></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-fireworks-png8.png" width="200" height="194" alt="Obama Fireworks Png8"></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-fireworks-png8-dither.png" width="200" height="194" alt="Obama Fireworks Png8"></td>
</tr>
</table>
<p>ImageOptim was unable to gain any savings, so I didn&#8217;t include it. It seems as though ImageOptim doesn&#8217;t include a <i>quantizer</i> that will reduce the color palette like PNGQuant and PNGNQ, which is <b>what we really want</b>.</p>
<p>But  I think we are <b>finally getting somewhere</b> on the Fireworks front. Our Fireworks PNG-8 was <b>73% smaller</b> than our original PNG-24, though that banding is back (see his shoulder).</p>
<p>I exported another image out of Fireworks with a 100% dither, and think it looks much better. While a tad larger, I would recommend going with the <b>dithered Fireworks PNG-8 image</b>.</p>
<p>Let&#8217;s see how our finished product looks.</p>
<style type="text/css">
.overlay-header
{
	background-image: url(http://www.artzstudio.com/files/png-alpha-transparency/obama-spirit.jpg); 
	width: 760px;
	height: 180px;
	position:relative;
	overflow:hidden;
}
.overlay-image
{
	background-image: url(http://www.artzstudio.com/files/png-alpha-transparency/obama-fireworks-png8-dither.png);
	_filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='obama-fireworks-png8-dither.png', sizingMethod='scale');	
	position:absolute;
	right:0;
	top:20px;
	width: 200px;
	height: 194px;
}
.patriot
{
	background-image: url(http://www.artzstudio.com/files/png-alpha-transparency/obama-spirit-red.jpg); 
}
</style>
<div class="overlay-header">
<div class="overlay-image">
	</div>
</div>
<p>And if the boss said to make the background more patriotic, we can do so without affecting our transparent image.</p>
<div class="overlay-header patriot">
<div class="overlay-image">
	</div>
</div>
<p>What a catchy campaign slogan!</p>
<h2>The Drop Shadow</h2>
<p>I almost forgot the drop shadow! Well, I did forget it, I am editing this post just after I published it. Here is how a two color logo faired with a drop shadow.</p>
<table cellpadding="0" cellspacing="10" border="0" style="background-image:url(http://www.artzstudio.com/files/png-alpha-transparency/obama-drop-shadow-bg.png); color:#000;">
<tr>
<td align="center">Photoshop PNG-24<br /><br />
	<b>25232 bytes</b></td>
<td align="center">Fireworks PNG-8<br /><br />
	<b>7150 bytes (72%)</b></td>
<td align="center">Fireworks PNG-8 (dithered)<br /><br />
	<b>8615 bytes (66%)</b></td>
<td align="center">ImageOptim<br /><br />
	<b>23255 bytes (8%)</b></td>
</tr>
<tr>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-drop-shadow-photoshop.png" alt="obama logo with photoshop"></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-drop-shadow-fireworks-png8.png" alt="fireworks"></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-drop-shadow-fireworks-png8-dither.png" alt="fireworks dithered"></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-drop-shadow-imageoptim.png" alt="fireworks dithered"></td>
</tr>
</table>
<p>In this one, my vote is for the Fireworks PNG-8 dithered with a whopping savings of 66% and a decent looking shadow.</p>
<p> Let&#8217;s add a gradient to the logo, and see how that looks.</p>
<table cellpadding="0" cellspacing="10" border="0" style="background-image:url(http://www.artzstudio.com/files/png-alpha-transparency/obama-drop-shadow-bg.png); color:#000;">
<tr>
<td align="center">Photoshop PNG-24<br /><br />
	<b>42264 bytes</b></td>
<td align="center">Fireworks PNG-8<br /><br />
	<b>11049 bytes (74%)</b></td>
<td align="center">Fireworks PNG-8 (dithered)<br /><br />
	<b>13909 bytes (67%)</b></td>
</tr>
<tr>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-drop-shadow-gradient-photoshop.png" alt="obama logo with photoshop"></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-drop-shadow-gradient-fireworks.png" alt="fireworks"></td>
<td><img src="http://www.artzstudio.com/files/png-alpha-transparency/obama-drop-shadow-gradient-fireworks-dither.png" alt="fireworks dithered"></td>
</tr>
</table>
<p>At first glance, the dithered version would be my choice. However, if you look close enough, you see some odd dark specks here that just don&#8217;t belong. I tried to get rid of them in Fireworks, but my skills there are lacking. In this situation I would probably modify the source image to get the result I wanted. 1/2 point for effort, Fireworks.</p>
<h2>PNG8 Graceful Degradation in IE 6</h2>
<p>The final thing I&#8217;d like to echo with Stoyan and Alex about PNG8, is how gracefully it degrades in IE6.</p>
<p><img src="http://www.artzstudio.com/files/png-alpha-transparency/ie-6-vs-ie-7-png8-transparency.png" width="477" height="298" alt="Ie 6 Vs Ie 7 Png8 Transparency"></p>
<p>Notice that all pixels that had transparency applied (the drop shadow) disappear, allowing for a graceful degradation in IE6. For most cases, this will be entirely acceptable and allow us to <b>avoid the performance penalty and CSS hack</b> associated with AlphaImageLoader, the traditional way to enable alpha transparency support in IE 6. </p>
<p>Take a look at how wide your IE6 audience is, and make a call on if it&#8217;s worth the design/performance tradeoff to fully support it.</p>
<h2>Findings and Conclusions</h2>
<p>At the end of the day, the score was +1 for Photoshop PNG-24, +1 for ImageOptim, and +2.5 for Fireworks PNG-8 (dithered). Because of Fireworks&#8217; poor performance on the first two scenarios, there is  <i>n</i><i>o clear winner</i>.</p>
<p>With my late discovery of Fireworks PNG-8, I went into this article thinking I would have the end all answer for saving out PNGs. If you&#8217;ve been reading, you know that it&#8217;s not quite so simple. We simply need better tools; preferably, <b>one tool</b>.</p>
<p>My final thought on a designer-friendly transparent PNG workflow:</p>
<ol>
<li>Save out your transparent PNG out of Photoshop as PNG-24, and take note of the size.</li>
<li>Open the PNG-24 in Fireworks, and export it as PNG-8 with Alpha Transparency (play with the <i>dither</i> option), and take note of the size(s).</li>
<li>Run your Photoshop PNG-24 through ImageOptim, and see if you saved anything.</li>
<li>Make a final decision based on quality, size and longevity (e.g. how long will the image be around, how important is it?).</li>
</ol>
<p>There seems to be a <b>big gap on the GUI PNG tool side</b> for saving out high quality, low file size PNGs. While command line tools exist, they are not a realistic answer for designers who haven&#8217;t ever launched a terminal window, and for developers who don&#8217;t have the time or patience to compile source code.</p>
<p>I want to encourage Adobe to look at the available open source PNG tools and <b>get them into Photoshop CS4&#8242;s Save For Web</b>, where it belongs.</p>
<p>Until that happens, I am going to have to respectfully disagree with Stoyan and Alex that PNG-8 is the clear winner, as in 2 of the important use cases above, it wasn&#8217;t.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.artzstudio.com/2008/07/png-alpha-transparency-no-clear-winner/feed/</wfw:commentRss>
		<slash:comments>26</slash:comments>
		</item>
		<item>
		<title>Beating Blocking JavaScript: Asynchronous JS</title>
		<link>http://www.artzstudio.com/2008/07/beating-blocking-javascript-asynchronous-js/</link>
		<comments>http://www.artzstudio.com/2008/07/beating-blocking-javascript-asynchronous-js/#comments</comments>
		<pubDate>Wed, 23 Jul 2008 19:03:51 +0000</pubDate>
		<dc:creator>ArtzStudio</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[engineering]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[solutions]]></category>

		<guid isPermaLink="false">http://www.artzstudio.com/?p=10</guid>
		<description><![CDATA[MSN is now implementing a technique for loading JavaScript in a way that doesn&#8217;t stall the rendering of the document. They use Dynodes, a technique I also recommend for loading functionality on demand so it only consumes bandwidth when it is needed. JavaScript Blocks Everything To see the problem, view a Pagetest waterfall report of [...]]]></description>
			<content:encoded><![CDATA[<p><img class="doodle" src="http://www.artzstudio.com/files/asynchronous-js/blocking-js.png" alt="" width="193" height="118" />
<p>MSN is now implementing a technique for loading JavaScript in a way that doesn&#8217;t stall the rendering of the document. They use <a href="http://www.mindsack.com/uxe/dynodes/"><i>Dynodes</i></a>, a technique I also recommend for loading functionality on demand so it only consumes bandwidth when it is needed.</p>
<h2>JavaScript Blocks Everything</h2>
<p>To see the problem, view a <a href="http://www.webpagetest.org">Pagetest waterfall report</a> of pretty much any website today, or see this <a href="http://pagetest.patrickmeenan.com:8080/results/ZB/">recent run of AOL.com</a>. Notice that <b>1 second </b>is spent downloading and executing JavaScript, <b>one at a time</b>.</p>
<p><img src="http://www.artzstudio.com/files/asynchronous-js/aol-blocking.png" alt="waterfall graphic of aol javascript blocking rendering" width="519" height="227" /></p>
<p> One by one, folks! This is how all browsers load JavaScript (unless the <code>defer</code> attribute is used in IE) when called from your standard <a href="http://www.w3schools.com/TAGS/tag_script.asp">HTML <code>&lt;script&gt;</code> element</a>.</p>
<p>However, viewing a <a href="http://pagetest.patrickmeenan.com:8080/results/1WD/waterfall.html?run=1">waterfall report of MSN</a>, we can see that they call 3 JavaScript files (dap.js, hptr.js, and hp.js) asynchronously, and  allow the subsequent CSS files to load right away. </p>
<p><img src="http://www.artzstudio.com/files/asynchronous-js/msn-no-blocking.png" alt="waterfall graphic of msn javascript not blocking" width="508" height="205" /></p>
<p>Had their scripts loaded in the standard way, dap.js, hptr.js, and hp.js would delay the page for <b>1.4 seconds!</b></p>
<h2>Loading JavaScript Asynchronously</h2>
<p>MSN is using standard DOM functions to create and append a script element to the HTML document&#8217;s <code>&lt;head&gt;</code> element. This technique, originally coined as <a href="http://www.mindsack.com/uxe/dynodes/"><i>Dynodes</i></a>, is encapsulated in a JavaScript loader, much like the one used by JS frameworks such as Dojo. </p>
<p>We downloaded and formatted the <a href="http://www.artzstudio.com/files/asynchronous-js/msn-source-code-formatted.html">MSN.com HTML source code</a> so you can have a closer look at it. Start on line 297 which kicks off a process to a function aptly named <code>JS</code>:</p>
<pre>(function(){}).JS(Msn.Page.Track).JS(Msn.Page.Js)</pre>
<p>Note that it is passed in two URLs defined back on Lines 13-19:</p>
<pre>
Msn={
	Page:{
		SignedIn:'False',
		Js:'<b>http://stj.msn.com/br/hp/en-us/js/46/hp.js</b>',
		Track:'<b>http://stj.msn.com/br/hp/en-us/js/46/hptr.js</b>',
</pre>
<p>The <code>JS</code> function kicks off a  method that  pulls down these two scripts. Take a look at line 130 to get to the heart of this technique:</p>
<pre>var c=g.createElement("script");
c.type="text/javascript";
c.onreadystatechange=n;
c.onerror=c.onload=k;
c.src=e;
p.appendChild(c)</pre>
<p>The <code>&lt;script&gt;</code> element (<code>c</code>) is appended to the <code>&lt;head&gt;</code> element (<code>p</code>), as defined back on line 113.</p>
<p>MSN also appears to be closely monitoring the load of all the scripts called by <code>JS</code>, in case something happens during the process. Event handlers are set on <code>readystatechange</code>, <code>error</code>, and <code>load</code> to stop the polling process (fired every 100ms) once the script is finished.</p>
<p>Their code is quite obfuscated and difficult to follow, but you can look for the timeout function on line 125. There also appears to be an optional parameter to kill the process after a specified period of time.</p>
<h2>JS Loader Prototype</h2>
<p>We designed a <a href="http://www.artzstudio.com/files/asynchronous-js/js-loader.html">JS Loader Prototype</a> (not nearly as fancy as MSN&#8217;s) that illustrates the benefits of this technique, and tested behaviors in IE and Firefox.</p>
<p>In our prototype (we strongly suggest you view the source now), the code is organized into 4 sections:</p>
<ul>
<li>JavaScript #1 and #2 are called from  a <code>&lt;script&gt;</code> block in the  <code>&lt;head&gt;</code> using our JS Loader function.</li>
<li>JavaScript #3 and #4 follow next, called by the JS Loader in a <code>&lt;script&gt;</code> block in the <code>&lt;body&gt;</code>.</li>
<li>JavaScript #5 and #6 are called after some text again by the JS Loader,  from a second <code>&lt;script&gt;</code> block in the <code>&lt;body&gt;</code>.</li>
<li>Finally, #7 and #8 are loaded in the traditional, HTML <code>&lt;script&gt;</code> element fashion.</li>
</ul>
<p>In Internet Explorer, the script files queued up normally and the screen was not blocked for any period of time (until #7 and #8). Notice a <b>very short Start Render time</b> in our <a href="http://pagetest.patrickmeenan.com:8080/results/1WT/waterfall.html?run=1">test run at Pagetest</a>, and JS loading <b>as fast as HTTP1.1&#8242;s 2 connection per domain limit will allow it</b>:</p>
<p><a href="http://pagetest.patrickmeenan.com:8080/results/1WT/waterfall.html?run=1"><img src="http://www.artzstudio.com/files/asynchronous-js/js-loader-ie.png" alt="waterfall chart of js loader prototype in ie showing full asynchronous load" width="731" height="160" /></a></p>
<p>In Firefox, however, any content below the <b>next</b> inline HTML <code>&lt;script&gt;</code> section is blocked until the scripts from the <b>previous</b> inline HTML <code>&lt;script&gt;</code> section called by the loader are complete. <a href="http://www.artzstudio.com/files/asynchronous-js/js-loader.html">You have to see it to believe it!</a></p>
<p><img src="http://www.artzstudio.com/files/asynchronous-js/js-loader-ff.png" alt="waterfall chart of firefox blocking until all js has download in each script block" width="547" height="172" /></p>
<p>Notice in the above chart, within each script block both JavaScript must fully load before the next script block is allowed to start processing. The takeaway here is to include as many JS Loader calls as possible in one script block.</p>
<p>We developed a workaround for this issue by including a <b>timeout delay</b> before calling the script. This allows Firefox to continue rendering like IE and Safari, and affords the fastest possible download. <a href="http://www.artzstudio.com/files/asynchronous-js/js-loader-ff.html">See the updated JS Loader Prototype for Firefox here.</a></p>
<p><img src="http://www.artzstudio.com/files/asynchronous-js/js-loader-ff-delay.png" alt="waterfall of updated prototype showing that firefox no longer blocks other scripts" width="553" height="168" /></p>
<p>We put a longer delay on the first script (10 seconds) so you can see that Firefox no longer waits until the script block has completed before loading others. The important bit of code looks like this:</p>
<pre>
js:function(url)
{
	// If you want to call IE and Safari straight up without the delay, uncomment this.
	// (navigator.userAgent.search('Firefox')) ? js = setTimeout("artz.create('"+url+"')", 0) : artz.create(url);
	js = setTimeout("artz.create('"+url+"')", 0);
},
create:function(url)
{
	s = artz.ce('script');
	s.type = 'text/javascript';
	s.src = url;
	artz.tag('head')[0].appendChild(s);
},
</pre>
<p>You will be pleased to know that Safari renders much like IE, with the added benefits of <b>4 open socket connections!</b></p>
<p><img src="http://www.artzstudio.com/files/asynchronous-js/js-loader-safari.png" alt="waterfall shot of safari with 4 open socket connections" width="508" height="270" /></p>
<p>Hopefully the benefits to this approach are clear. With a large site like MSN faithfully using Dynodes for their scripts, it might just be the time to <b>standardize on this approach</b>.</p>
<h2>Race Condition Challenges</h2>
<p>Not so fast! (pardon the pun) There are some additional considerations we will need to think through when moving in this direction.</p>
<ul>
<li>JavaScript <b>functions</b> in the external scripts <b>may not be available </b>when inline HTML JavaScript functions call for them.</li>
<li>Along the same lines, even if Script A is called before Script B, Script B may finish and execute before Script A. </li>
<li>DOM <b>elements may not yet be available</b> in the HTML should an external script need them to hook events, access data from, etc.</li>
</ul>
<p>In tackling the above, we would first recommend following the <a href="http://en.wikipedia.org/wiki/Progressive_enhancement">Progressive Enhancement</a> approach, and work to completely eliminate inline HTML JavaScript function calls. The external scripts can latch any events needed on to links, buttons, etc., once they are ready.</p>
<p>While race conditions may still exist, a way to solve this is using the <code>setInterval</code> function to initialize your functions where you know a race condition may exist.</p>
<pre>function id(id){return document.getElementById(id)}

var oranges =
{
	init: function()
	{
		if(id('oranges'))
		{
			clearInterval(oranges_init);
			alert('We have oranges!');
			// We may proceed with orange code!
		}
	}
}

oranges_init = setInterval("oranges.init()", 100);</pre>
<p>We recommend using a period of 100ms to go easy on the CPU, and <a href="http://www.useit.com/papers/responsetime.html">still feel instantaneous to users</a>. To see this code in action, have a look at our <a href="http://www.artzstudio.com/files/asynchronous-js/id-polling.html">ID Polling Prototype</a>.</p>
<h2>Document.Wrong</h2>
<p>External scripts loaded that include the infamous<code> document.write</code> method executed by the script  will cause problems with this technique. Be sure to wrap it in a function and call it  from the HTML if you decide to head down this path. We hope that by now, you have thrown this ancient tool away.</p>
<p>Advertising vendors&#8230;this means <b>you</b>!</p>
<h2>Final Thoughts</h2>
<p>This technique has been on the back shelf for some time due to the tricky Firefox and race condition issues. </p>
<p>That said, if we are careful, and with MSN proving its value, now just may be the time to <b>adopt Dynodes as a standard JavaScript loading practice</b>.</p>
<p>Leave us a comment with your thoughts, questions, and <b>concerns</b>, and post links to implementations that leverage this!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.artzstudio.com/2008/07/beating-blocking-javascript-asynchronous-js/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Optimizing Web Performance with AOL Pagetest</title>
		<link>http://www.artzstudio.com/2008/07/optimizing-web-performance-with-aol-pagetest/</link>
		<comments>http://www.artzstudio.com/2008/07/optimizing-web-performance-with-aol-pagetest/#comments</comments>
		<pubDate>Fri, 11 Jul 2008 03:26:34 +0000</pubDate>
		<dc:creator>ArtzStudio</dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[engineering]]></category>
		<category><![CDATA[operations]]></category>
		<category><![CDATA[pagetest]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.artzstudio.com/?p=6</guid>
		<description><![CDATA[In this screencast, I walk through how to analyze your site using the reports generated by AOL Pagetest, and explain why and how to go about addressing your pressing performance issues. Come for the tool demo, and stay to learn about the anatomy of an HTTP request, the importance of CDN&#8217;s and keep-alives, and a [...]]]></description>
			<content:encoded><![CDATA[<p>In this screencast, I walk through how to analyze your site using the reports generated by <a href="http://www.webpagetest.org">AOL Pagetest</a>, and explain why and how to go about addressing your pressing performance issues. Come for the tool demo, and stay to learn about the anatomy of an HTTP request, the importance of CDN&#8217;s and keep-alives, and a handy Apache module to concatenate CSS and JavaScript. Leave me feedback so I can improve it, and enjoy!</p>
<p><object width="640" height="480"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="http://www.vimeo.com/moogaloop.swf?clip_id=1319060&amp;server=www.vimeo.com&amp;show_title=1&amp;show_byline=1&amp;show_portrait=0&amp;color=&amp;fullscreen=1" /><embed src="http://www.vimeo.com/moogaloop.swf?clip_id=1319060&amp;server=www.vimeo.com&amp;show_title=1&amp;show_byline=1&amp;show_portrait=0&amp;color=&amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="640" height="480"></embed></object></p>
]]></content:encoded>
			<wfw:commentRss>http://www.artzstudio.com/2008/07/optimizing-web-performance-with-aol-pagetest/feed/</wfw:commentRss>
		<slash:comments>18</slash:comments>
		</item>
	</channel>
</rss>

