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 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.