Animation Widget, Slide Out From Top of Viewport

One of the most useful JavaScript animations is to animate a DOM node as it slides in from the top (or another side) of the browser viewport. I have implemented this animation several times for various projects, and thought that it would be a good idea to unify my implementations into one concise widget. I have used YUI as the Framework to power this widget, but unlike most of my other widgets, I have internalized all library references, so it would be easy to replace with another framework. In addition, I have not leveraged any of my own Framework files. By coding this way, the widget is completely standalone and more easily imported into your existing codebase. I intend to write all future widgets in this manner.

Anyway, this is my first version of ViewportSlider and I am open to suggestions. Currently, it only supports animating out of the top of the viewport, but I have included a configuration object that I may later extend to support animations form any side of the viewport.

Example 1: Viewport Slider (source)

YAHOO.widget.ViewportSlider = function(elem, cfg) {
	// library namespace
	var YUI = YAHOO.util,
		Dom = YUI.Dom,
        E = YUI.Event,
        A = YUI.Anim;

	// local namespace
	var config = cfg || {},
		F = function() {},
        isAnimated = false,
        isSlideDown = false,
        that = null;

	// DOM namespace
	var dom = {
		node: Dom.get(elem)
	};

	if (! dom.node) {return null;} // missing required

	// setup configuration
	// config.useFixed
	// config.center
	if (! config.easing) {config.easing = YAHOO.util.Easing.easeBoth;}
	if (! config.duration) {config.duration = 1;}
	if (! config.zIndex) {config.zIndex = 101;}

	// position for scroll
	Dom.setStyle(dom.node, visibility, hidden);
	Dom.setStyle(dom.node, display, block);
	Dom.setStyle(dom.node, position, absolute);
	Dom.setStyle(dom.node, z-index, config.zIndex);

	var rect = Dom.getRegion(dom.node),
		height = rect.bottom - rect.top;

	Dom.setStyle(dom.node, top, - + height + px);
	Dom.setStyle(dom.node, visibility, visible);

    // event namespace
    var evt = {
        ieScroll: function() {
            var reg = Dom.getClientRegion();
            Dom.setStyle(dom.node, top, reg.top + px);
        }
    };

    // public namespace
	F.prototype = {

        isAnimated: function() {
            return isAnimated;
        },

        isSlideDown: function() {
            return isSlideDown;
        },

        isSlideUp: function() {
            return ! isSlideDown;
        },

        slideDown: function() {
            if (! isAnimated) {
                var rect = Dom.getRegion(dom.node),
                    reg = Dom.getClientRegion(),
                    vheight = Dom.getViewportHeight(),
                    vwidth = Dom.getViewportWidth(),
                    height = rect.bottom - rect.top,
                    width = rect.right - rect.left,
                    left = (vwidth - width) / 2;

                if (0 > left || isNaN(left)) {left = 0;}
                isAnimated = true;
                isSlideDown = true;

                Dom.setStyle(dom.node, top, reg.top - height + px);
                Dom.setStyle(dom.node, position, absolute);
                if (config.center) {Dom.setStyle(dom.node, left, left + px);}

                // setup the animation
                var anim = new A(dom.node, {top: {from: reg.top - height, to: reg.top}}, config.duration, config.easing);
                anim._onComplete.subscribe(function() {
                    isAnimated = false;

                    // fix the position, when configured and smaller than viewport
                    if (config.useFixed && vheight > height) {
                        // special case IE < 7, becuase it improperly supports "position:fixed"
                        if (0 < YAHOO.env.ua.ie && 7 > YAHOO.env.ua.ie) {
                            E.on(window, scroll, evt.ieScroll);
                        }
                        else {
                            Dom.setStyle(dom.node, top, 0px);
                            Dom.setStyle(dom.node, position, fixed);
                        }
                    } // dont position fixed, if viewport is too small
                });
                anim.animate();
            }
        },

        slideToggle: function() {
            that[isSlideDown ? slideUp : slideDown]();
        },

        slideUp: function() {
            if (! isAnimated) {
                var rect = Dom.getRegion(dom.node),
                    height = rect.bottom - rect.top,
                    reg = Dom.getClientRegion();

                isAnimated = true;
                isSlideDown = false;
                Dom.setStyle(dom.node, top, reg.top + px); // reposition to scroll offset + viewport before animating
                Dom.setStyle(dom.node, position, absolute);

                // setup the animation
                var anim = new A(dom.node, {top: {from: reg.top, to: reg.top - height}}, config.duration, config.easing);
                anim._onComplete.subscribe(function() {
                    isAnimated = false;
                    Dom.setStyle(dom.node, top, - + height + px);
                    // remove special case event handler for IE < 7
                    if (0 < YAHOO.env.ua.ie && 7 > YAHOO.env.ua.ie) {
                        E.removeListener(window, scroll, evt.ieScroll);
                    }
                });
                anim.animate();
            }
		}
	};

	that = new F();
	return that;
};

