Helpful Information
 
 
Category: Coding tips & tutorials threads
xBrowser Fading Images

Here is a relatively simple function I've come up with for fading images cross browser:


function fade(el, way, op, opinc, speed){
/* Cross Browser Fader © 2007 John Davenport Scheuer
This comment must remain for Legal Use */
clearTimeout(el.timer);
if(!fade.t) fade.t=function(o){return typeof el.style[o]=='string'},
fade.ie=el.filters&&el.filters[0], fade.ie6=fade.ie&&typeof el.filters[0].opacity=='number',
fade.slctr=fade.t('opacity')||fade.ie6? 'opacity' : fade.t('MozOpacity')? 'MozOpacity' : fade.t('KhtmlOpacity')? 'KhtmlOpacity' : null;
var optype=fade.ie6? el.filters[0] : el.style,
waym=way=='in'? 1 : -1, speed=speed? speed*1 : 30, opinc=opinc&&opinc>=1? opinc*(fade.ie? 1 : .01) : opinc? opinc : fade.ie? 5 : .05,
op=op&&fade.ie? op*1 : op&&op*1>=1? Math.min(op*.01, .99) : op? op : waym>0&&fade.ie? 100 : waym>0? .99 : 0;
if(fade.slctr&&Math.abs(op*1-optype[fade.slctr]*1 + opinc*waym)<opinc)
optype[fade.slctr]=op;
else if(fade.slctr)
optype[fade.slctr]=fade.ie6? optype[fade.slctr]*1 + opinc*waym : Math.min(optype[fade.slctr]*1 + opinc*waym, .99);
else if (fade.ie&&Math.abs(op*1 - optype.filter.replace(/\D/g,'')*1 + opinc*waym)<opinc)
optype.filter='alpha(opacity='+op+')';
else if (fade.ie)
optype.filter='alpha(opacity='+[optype.filter.replace(/\D/g,'')*1 + opinc*waym]+')';
else
return;
if(optype[fade.slctr]&&optype[fade.slctr]*waym<op*waym||!fade.ie6&&fade.ie&&optype.filter.replace(/\D/g,'')*waym<op*waym)
el.timer=setTimeout(function(){fade(el, way, op, opinc, speed)}, speed);
}

It does require that you set the relevant style for the image either in the head or external stylesheet (if the beginning opacity is 0), or inline if the beginning opacity is other than 0 (beginning opacity of 0 may also be set inline, if desired). Here is an example for starting with 0 opacity in the head:


<style type="text/css">
#frond img {
opacity:0;
-moz-opacity:0;
-khtml-opacity:0;
filter:progid:DXImageTransform.Microsoft.alpha(opacity=0);
}
</style>

Here is an example for setting the beginning style inline along with example events using the fade function onmouseover/out for an image starting at 50% opacity:


<img style="opacity:.50;-moz-opacity:.50;-khtml-opacity:.50;filter:progid:DXImageTransform.Microsoft.alpha(opacity=50);"
onmouseover="fade(this, 'in');" onmouseout="fade(this, 'out',50);" src="some.jpg" alt="">

The function itself is versatile and can be used rather simply as shown above or more complexly:


onmouseover="fade(this, 'in',100,2,50);"
onmouseout="fade(this, 'out',0,2,50);"

The parameters are:

element, 'inORout', target_opacity, opacity_increment, speed

Only the element and 'inOrout' are required. The target opacity defaults to 100 (100%) if not listed and the 'inOrout' is 'in'. If it is 'out' the default is 0. The default increment is 5 degrees of opacity and the default speed is 30 milliseconds between each increment.

This will work in browsers supporting alpha(opacity), opacity, -moz-opacity, or -khtml-opacity, and can be reused as much as required for various images, or the same image on a page. In non-supporting browsers it will do nothing, and since opacity isn't supported in those browsers, the image will simply appear normally.

The function can be used with other elements, except for an unfortunate bug in IE 7 which causes text in any element with any filter to lose its anti-aliasing quality. This used to be able to be overcome (in earlier IE versions) by giving the text a background color, but this is not currently the case in IE 7. Hopefully, MS will fix this bug soon.

