Should I write my fadeTo function differently?
Well written animation functions typically use a tweening algorithm that checks the system clock to see how much time is actually remaining in the originally specified time allotment and then adjusts the animation increment to put things back on time, even if the timer events weren't coming in exactly on time or the browser was super busy and you weren't getting all the cycles you might want or the host CPU just isn't very fast.  Done well, this can even allow you to adjust the step value for smoother animations on more capable computers and coarser steps on less capable computers.

As you have it, you have a hard coded step function in time of 1/10th the time and thus 1/10th the total opacity change.  On capable computers, you can run much, much smoother than this.

Here's an example:

function now() {
    return new Date().getTime();    
}

function fadeTo(obj, time, to, doneFn){

    // sanitize optional arguments
    if ( typeof time === "undefined" )
        time = 1000;
    if ( typeof to === "undefined" )
        to = 1;

    if ( time &lt;= 0 )
        return true;

    var objSt = obj.style, 
        originalOpacity = parseFloat(getComputedStyle(obj, null).getPropertyValue("opacity")),
        deltaOpacity = to - originalOpacity,
        sanitizeOpacity,
        minT = 30,
        startTime = now();

    // based on whether we're going up or down, set our santize function
    if (deltaOpacity &gt; 0) {
        sanitizeOpacity = function(x) {
            // not more than 1
            return Math.min(x, 1);
        }
    } else {
        sanitizeOpacity = function(x) {
            // not less than zero
            return Math.max(x, 0);
        }
    }

    function step() {
        // calculate how much time has elapsed since start
        // and then calculate what the opacity should be set
        // to for the animation to be perfectly on time
        var elapsed = now() - startTime, completion;

        // if all our time has elapsed, then just set to final opacity
        if (elapsed &gt;= time) {
            objSt.opacity = to;
            // we're done, call the completion function
            // with the this pointer set to the object that was animating
            if (doneFn) {
                doneFn.apply(obj);
            }
        } else {
            // still some time remaining
            // calculate ideal opacity value to be "on time"
            completion = elapsed/time;
            objSt.opacity = sanitizeOpacity((deltaOpacity * completion) + originalOpacity);
            // schedule next step
            setTimeout(step, minT);
        }


    }
    // start the animation
    setTimeout(step, minT);
}


Working demo: http://jsfiddle.net/jfriend00/hG3Wj/

In other comments:


You MUST use var in front of variables that should be local variables.
You need to be getting the original opacity as the computedStyle, not just reading opacity directly because you need to include style sheet opacity, if nothing is set directly on the object.
If you want to know if an argument is truly undefined, you need to use === to test for it so there can be no automatic type conversions.
I find it more efficient to define an inner function that is called repeatedly because then all of our argument sanitization and what/not only has to happen once, not on each iteration and we can have state variables in the outer closure.