To instantiate, call as follows:

Example 2: Viewport Slider Instantiation

YAHOO.widget.ViewportSlider(idOfElementToAnimate, {center: true, useFixed: true, duration: 0.5});

The widget requires that a DOM node is provided, but the second parameter (configuration object) is entirely optional. Once call, ViewportSlider first creates shortcut references to YUI, some internal variables, and a DOM reference to the node. Then it goes through the configuration parameters, setting default values as necessary. You can currently configure the following: center (true|false) - centers the node horizontally in the viewport, useFixed (true|false) - fixes node to the top of the viewport, duration (number) - the length of animation in seconds, easing (YAHOO.util.Easing.*) - any YAHOO easing method to use when animating, and zIndex (number) - the zIndex to make the node. Next we position the node absolutely above the top of the viewport and display it as block (in case it wasnt a block-level node already). There are several public methods, the names of which explain there purpose, but check the source-code for additional documentation. To animate, call slideDown, slideUp, or slideToggle.

When slideDown is called, we compute the dimension of the viewport, the node, and how far the page has been scrolled. Then we update the isAnimated and isSlideDown states, before positioning the element to the top of the viewport plus any scroll offset. This is so that a user who has scrolled down the page still see the animation. We also center the node, if the center configuration property was set to true. Next we setup the animation to scroll from the top position we just set, to the top of the viewport plus any scroll offset. We subscribe to the YUI _onComplete custom event so that when the animation terminates, we can adjust the isAnimated state. We also check to see if useFixed is set and that the size of the node is smaller than the viewport. When both are true, we position the element statically to the top of the viewport (0px), except in IE less than 7, where we have to use a special scroll event monitor to simulate static position (the node stays absolutely positioned and the top is adjusted to the scroll offset).

When slideUp is called, like slideDown, we compute dimensions and adjust isAnimated and isSlideDown states. Then we reposition absolutely to the top of the viewport plus any scroll offset. This is required, because when useStatic is set to true, the top position of the node was adjusted to 0px and now must be restored before animating. We animate from the top of the viewport plus any scroll offset, to that value minus the height of the node. Using the same animation custom event subscription, as slideDown, we adjust the isAnimated state and reposition the element off where it was first positioned when the widget loaded. Lastly, if the IE less than 7 special-case listener was used, we unsubscribe it now, freeing up the resources.

When slideToggle is called, it uses the isSlideDown to determine whether to call slideUp or slideDown.

That is pretty much it, simple instantiate a ViewportSlider object, and use slideUp and slideDown to do all the work. Keep in mind that any node you pass to this widget will have their position set to absolute and be visually removed from the page, until slideDown is called. If the nodes are not initially hidden or "display:none" then there will be a flicker (as seen on the demo page) as the widget loads. Here is a ViewportSlider Demo Page so you can try it out. The demo is straight forward, except there is a third button hidden way at the bottom of the page, used to illustrate how the ViewportSlider works with scrolling.