Demo:

http://home.comcast.net/~jscheuer1/side/files/fade_advanced.htm

Argh, who killed readability? :p The code is pretty nice, but it's virtually unreadable. You might also consider using a closure to define static variables for that function rather than storing them as properties of the function... I find this makes for neater code and eliminates all that if(/* first time we've run */) cruft.
var fade = (function() {
var dEl = document.documentElement.style,
getOp, setOp, changeOp,
// Get the CSS property by which we should modify the opacity. "filter" is treated specially.
meth = typeof dEl['opacity'] !== "undefined"
? "opacity"
: typeof dEl['MozOpacity'] !== "undefined"
? "MozOpacity"
: typeof dEl['KhtmlOpacity'] !== "undefined"
? "KhtmlOpacity"
: typeof dEl['filter'] !== "undefined"
? "filter"
: null;

// If we don't have opacity support, don't do anything when this function is called.
if(!meth) return function() {};

// If IE-style filters, perform the appropriate special treatment.
if(meth === "filter") {
getOp = function(el) {
// If the element doesn't have the alpha filter applied to it already, add it.
if(!el.filters['DXImageTransform.Microsoft.alpha'])
el.style.filter += " progid:DXImageTransform.Microsoft.alpha(opacity=100)";
// Return the value of its opacity property.
return el.filters['DXImageTransform.Microsoft.alpha'].opacity;
};
setOp = function(el, v) {
// We can set alpha all we like, but this has no effect unless the element has layout.
if(!el.style.hasLayout)
// IE 5.5+, preferable since it's less likely to interfere with user style
if(typeof el.style.zoom !== "undefined")
el.style.zoom = 1;
// IE5.0
else
el.style.display = "inline-block";
// Now actually do the setting
el.filters['DXImageTransform.Microsoft.alpha'].opacity = parseInt(v);
};
} else {
// Otherwise, it's much simpler: just get/set the appropriate style property.
getOp = function(el) {
if(isNaN(parseFloat(el.style[meth])))
el.style[meth] = 1;
return Math.round((el.style[meth] || 0) * 100);
};
setOp = function(el, v) {
el.style[meth] = v / 100;
};
}

// Add delta to el's opacity, within the bounds of 0/100.
changeOp = function(el, delta) {
var currOp = getOp(el);
if(currOp + delta > 100)
setOp(el, 100);
else if(currOp + delta < 0)
setOp(el, 0);
else {
setOp(el, currOp + delta);
return true;
}
// Return a boolean value indicating if we've hit a bound.
return false;
};

// Prevent memory leak in IE
dEl = null;

return function(el, args) {
// Set some default values if they don't exist.
// Also mark the arguments as sane, so we don't
// have to do this checking again.
if(!args) args = {};
if(!args.notfirst) {
if(getOp(el) === 0) {
args.delta = args.delta || 5;
args.target = (!args.target && args.target !== 0 ? 99 : args.target);
} else {
args.delta = args.delta || -5;
args.target = Math.max(args.target || 0, 99);
}
args.initial = args.initial || getOp(el);
args.speed = args.speed || 100;
args.notfirst = true;

// Initialise the element to its initial opacity.
setOp(el, args.initial);
}
var op = getOp(el);
// If we haven't passed the target or hit any bounds of sanity...
if(
(
(args.delta > 0 && op < args.target)
|| (args.delta < 0 && op > args.target)
)
&& changeOp(el, args.delta)
)
// ... fade again.
setTimeout(function() { fade(el, args); }, args.speed);
else if(typeof args.callback === "function")
// If we've finished, call the callback.
args.callback(el, args);
};
})();

// EDIT: This code has now been tested and officially works.

Twey, I hardly think that this is the section for untested code. I wasn't going for readability, obviously. I did consider making the 'once only' portions of the script external to the main body of the function, but two things prevented me from taking that route (as I normally would with any script of this sort) in this case:


Mainly, most only work after the markup is parsed.
Less important - I wanted (for user friendliness to beginners) a single function if possible.


Now, one thing that I am not happy about with the code is that it requires (in cases where the initial opacity is other than 0) inline style. This goes against the grain of sound design principals and also prevents one from shielding incompatible browsers from the IE specific filter.

To that end I have developed an alternate set up with an added function and code in the main function to utilize the added function, which allows one to preset an image's opacity if it is to initially be other than 0. It still must also be styled (now in the head or in an external stylesheet is fine) and it must have a unique id. Here is the alternate coding:


//preset image's initial opacity if other than 0:
var mona=50;

function fade(el, way, op, opinc, speed){
/* Cross Browser Fader © 2007 John Davenport Scheuer
This comment must remain for Legal Use */
clearTimeout(el.timer);
if(!fade.t) fade.t=function(o){return typeof el.style[o]=='string'},
fade.ie=el.filters&&el.filters[0], fade.ie6=fade.ie&&typeof el.filters[0].opacity=='number',
fade.slctr=fade.t('opacity')||fade.ie6? 'opacity' : fade.t('MozOpacity')? 'MozOpacity' : fade.t('KhtmlOpacity')? 'KhtmlOpacity' : null;
var optype=fade.ie6? el.filters[0] : el.style,
waym=way=='in'? 1 : -1, speed=speed? speed*1 : 30, opinc=opinc&&opinc>=1? opinc*(fade.ie? 1 : .01) : opinc? opinc : fade.ie? 5 : .05,
op=op&&fade.ie? op*1 : op&&op*1>=1? Math.min(op*.01, .99) : op? op : waym>0&&fade.ie? 100 : waym>0? .99 : 0;
if(!fade.ie6&&!el.yet&&fade.preset(el)&&fade.slctr)
optype[fade.slctr]=fade.ie? fade.preset(el): Math.min(fade.preset(el)*.01, .99);
else if(!fade.ie6&&!el.yet&&fade.preset(el)&&fade.ie)
optype.filter='alpha(opacity='+fade.preset(el)+')';
el.yet=true;
if(fade.slctr&&Math.abs(op*1-optype[fade.slctr]*1 + opinc*waym)<opinc)
optype[fade.slctr]=op;
else if(fade.slctr)
optype[fade.slctr]=fade.ie6? optype[fade.slctr]*1 + opinc*waym : Math.min(optype[fade.slctr]*1 + opinc*waym, .99);
else if (fade.ie&&Math.abs(op*1 - optype.filter.replace(/\D/g,'')*1 + opinc*waym)<opinc)
optype.filter='alpha(opacity='+op+')';
else if (fade.ie)
optype.filter='alpha(opacity='+[optype.filter.replace(/\D/g,'')*1 + opinc*waym]+')';
else
return;
if(optype[fade.slctr]&&optype[fade.slctr]*waym<op*waym||!fade.ie6&&fade.ie&&optype.filter.replace(/\D/g,'')*waym<op*waym)
el.timer=setTimeout(function(){fade(el, way, op, opinc, speed)}, speed);
}

fade.preset=function(el){
return el.id&&typeof eval(el.id)=='number'? eval(el.id) : null;
}

Incidentally, I think I've found a respectable use for the 'evil' eval here. Here is a link to an identical looking demo that takes advantage of this alternate coding for the Monalisa image:

http://home.comcast.net/~jscheuer1/side/files/fade_advanced_preset.htm

One other addition I am interested in and have had some luck with figuring out the preliminaries would be to set the the type of opacity from the vary beginning, according to the browser's capabilities, and then to have the script write out the style in the head for each fading image. However, at that point, it will no longer be a tip or tutorial, rather a full blown script submission.

Twey, I hardly think that this is the section for untested code.You're right. I've tested and debugged it and also made some non-bug-related modifications (allowing an initial value to be specified, for example, and using an object to allow named arguments for much greater flexibility). I've also avoided adding arbitrary properties to DOM nodes, which I'm told is non-standard.
Mainly, most only work after the markup is parsed.I've solved this problem in my code by using document.documentElement to check possible approaches. document.documentElement represents the <html> element, and so will always be present, even if none of the other markup has been parsed. I suspect <head> would also work for this purpose, but it's more code to gain a reference to <head> and I can see no benefit in doing so, so I didn't try it.
To that end I have developed an alternate set up with an added function and code in the main function to utilize the added function, which allows one to preset an image's opacity if it is to initially be other than 0. It still must also be styled (now in the head or in an external stylesheet is fine) and it must have a unique id.My own requires nothing, although users should beware a bug in IE that causes translucent elements to look rather odd when they have transparent backgrounds. It is recommended that if you use this script with an element that can have a background colour (images are fine, since they take up all their allotted space), the element should have a background colour or image (other than transparent) set for IE's sake.
Now, one thing that I am not happy about with the code is that it requires (in cases where the initial opacity is other than 0) inline style.Thus, the initial argument I added.
Incidentally, I think I've found a respectable use for the 'evil' eval here.Not really. A valid ID may not start with a number, so the only way this would work is if there is a global variable with the same name as the element's ID which happened to store a number.

I don't really want to take the time at the moment to go over your code so as to be able to comment on it in any detailed way. I'm still going to go with my version, adding to it as I see fit.

I will say:

I was alluding to the document.documentElement earlier when I mentioned that I saw promise in writing out the style for a fade-able image. It worked for all the target browsers I have, I just didn't know how it would play in others. Thinking about it, testing for it should be sufficient, as any browser that doesn't recognise it will probably be either old enough to be rarely used, or simply not support opacity of any kind anyway. But - I had wanted this to be as retroactive as possible. If I can test it out in some older browsers, like early Safari and - say, a Mozilla based browser that supports only -moz-opacity, I will be happier.

There is a problem in IE because, although document.documentElement.filters will be true, document.documentElement.filters[0] will not and it is an important distinction between the older, less efficient method of dealing with filters in v 5.5 and that used in 6 and later. typeof document.documentElement.filters[0] is number in the later more efficient IE browsers but, as I say - document.documentElement.filters[0] will not exist in any IE unless a filter has already been applied to the html element, not something you really want to do. A test element may be created though, once filters has been established.

Now - about my use of eval. Either I've misunderstood you, or you've misunderstood my code. The one example I have put forth is for an image with an id of 'mona'. By setting a variable:

var mona=50;

and testing that the image has, in this order:


not been run through this process already
an id
that id has also been set as a variable that evaluates as a number


So, I'm not using a number for an id or evaluating an id. The whole thing will be ignored if the aforementioned variable hasn't been set to a number. The image will then get its initial opacity in the script as 0, or as otherwise set via inline style, except in IE 6 and up, which will recognise the filter from any style source and get its initial in script value from there.

I'm glad you recognise the trouble with opacity in IE for, essentially text with no background. It has actually gotten worse in IE 7, as I mentioned previously in this thread, and applies to all filters, not just the alpha filter.

Added Later:

One thing I am noticing about your code is that it doesn't seem to set the opacity prior to active use of the script for the image itself without any onload event. I may have missed something though. That is what I am after as the final improvement - if it can be done simply enough.

I don't really want to take the time at the moment to go over your code so as to be able to comment on it in any detailed way. I'm still going to go with my version, adding to it as I see fit.I'll comment it for you.
document.documentElement.filters[0] will not exist in any IE unless a filter has already been applied to the html element, not something you really want to do.I don't see why not. It's only one filter, and thus no less efficient than applying a filter to any other element. You could always create a hidden element for testing and then destroy it, I guess, but I think applying a filter to the documentElement would be more efficient.
Now about my use of eval. Either I've misunderstood you, or you've misunderstood my code. The one example I have put forth is for an image with an id of 'mona'. By setting a variable:

var mona=50;

and testing that the image has, in this order:

not been run through this process already
an id
that id has also been set as a variable that evaluates as a number

So, I'm not using a number for an id or evaluating an id. The whole thing will be ignored if the aforementioned variable hasn't been set to a number. The image will then get its initial opacity in the script as 0, or as otherwise set via inline style, except in IE 6 and up, which will recognise the filter from any style source and get its initial in script value from there.Oh, I see. In this case you'd probably be better off using window[el.id].

