/* http://keith-wood.name/labeleffect.html
Label Effects for jQuery v1.0.0.
Written by Keith Wood (kbwood{at}iinet.com.au) July 2009.
Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
Please attribute the author if you use it. */
(function($) { // Hide scope, no $ conflict
var PROP_NAME = 'labelEffect';
/* Label effects manager. */
function LabelEffects() {
this._defaults = {
color: '', // Main text colour
hiColor: 'silver', // The highlight colour
hiDir: this.NONE, // The highlight direction
hiOffset: 0, // The highlight offset
hiFill: false, // True to fill from main text to highlight
hiBlend: false, // True to blend from main color to highlight
shadowColor: 'silver', // The shadow colour
shadowDir: this.DOWNRIGHT, // The shadow direction
shadowOffset: 5, // The shadow offset
shadowFill: false, // True to fill from main text to shadow
shadowBlend: false, // True to blend from main color to shadow
effect: '' // A preset effect
};
this._effects = {
echoed: {color: '', hiColor: 'white', hiDir: this.DOWNRIGHT, hiOffset: 2,
hiFill: false, hiBlend: false, shadowColor: 'gray', shadowDir: this.DOWNRIGHT,
shadowOffset: 4, shadowFill: false, shadowBlend: false},
floating: {color: '', hiColor: 'silver', hiDir: this.NONE, hiOffset: 0,
hiFill: false, hiBlend: false, shadowColor: 'silver', shadowDir: this.DOWNRIGHT,
shadowOffset: 5, shadowFill: false, shadowBlend: false},
raised: {color: 'white', hiColor: 'silver', hiDir: this.UPLEFT, hiOffset: 1,
hiFill: false, hiBlend: false, shadowColor: 'black', shadowDir: this.DOWNRIGHT,
shadowOffset: 1, shadowFill: false, shadowBlend: false},
shadow: {color: 'white', hiColor: 'white', hiDir: this.NONE, hiOffset: 0,
hiFill: false, hiBlend: false, shadowColor: 'black', shadowDir: this.DOWNRIGHT,
shadowOffset: 1, shadowFill: false, shadowBlend: false},
sunken: {color: 'white', hiColor: 'silver', hiDir: this.DOWNRIGHT, hiOffset: 1,
hiFill: false, hiBlend: false, shadowColor: 'black', shadowDir: this.UPLEFT,
shadowOffset: 1, shadowFill: false, shadowBlend: false}
};
}
// X- and y-offsets by direction
var OFFSETS = [[-1, -1], [0, -1], [+1, -1], [-1, 0],
[0, 0], [+1, 0], [-1, +1], [0, +1], [+1, +1]];
$.extend(LabelEffects.prototype, {
/* Class name added to elements to indicate already configured with effects. */
markerClassName: 'hasLabelEffect',
/* Direction indicators. */
UPLEFT: 0,
UP: 1,
UPRIGHT: 2,
LEFT: 3,
NONE: 4,
RIGHT: 5,
DOWNLEFT: 6,
DOWN: 7,
DOWNRIGHT: 8,
/* Override the default settings for all label effects instances.
@param settings (object) the new settings to use as defaults
@return (LabelEffects) this object */
setDefaults: function(settings) {
extendRemove(this._defaults, settings || {});
return this;
},
/* Add a new label effect to the list.
@param id (string) the ID of the new effect
@param settings (object) the preset settings
@return (LabelEffects) this object */
addEffect: function(id, settings) {
this._effects[id] = settings;
return this;
},
/* Return the list of defined effects.
@return (object[]) indexed by effect id (string), each object contains:
settings (object) the effect's settings */
getEffects: function() {
return this._effects;
},
/* Attach the label effect to a control.
@param target (element) the control to affect
@param settings (object) the custom options for this instance */
_attachEffect: function(target, settings) {
target = $(target);
if (target.hasClass(this.markerClassName)) {
return;
}
target.addClass(this.markerClassName);
var inst = {settings: $.extend({}, this._defaults),
saveCSS: {position: target.css('position'), color: target.css('color')}};
$.data(target[0], PROP_NAME, inst);
this._updateEffect(target, settings);
},
/* Reconfigure the settings for a label effect control.
@param target (element) the control to affect
@param settings (object) the new options for this instance or
(string) an individual property name
@param value (any) the individual property value (omit if settings is an object) */
_changeEffect: function(target, settings, value) {
target = $(target);
if (!target.hasClass(this.markerClassName)) {
return;
}
if (typeof settings == 'string') {
var name = settings;
settings = {};
settings[name] = value;
}
this._updateEffect(target, settings);
},
/* Construct the requested label effect.
@param target (jQuery) the control to affect
@param settings (object) the custom options for this instance */
_updateEffect: function(target, settings) {
settings = settings || {};
var inst = $.data(target[0], PROP_NAME);
settings = extendRemove(extendRemove(inst.settings,
this._effects[settings.effect] || {}), settings);
this._removeEffect(target);
target.css({position: 'relative', color: 'transparent'});
var template = $('').html(target.html());
this._createEffect(target, template, 'shadow', settings.color,
settings.shadowColor, settings.shadowDir, settings.shadowOffset,
settings.shadowFill, settings.shadowBlend);
this._createEffect(target, template, 'highlight', settings.color,
settings.hiColor, settings.hiDir, settings.hiOffset,
settings.hiFill, settings.hiBlend);
this._createEffect(target, template, 'orig', settings.color,
settings.color || inst.saveCSS.color, this.UP, 0, false, false);
},
/* Create one of the effects.
@param target (jQuery) the original element
@param template (jQuery) the template for effect elements
@param effectClass (string) a class to add to the effect element(s)
@param mainColour (string) the colour of the main text
@param colour (string) the colour for this effect
@param dir (number) the direction for this effect
@param offset (number) the distance for this effect
@param fill (boolean) true to fill between text and this effect
@param blend (boolean) true to blend the colours for this effect */
_createEffect: function(target, template, effectClass, mainColour,
colour, dir, offset, fill, blend) {
if (dir == this.NONE) {
return;
}
var cOffsets = [0, 0, 0];
var baseRGB = getRGB(blend && offset > 1 ? mainColour : colour);
if (blend && offset > 1) {
var endRGB = getRGB(colour);
cOffsets = [(endRGB[0] - baseRGB[0]) / offset,
(endRGB[1] - baseRGB[1]) / offset, (endRGB[2] - baseRGB[2]) / offset];
}
var blendColour = function(i) {
return 'rgb(' + Math.round(baseRGB[0] + i * cOffsets[0]) + ',' +
Math.round(baseRGB[1] + i * cOffsets[1]) + ',' +
Math.round(baseRGB[2] + i * cOffsets[2]) + ')';
};
var scroll = ($.browser.opera ? [document.documentElement.scrollLeft,
document.documentElement.scrollTop] : [0, 0]);
for (var i = offset; i >= (fill ? 1 : offset); i--) {
template.clone().addClass('labelEffect-' + effectClass).
css({color: blendColour(i), left: i * OFFSETS[dir][0] + scroll[0],
top: i * OFFSETS[dir][1] + scroll[1]}).
appendTo(target);
}
},
/* Remove the label effect widget from a control.
@param target (element) the control to affect */
_destroyEffect: function(target) {
target = $(target);
if (!target.hasClass(this.markerClassName)) {
return;
}
target.removeClass(this.markerClassName);
this._removeEffect(target);
var inst = $.data(target[0], PROP_NAME);
target.css(inst.saveCSS);
$.removeData(target[0], PROP_NAME);
},
/* Remove the label effect additions.
@param target (jQuery) the control to affect */
_removeEffect: function(target) {
var html = target.find('.labelEffect-orig').html();
target.find('.labelEffect-highlight,.labelEffect-shadow,' +
'.labelEffect-orig').remove().html(html);
}
});
/* Parse strings looking for common colour formats.
@param colour (string) colour description to parse
@return (number[3|4]) RGB[A] components of this colour */
function getRGB(colour) {
var result;
// Check if we're already dealing with an array of colors
if (colour && colour.constructor == Array && (colour.length == 3 || colour.length == 4)) {
return colour;
}
// Look for rgb(num,num,num)
if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(colour)) {
return [parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10)];
}
// Look for rgb(num%,num%,num%)
if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(colour)) {
return [parseFloat(result[1]) * 2.55, parseFloat(result[2]) * 2.55,
parseFloat(result[3]) * 2.55];
}
// Look for #a0b1c2
if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(colour)) {
return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)];
}
// Look for #abc
if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(colour)) {
return [parseInt(result[1] + result[1], 16), parseInt(result[2] + result[2], 16),
parseInt(result[3] + result[3], 16)];
}
// Otherwise, we're most likely dealing with a named color
return COLOURS[$.trim(colour || '').toLowerCase()] || COLOURS['none'];
}
// The HTML/CSS named colours
var COLOURS = {
'': [255, 255, 255, 1],
none: [255, 255, 255, 1],
transparent:[255, 255, 255, 0],
aqua: [0, 255, 255],
black: [0, 0, 0],
blue: [0, 0, 255],
fuchsia: [255, 0, 255],
gray: [128, 128, 128],
grey: [128, 128, 128],
green: [0, 128, 0],
lime: [0, 255, 0],
maroon: [128, 0, 0],
navy: [0, 0, 128],
olive: [128, 128, 0],
orange: [255, 165, 0],
purple: [128, 0, 128],
red: [255, 0, 0],
silver: [192, 192, 192],
teal: [0, 128, 128],
white: [255, 255, 255],
yellow: [255, 255, 0]
};
/* Add and/or remove attributes of an object.
jQuery extend now ignores nulls!
@param target (object) the object to amend
@param props (object) the attributes to copy
@return (object) the updated object */
function extendRemove(target, props) {
$.extend(target, props);
for (var name in props) {
if (props[name] == null) {
target[name] = null;
}
}
return target;
}
/* Attach the label effect functionality to a jQuery selection.
@param command (string) the command to run (optional, default 'attach')
@param options (object) the new settings to use for these label effect instances (optional)
@return (jQuery) for chaining further calls */
$.fn.labeleffect = function(options) {
var otherArgs = Array.prototype.slice.call(arguments, 1);
return this.each(function() {
if (typeof options == 'string') {
$.labeleffect['_' + options + 'Effect'].
apply($.labeleffect, [this].concat(otherArgs));
}
else {
$.labeleffect._attachEffect(this, options || {});
}
});
};
/* Initialise the label effects functionality. */
$.labeleffect = new LabelEffects(); // singleton instance
if ($.browser.opera) {
$(document).scroll(function() { // Re-apply after scroll
$('.' + $.labeleffect.markerClassName).each(function() {
if (!this.nodeName.toLowerCase().match(/^div$|^p$|^h[1-6]$/)) {
$(this).labeleffect('change', {});
}
});
});
}
})(jQuery);