Cross-fading disjointed image rollover
Last update: 16th October 2008
This advanced technique uses CSS-sprites and JavaScript to change an image which is not directly associated to the link. It uses accessible and clean mark-up, with unobtrusive JavaScript, and a little CSS. Content, presentation and behaviour are completely separated. The JavaScript acts on the XHTML using the style sheet.
The JavaScript is configurable and completely reusable by changing four variables.
Demonstration
This example was developed for the real-world and may be seen on the Tesco Direct website. The brief had specific requirements:
- Accessible 100% unobtrusive scripting.
- First button link locked on. Done via CSS, very easily changed.
- Rollover action on the other button links. All via CSS.
- Disjointed image locks-on until another button link is hovered over. I'll make this configurable at a later date.
A bare-bones example is available as is a complete set of files which includes minified [1.78KB] versions and graphics. Or just look at the commented JavaScript.
How it works
Using CSS-sprites there are only three images in total:
"top.jpg" - the default image which loads from the style sheet.
"buttons.jpg" - The four buttons, with rollovers, in one sprite. Buttons in order one above the next with the rollover version to the right. All controlled via the CSS.
"top.sprite.jpg" - The final image is a CSS-sprite containing all four of the disjointed rollover images. Presented in order, one above the next. It is preloaded by the JavaScript. The name and location of the sprite is stripped from the default image attributes. It just has ".sprite" added. So ensure it is named accordingly and placed in the same folder.
Once the page has fully loaded the JavaScript kicks in and pre fetches the disjointed rollover image. The secondary images are only displayed once completely loaded thus preventing white, no-image flashes on slow connections. Well IE anyway, Firefox seems to have issues when changing background images.
For fading purposes an overlay is unobtrusively added to the disjointed image container. Both overlay and container have their background-image changed to "top.sprite.jpg" ready for manipulation.
On rolling over a button, the overlay sprite is repositioned and faded in over the container image. The containers' sprite is then repositioned in background, and finally the overlay is set transparent ready for the next rollover.
Semantic XHTML sir?
Very simple semantic mark-up:
<div id="sectionTop" class="top1">
<ul>
<li><a id="top1" href="#1">link 1 accessible text</a></li>
<li><a id="top2" href="#2">link 2 accessible text</a></li>
<li><a id="top3" href="#3">link 3 accessible text</a></li>
<li><a id="top4" href="#4">link 4 accessible text</a></li>
</ul>
</div>
Required:
- All id's must be unique.
- Id name of the disjointed image container is required by, but changeable in, the JavaScript function call.
- The disjointed image container also requires the className of the default link id. Specifically for its href property, do if clicked before a rollover it has somewhere to go.
- Id names on the links are required by both CSS and JavaScript. The names may be changed but the change must be reflected in the CSS which controls the sprite position. The JavaScript doesn't care what they are called so long as they're present.
- For accessibility reasons the links should fully describe the destination.
It is not a requirement for the example navigation list to be within the disjointed image container. Consequently, with adjustments, this would also work:
<div id="disjointedImageDiv" class="firstLink"></div>
<ul id="navigation">
<li><a id="firstLink" href="#">1</a></li>
<li><a id="secondLink" href="#">2</a></li>
<li><a id="thirdLink" href="#">3</a></li>
<li><a id="fourthLink" href="#">4</a></li>
</ul>
A little style goes a long way
Because of the use of CSS sprites the style sheet is quite complex.
Global reset
#sectionTop * {
margin:0;
padding:0;
border:0 solid;
list-style:none
}
The disjointed-image: layout and positioning
This is the business end of the CSS. The CSS sprite, name and location is obtained from #sectionTop background-image
#sectionTop {
background-image:url(top.jpg);
background-repeat:no-repeat
position:relative;
width:476px;
height:120px;
overflow:hidden;
margin:1em auto;
}
/* Disjointed image positioning (CSS-sprite) */
.top1 {background-position:0 0}
.top2 {background-position:0 -122px}
.top3 {background-position:0 -244px}
.top4 {background-position:0 -366px}
The link buttons
Not exactly part of the tutorial, just added for completeness
/* Buttons layout */
#sectionTop ul {
position:absolute;
width:100px;
height:120px;
top:0;
right:0
}
#sectionTop a {
background-image:url(buttons.jpg);
display:block;
width:100px;
height:30px;
text-indent:-200em
}
/* Button graphics positioning (CSS-sprite) */
a#top1 {background-position:100px 0} /* locked-on as per brief */
a#top2 {background-position:0 -30px}
a#top3 {background-position:0 -60px}
a#top4 {background-position:0 -90px}
a#top1:hover,
a#top1:focus {background-position:100px 0}
a#top2:hover,
a#top2:focus {background-position:100px -30px}
a#top3:hover,
a#top3:focus {background-position:100px -60px}
a#top4:hover,
a#top4:focus {background-position:100px -90px}
The overlay required by the JavaScript
Used by the JavaScript to cross fade the image sprites.
#overlay {
position:absolute;
width:371px;
height:127px;
top:0;
left:0;
cursor:pointer
}
It's all in the scripting
Going to start with the set-up for the example then delve into the coding depths. With that exception each section is a continuation of the previous, split for clarity.
Set-up
function setup(){
/*
id of disjointed object,
id of object containing the links,
id of the overlay to be created,
extension of disjointed image sprite
*/
disjointedRollover('sectionTop','sectionTop',"overlay",".sprite")
}
// test DOM functions are supported and run setup on page load
if (isDom()){
addLoadEvent(setup);
}
Standard functions
These are part of my standard function set:
/* author: Simon Willisons - http://simon.incutio.com/archive/2004/05/26/addLoadEvent */
function addLoadEvent(f){var o=window.onload;if(typeof window.onload!='function'){window.onload=f}else{window.onload=function(){o();f()}}}
// standard functions
function $id(id){return(document.getElementById(id)?document.getElementById(id):false)}
function idExists(id){return($id(id)?true:false)}
function isDom(){return (document.getElementById&&document.getElementsByTagName&&document.createElement)?true:false}
- addLoadEvent() - Starts the JavaScript after the page loads.
- $id(id) - Short form for document.getElementById('id').
- idExists(id) - Checks an id exists.
- isDom() - Tests the DOM functions used are supported.
Set opacity function
A cross-browser, cross platform solution for setting the opacity of an id'd object:
function setOpacity(id,opacity){
var obj=$id(id).style;
obj.opacity=opacity/100;
obj.MozOpacity=opacity/100;
obj.KhtmlOpacity=opacity/100;
obj.filter="alpha(opacity="+opacity+")";
}
Get a CSS property value
Very useful for pulling a property value from a style sheet.
Not 100% for grabbing values of all properties. Based on "Retrieving CSS styles via JavaScript" by Steffen Rusitschka. See his article for more info.
/* author: Steffen Rusitschka - http://www.ruzee.com/blog/2006/07/retrieving-css-styles-via-javascript/ */
function hyphenToCamel(s){for(var exp=/-([a-z])/;exp.test(s);s=s.replace(exp,RegExp.$1.toUpperCase()));return s;};
function getStyleProperty(id,property){
// note this function is not 100% generic for all CSS properties
var obj=$id(id),value='';
if(window.getComputedStyle){
value=window.getComputedStyle(obj,null).getPropertyValue(property);
}else{
if(obj.currentStyle){
value=obj.currentStyle[hyphenToCamel(property)];
}
}
return value;
}
The main routine
Parameters:
- id - 'sectionTop' in the example. The id of the disjointed-image area.
- linkContainer - The id of the links block, 'sectionTop' in the example.
- overlayID - 'overlay' in the example.
- spriteExt - '.sprite' in the example.
function disjointedRollover(id,linkContainer,overlayID,spriteExt){
if (idExists(id)){
// get original background-image name
var newImg=getStyleProperty(id,'background-image');
//remove text styling "url()"
newImg=newImg.replace('url(','').replace(')','');
// remove IEs ""
newImg=newImg.replace(/"/g,'');
// replace .extension with .sprite.extension
var ext=newImg.substring(newImg.lastIndexOf("."));
newImg=newImg.replace(ext,spriteExt+ext);
// preload rollover image and attach variables
var img=new Image();
img.overlayID=overlayID;
img.spriteExt=spriteExt;
img.disjointID=id;
When the disjointed-image sprite has loaded
Don't do anything until the sprite has loaded but then:
img.onload=function(){
// image loaded so replace background image
$id(id).style.backgroundImage="url("+img.src+")";
// create an overlay span for fading
var sp=document.createElement('span');
sp.id=img.overlayID;
sp.className=$id(id).className;
sp.style.backgroundImage="url("+img.src+")";
// main graphic acts as a link too
sp.onclick=function(){
window.location=$id(this.className).href;
}
// add overlay to disjoint-image block 'sectionTop'
$id(id).appendChild(sp);
The button mouse actions
Attach mouse actions to the links in the links container. The fade is done from here too. Note no fade-out just fade-ins. The timing of which is not variable, deemed beyond brief. Currently it has five opacity steps [0, 25%, 50%, 75%, 100%] timed at [0, 250ms, 500ms, 750ms, 1s]. I leave amendments to you.
var rollover=function(){
var obj=$id(img.overlayID)
// set overlay initial opacity and position (via class)
setOpacity(img.overlayID,0);
obj.className=this.id;
// fade in overlay: adjust 101 for fade steps, currently there's 4.
for(var i=25;i<101;i+=25){
setTimeout("setOpacity('"+img.overlayID+"',"+i+")",i*2);
}
// set bg img position
setTimeout("$id('"+img.disjointID+"').className='"+this.id+"'",i*2);
// switch off overlay
setTimeout("setOpacity('"+img.overlayID+"',0)",i*2);
}
// add mouseover actions to links
var liAs=$id(linkContainer).getElementsByTagName('a');
for (var i=0;i<liAs.length;i++){
liAs[i].onmouseover=rollover;
liAs[i].onfocus=rollover;
}
}
img.src = newImg;
}
}
Update history
3rd October 2008 - Test for DOM functions added and the initial on-click repaired.
16th October 2008 - Reworked for keyboard only and added missing graphic to zip file. Thanks Patrick H. Lauke at splintered for raising the issue.
Any issues or ideas please email me mike dot foskett at this address
Going further
The action of locked-on is set in the code. I'll try to allow optional actions as time allows.
Social links and email client: