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'); } } // don't 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 wasn’t 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.

I came here from a link on the YUI Blog talking about Apple OS X style “sheets” and was unsatisfied with this demo really not having too much to do with “sheets” or dialogs that extend down from the viewport.
I tried doing my own take on OS X style “sheets”:
http://925html.com/files/sheets/sheets.html
Comment by Eric Ferraiuolo — November 10, 2008 @ 12:41 am
Eric, I am glad you found my blog and you have written a good example for the OS sheets.
However, I don’t think you should judge this site, based on the link that another site provided. If you read the article, you’ll see that I was writing a tool for sliding elements in from outside of the viewport. I understand why Eric of YAHOO! likened it to OS X Sheets, but I did not make that claim.
Comment by Matt Snider — November 10, 2008 @ 10:43 am
Matt, in my opinion the code you provided seems over-complicated to preform a task which would be pretty straight forward and concise in any JavaScript library.
I think the concept of pre-built animations is great, and needed in YUI. I could imagine a Slide animation class that extends YUI.util.Motion. This class could animate an object in/out of the viewport on any side (top, bottom, right, left) as well as other slide animations. You’re code is a good starting point for such a class. Although maybe it should just be for YUI only?
One practical example of a slide down from viewport would be for notification message in a web app, and this would be something like OS X style sheet-dialogs. I thought the idea of that was cool, so figured I’d try something out… Didn’t mean to offend or seem judgmental.
What do you think about a Slide animation class that extends YUI.util.Motion and requires YUI to work and support all-side viewport sliding?
Comment by Eric Ferraiuolo — November 10, 2008 @ 3:48 pm
Eric, I think that is a great idea and where I am ultimately heading with this widget.
Comment by Matt Snider — November 10, 2008 @ 4:04 pm