Yesterday I decided to improve the way my website loads YouTube videos by only embedding them when a user requests them. Sometimes a page could have as many as 30 videos on, and this would take a long time to load.
This is the first time I've attempted a "lazy loading" method of anything, and I figured it would be well worth asking:
What can I do to improve on this?
How can I make it a bit more graceful?
Does this completely miss the point of deferred loading?
JSFiddle.
Ignore the styling as that's irrelevant here.
The way this works is by first placing an anchor on the page containing the video's ID:
<a href="#" data-video="FzRH3iTQPrk" class="youtube-video">
The jQuery behind then loops through every a.youtube-video
and creates a transparent span with the video's thumbnail as its background:
$('a.youtube-video').each(function() {
var videoId = $(this).attr('data-video');
var videoThumbnail = "http://img.youtube.com/vi/" + videoId + "/0.jpg";
var videoBackground = $('<span class="youtube-thumbnail"></span>');
videoBackground.css({
background:"#fff url('"+videoThumbnail+"') no-repeat"
})
...
It then modifies the styling of the anchor tag (this is done here to prevent affecting browsers with JavaScript disabled):
$(this).css({
height:315,
width:460,
position:"relative",
display:"block",
textAlign:"center",
color:"#fff",
fontSize:26
});
It then finishes up the loop by adding the span to the anchor:
$(this).text('Click to load video');
$(this).append(videoBackground);
});
The loading of the embedded YouTube video object occurs on the anchor click:
$('a.youtube-video').click(function(e) {
e.preventDefault();
var videoId = $(this).attr('data-video');
var videoThumbnail = "http://img.youtube.com/vi/" + videoId + "/0.jpg";
var videoEmbed = $('<object> ... </object>');
...
This finishes up by adding the embed code to the anchor's parent and removing the anchor:
$(this).parent().append(videoEmbed);
$(this).hide();
});
You should set the href so that people know what they're going to be loading. That can be done on the a element when the page is generated, or in JS. It has to be on the element if you want your page to work for people who have Javascript disabled.
var url = "http://www.youtube.com/watch?v=" + videoId;
$(this).attr('href', url);
You should also tell people what will happen if they click either by setting a title:
$(this).attr('title', "Click to play video");
Or by creating an overlay like the ones at the SMH which makes it more obvious that clicking on the image will play a video. Or both.
Also you shouldn't break opening things in new window when people hold down ctrl or command key. I updated the fiddle but basically in your click event you need:
var isCtrlPressed = e.ctrlKey || e.metaKey;
if (isCtrlPressed == true){
return;
}
I know you said ignore the style, but I prefer it if a placeholder has a background color that is obviously different from the background of the page. If a page is slow loading, I can tell that something is going to load there, rather than it just being a blank bit of page.
Does this completely miss the point of deferred loading?
Not entirely but it is slightly pointless in this case. Deferred loading is usually used when the thing that is being loaded is very complex (e.g. a massive table) and so would add greatly to the page size, which would make the initial render of the page be a long time after the user followed the link.
Although the youtube videos may be slow to load, they shouldn't actually block the rest of the page from loading and rendering in the meantime so deferring loading the videos only gives you a small benefit for quite a bit of work.
I guess you should measure how fast someone can view a page, scroll to the bottom and then click to play the last video. If it's faster with the deferred loading, because the other videos flash player isn't loaded then it could still be worth it.
There is a jsfiddle of the below code here. (The player doesn't show up correctly, I think jsfiddle doesn't work well with the "embed method". I know it works on jsfiddle using the iframe method but you mentioned you wanted to avoid iframes).
One big improvement, as already mentioned, is to use swfobject to generate the player embed. When using this method, you must create a placeholder for the player element. The placeholder will be removed and replaced with the embed when the link is clicked.
I highly recommend creating a parent div for the link and the player. In order to prevent resizing of elements when the embed is generating, you should also explicitly set the width/height of the container element.
I think this is nice use of deferred loading. Although flash players don't block on load, they still use a lot of resources and I would appreciate a page that initially presents as a thumbnail set.
Markup
<script src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js">
</script>
<div class="container">
<div id="placeholder_FzRH3iTQPrk"></div>
<a href="#" data-video="FzRH3iTQPrk" class="youtube-video"></a>
</div>
<div class="container">
<div id="placeholder_go43XeW6Wg4"></div>
<a href="#" data-video="go43XeW6Wg4" class="youtube-video"></a>
</div>
JavaScript
$(document).ready(function() {
$('a.youtube-video').each(function() {
var videoId = $(this).attr('data-video');
var videoThumbnail = "http://img.youtube.com/vi/" + videoId + "/0.jpg";
var videoBackground = $('<span class="youtube-thumbnail"></span>');
videoBackground.css({
background:"#fff url('"+videoThumbnail+"') no-repeat"
});
// also set the parent container size to prevent flicker
$(this).add($(this).parent()).css({
height:315,
width:460,
position:"relative",
display:"block",
textAlign:"center",
color:"#fff",
fontSize:26
});
$(this).text('Click to load video');
$(this).append(videoBackground);
});
$('a.youtube-video').click(function(e) {
e.preventDefault();
var videoId = $(this).attr('data-video');
var params = { allowScriptAccess: "always", allowFullScreen: "true" };
var atts = { id: 'player_'+videoId };
$(this).hide();
swfobject.embedSWF(
"http://www.youtube.com/v/"+videoId+"?enablejsapi=1&playerapiid=ytplayer&version=3",
'placeholder_'+videoId, "460", "315", "8", null, null, params, atts);
});
});