OK, comments added. Sorry for the double post, but edit doesn't highlight the thread as unread and I'm leery of leaving old code lying around in a tips thread.

John, on further consideration it'd actually be even better to use a designated object as a namespace to hold all those element values, rather than window.

I didn't mean that it needed comments, although that generally never hurts when sharing scripts. I meant that I didn't want to get too distracted from my own version to comment in detail on yours. I do have a few things to mention though about it and about this fading business in general.

I cannot see anywhere that you have taken into account that many older browsers, like FF 1.0.1 I believe, and NS 7 for sure (and perhaps later NS) cannot transition smoothly from any opacity less than 1 to 1 without a disconcerting flash. That is why there is so much mention of .99 in my code. It is unfortunate that there is no object test to determine (as far as I know) which opacity/-moz-opacity based browsers will feature this defect, so all are given the .99 treatment in my code. Opera 9.0 (first Opera browser with opacity support) had this problem too, but the recent/current sub-version I now use does not.

The next thing is about IE 7 and filters. There is no way that adding a filter to the html element for testing would be a good idea. It will prevent all text on the page from rendering in ClearType mode, making it appear faint and scraggly. I mentioned this earlier, but am not sure it sunk in. In IE 7, any element with a filter or that has a parent with a filter will exhibit this unsightly rendering of text, regardless of whether or not said element or parent has background. Horrible, but true to the best of my knowledge. In IE 6 and earlier, this could be avoided simply by having background - as you mentioned, but this no longer appears to work.

Now, as to readability/simplicity of coding, I would direct you, or anyone to this post:

http://www.dynamicdrive.com/forums/showpost.php?p=99490&postcount=2

where the germ for my current project came up. The code featured there is so simple that many more folks will be able to grasp it as compared to any of the code put forth so far in this thread. However, it will only work well in the more recent opacity/alpha opacity supporting browsers.

I believe I am now about finished with my code's evolution. The most recent version should work well in any browser that supports any type of style or filter opacity and has been tested and proven to in many that do, as well as to degrade gracefully in several that do not. I took your advice on not assigning script properties to the element and have worked out my way of not requiring any style to be set. Elements now need be configured like so:


var faders=[];
//preset each image's initial opacity (0 to 100):
faders[0]=['mona', 50];
faders[1]=['arch', 0];

by their id. And the timeout caching is handled this way:


fade.timer=function(el, to){
for (var i = 0; i < faders.length; i++)
if (el.id==faders[0]){
if(arguments[1])
faders[i][3]=to;
else if(faders[i][3])
clearTimeout(faders[i][3]);
}
}

with this syntax:


fade.timer([I]el);

clearing any timeout that has been previously cached for that element, and this type syntax:


fade.timer(el, setTimeout(function(){fade(el, way, op, opinc, speed)}, speed));

setting and caching a timeout for an element without attaching any property to it - where el is the element passed along in the main script. and faders[i][3] is an extension of the dimension of the array that already has the element's id as its first [0] value.

Position [2] in the dimension for a given element is used to register whether or not the element's initial 'in script' opacity has been set yet or not, thus eliminating the only other property I was attaching to the element (el.yet). This is carried out in another function:


fade.preset=function(el){
for (var i = 0; i < faders.length; i++)
if (el.id==faders[i][0]&&!faders[i][2]){
if(arguments[1])
faders[i][2]=1;
else
return faders[i][1];
}
return null;
}

I intend to make the full code available in the submissions section soon.

Added Later:

The code is now up in the submissions section. Also, I realized that since I was now requiring that the image to be faded have an id, I could retrieve it and use it for timing and other unique properties, examples:


fade[id+'timer']=setTimeout(function(){fade(el, way, op, opinc, speed)}, speed);


clearTimeout(fade.[id+'timer']);


fade[id+'yet']=true;

Here is the link to the submission:

http://www.dynamicdrive.com/forums/showthread.php?t=22534










privacy (GDPR)