You've overcomplicated this in two ways: by using innerHTML, which is non-standard, bad style, and makes this sort of operation much trickier than it should be, and by making it very object-oriented, which doesn't offer much in the way of advantages here. I came into programming from the Java area, and it took me a while to realise that objects aren't the answer to everything. Closures are often (but not always) a much neater solution. Here's a less complicated (but probably fully-featured) version, just using DOM methods and closures:
Code:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Testcase</title>
<script type="text/javascript">
if (typeof Node === "undefined")
var Node = {
TEXT_NODE: 3
};
// Apply an animated "rainbow" effect to an element, with random colours.
var rainbow = (function(opts) {
var t = null, // To store the interval ID.
c = [], // To store the letters to be updated.
interval = opts.interval || 100,
coverage = opts.coverage || 5;
// Zero-pad a number.
function pad(num, places, radix) {
var s = num.toString(radix || 10);
return (new Array(places - s.length + 1)).join('0') + s;
}
// Give each letter its own <span> and add it to an array of elements to update.
function getLetters(el) {
var r = [];
if (el.nodeType === Node.TEXT_NODE) {
for (var s = el.nodeValue, i = 0; i < s.length; ++i)
el.parentNode.appendChild(r[r.length] = document.createElement("span")).appendChild(document.createTextNode(s.charAt(i)));
el.parentNode.removeChild(el);
} else {
// e has to be an array, otherwise it gets modified by getLetters() while we're using it.
for (var e = Array.prototype.slice.call(el ? el.childNodes : []), i = 0; i < e.length; ++i)
r.push.apply(r, getLetters(e[i], true));
if(arguments[1])
el.parentNode.appendChild(el);
}
return r;
}
// Generate a random colour.
function randomColour() {
return "#" + pad(Math.floor(Math.random() * 0x1000000), 6, 16);
}
// Get a random sample of an array.
function randomSample(arr, n) {
for (var r = [], i = n - 1, a = arr.slice(); a.length && i >= 0; --i)
r.push(a.splice(Math.floor(Math.random() * a.length), 1)[0]);
return r;
}
// Update all the letters.
function tick() {
for (var s = randomSample(c, Math.floor(coverage * c.length)), i = s.length - 1; i >= 0; --i)
s[i].style.color = randomColour();
}
// The function to be called on elements.
function rainbow(el) {
if (arguments.length > 1) {
for (var i = arguments.length - 1; i >= 0; --i)
rainbow(arguments[i]);
return t;
}
c.push.apply(c, getLetters(el));
if (!t)
t = setInterval(tick, interval);
return t;
}
return rainbow;
})({
interval: 100,
coverage: 0.2
});
onload = function() {
rainbow(document.getElementById("test"));
};
</script>
</head>
<body>
<p id="test">A <em>test</em> item.</p>
</body>
</html>
Bookmarks