Apple's web developers could do better. This is the first entry in a big dumb effort to implement a faster and more efficient version of Apple's new skimming functionality than their current .mac gallery implementation.
You have probably seen Apple's new . This is really cool. Surprisingly, though, the implementation is rather pedestrian. 15 seconds with firebug and it's clear - Apple sure doesn't care about doing this efficiently. Maybe they
saved a few weeks developing this, but that demo site takes almost 1 minute to load - on my PowerBook G4 over DSL. But, if they did care about speeding it up and just ran out of time, wouldn't they have at least stripped all the whitespace and comments out of their javascript? Here are the last 128 lines from the main gallery.js:
/*
require('core');
Gallery.WebWidgetPanel = Mac.FormView.extend({
isPanel: true,
outlets: ["widgetCode", "photo", "reflection", "flash", "lTitle", 'lInstructionalText', 'lCopySnippetLink', 'lDoneButton'],
widgetCode: ".widgetCode?",
widgetTag: "",
lTitle : Mac.LabelView.extend({localize:true}).outletFor(".lTitle?"),
lInstructionalText : Mac.LabelView.extend({localize:true}).outletFor(".lInstructionalText?"),
lCopySnippetLink : Mac.LabelView.extend({localize:true}).outletFor(".lCopySnippetLink?"),
lDoneButton : Mac.LabelView.extend({
localize:true,
toolTip: "_VisitorExperience.AlbumOptions.WebWidget.Done.Button.Tooltip"
}).outletFor(".lDoneButton?"),
flash: ".flash?",
getSmallest: function() {
if (Gallery.albumController.get("videoSmall"))
{
return Gallery.albumController.get("videoSmall");
}
else
if (Gallery.albumController.get("videoMedium"))
{
return Gallery.albumController.get("videoMedium");
}
else
{
return Gallery.albumController.get("videoLarge");
}
},
generateData: function ()
{
if(Gallery.detailView().isVideo && Gallery.detailView().showOnIndex) {
var height= Gallery.albumController.get("thumbImageHeight") + 16;
var width = 320;
var noScript = false;
var widgetID = 'galleryWidget_' + Math.floor(Math.random() * 100000);
var widgetAlbumTitle = Gallery.albumController.get("title");
var widgetAlbumURL = Gallery.albumController.get("url");
var widgetPosterImg = Gallery.albumController.get("medium");
var widgetMovieURL = this.getSmallest();
var styleTxt ="<style type=\"text/css\" media=\"screen\">.widget_header a { color: white ! important; display: block; text-align: center; font-size: 11px ! important; font-family: \"Helvetica Neue\", helvetica; text-decoration:none ! important; background:transparent url('http://gallery.mac.com/g/flash/gall_link_arrow.png') no-repeat scroll top right; padding-top: 3px; padding-bottom: 0px; padding-right: 16px; margin-right: 0px; height: 12px; padding-left: 5px; text-overflow: ellipsis; overflow: hidden; } .widget_header:hover a { text-decoration: underline ! important; color: white; background-image: url('http://gallery.mac.com/g/flash/gall_link_arrow_on.png'); } .widget_header { overflow: hidden; text-overflow: ellipsis; background: black url('http://gallery.mac.com/g/flash/gallery_widget_bg.png') repeat-x scroll top center; height: 20px; width: 100%; display: block; top: 0px; z-index: 4; cursor: pointer; } .widget_header a{ } .movie_div { display:block; z-index: 1; position: absolute; top: 20px; left: 0px; cursor: pointer; background-color: #000; } .movie_play_badge { width: 40px; height: 30px; position: absolute; left: 50%; margin-left: -20px; display: block; top: 50%; margin-top: -23px; background: transparent url('http://gallery.mac.com/g/flash/movie_play_bg.png') no-repeat top center; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='http://gallery.mac.com/g/flash/movie_play_bg.png'); } .qt_ctrls { background: transparent url('http://gallery.mac.com/g/flash/qt_control_left.gif') no-repeat top left; height: 16px; text-align: right; display:block; margin: 0px; }</style>";
var htmlTxt = '<div id=\"'+ widgetID+ '\" class=\"shared_movie\" style=\"text-align: center; width: ' + width + 'px; display:block; height: '+ (height + 22) + 'px; position: relative;\"><div class=\"widget_header\"><img src=\"http://gallery.mac.com/g/flash/gallery_logo.png\" width=\"21\" height=\"19\" alt=\".Mac Gallery\" style=\"float: left\" /><a title=\"' + widgetAlbumTitle + '\" href=\"'+ widgetAlbumURL + '\" target=\"_blank\">' + widgetAlbumTitle + '</a></div><div id=\"movieDiv\" class=\"movie_div\" ><a id=\"movieLink\" class=\"movie_link\" href=\"'+ widgetAlbumURL + '/' + widgetMovieURL + '\" target=\"_blank\"><img id=\"playBadge\" class=\"movie_play_badge\" src=\"http://gallery.mac.com/g/flash/movie_play_badge.png\" border=\"0\" width=\"40\" height=\"30\" alt=\"Play Movie\" /><img class=\"movie_poster\" id=\"moviePoster\" src=\"' + widgetPosterImg + '" width=\"320\" alt=\"\" border=\"0\" style=\"padding: 0px; margin: 0px\"/></a><div class=\"qt_ctrls\" id=\"qtCtrls\"><img src=\"http://gallery.mac.com/g/flash/qt_ctrl_right.gif\" width=\"57\" height=\"16\" alt=\"\" ></div></div></div>';
var scriptTxt = "<script src=\"http://gallery.mac.com/g/flash/gallery_moviewidget.js\" type=\"text/javascript\" charset=\"utf-8\"></script><script type=\"text/javascript\" charset=\"utf-8\"> initWidget('"+ widgetAlbumURL +"','" + widgetMovieURL + "', 320, "+ height +", '"+ widgetID +"', true);</script>";
var source = Gallery.albumController.get("path");
this.flash.innerHTML = styleTxt + htmlTxt;
this.widgetTag = styleTxt + htmlTxt + scriptTxt;
this.widgetCode.value = this.widgetTag;
var links = this.flash.getElementsByTagName('a');
for (i=0; i<links.length; i++){
links[i].removeAttribute('href');
links[i].setStyle({cursor: 'default'});
}
} else {
this.widgetTag = this.createSnippetCode();
this.flash.innerHTML = this.createSnippetCode(true);
this.widgetCode.value = this.createSnippetCode();
}
},
createSnippetCode: function(previewBool) {
var height = 260;
var width = 320;
var source = Gallery.albumController.get("path");
if (previewBool) {
previewTxt = 'preview=true&';
} else {
previewTxt = '';
}
var flashvars = previewTxt + 'feed=' + source + '?webdav-method=truthget&depth=infinity&feedfmt=atom&widgetWidth=' + width + '&widgetHeight=' + height;
var widgetTag;
widgetTag = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="'+width+'" height="'+height+'" id="photowidget" align="middle">';
widgetTag += '<param name="allowScriptAccess" value="always" />';
widgetTag += '<param name="movie" value="http://' + HOST + '/g/flash/photowidget.swf?def" />';
widgetTag += '<param name="quality" value="high" />';
widgetTag += '<param name="bgcolor" value="#000000" />';
widgetTag += '<param name="wmode" value="opaque" />';
widgetTag += '<param name="flashVars" value="' + flashvars + '" />';
widgetTag += '<embed src="http://' + HOST + '/g/flash/photowidget.swf?abab" quality="high" bgcolor="#000000" width="' + width + '" height="' + height + '" wmode="opaque" name="photowidget" align="middle" allowScriptAccess="always" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashVars="' + flashvars + '" />';
widgetTag += '</object>';
return widgetTag;
},
copyToClipboard: function()
{
var flashcopier = 'flashcopier';
if(!document.getElementById(flashcopier)) {
var divholder = document.createElement('div');
divholder.id = flashcopier;
document.body.appendChild(divholder);
}
document.getElementById(flashcopier).innerHTML = '';
var divinfo = '<embed src="/g/flash/_clipboard.swf" FlashVars="clipboard='+escape(this.widgetTag)+'" width="0" height="0" type="application/x-shockwave-flash"></embed>';
document.getElementById(flashcopier).innerHTML = divinfo;
new Effect.Highlight(this.widgetCode, {duration: .5, startcolor: "#70b0ff", endcolor: "#ffffff" });
},
commitForm: function() {
this.set("isVisible", false);
this.style.display = '';
}
});
*/
That's right, it's one big comment!
Next up, images. It requests a lot of them. A LOT of them. Here is a screenshot from firebug:
That's right, 137 images. Most of these (let's say 100) are 160x160 images for making the skim gallery effect. Looking into the gallery code, the image is swapped by changing the src on an image. This approach has some drawbacks:
- You're eating your net latency once for each image (divided by ~4, as the browser will request more than one at a time). Even if you're latency is only 100ms, you've got 100 images / 4 threads * 100ms = 2500ms. So, two and a half seconds eaten up in overhead. This gets worse the farther away you are from the server (light is only so fast, after all).
- You can do better than changing the source
A common web developer tool is to use one bigger image as a background to a smaller element and shift the position of the background to achieve some effect. Example: . We can use this here. Instead of 20 images for each gallery, create one image per gallery that's 160px x 3200px, then shift it to display whatever image we want. So, for 5 galleries, you're downloading 5 images instead of 100. This will cut out a lot of latency.
But is it faster? Well, how about an example. I took 5 gallery images and constructed a
film strip image. I built a test page/script - same 5 image - one method uses the filmstrip and background offset, the other uses an image src.
Here is the example. The top image uses the film strip approach. The bottom uses Apple's mechinism.
I used firebug's profiler to compare the performance:
big dumb dev -
.23ms.41ms average
apple - 2.1ms average
So, better than 4x improvement.
And this is where part 1 ends.
Next up -
part 2 : how to build a film strip image.