Animated Scrolling
The graphic designer thought it would be great to replace the default scroll-bars with two buttons which control the scrolling of the content in a div. There were several problesm bundled up inside of this.- The CSS would be a hassle
- There would be multiple instances of these two scrollers on one page
- The scrollers would need to listen for events like mousewheel
- The scrollers would need to be overlaid in the right place
- and stay in the right place if the browser is resized
- The way the scrollers look should come from CSS
- So, Javascript would need to read the CSS rules for the paddings etc.
A fun problem! Here is what I came up with (it sits on the YUI framework):
/* only vertical scrolling supported */
cos.scrollers = function() {
var scrollEvents;
var buildCtrl = function(parent, name) {
var ctrl = cos.newEl({
'el' : 'a',
'parent' : parent,
'href' : '#',
'class' : [name]
});
Event.on(ctrl, 'mouseover', function(){
Dom.addClass(this, name + '_hover');
});
Event.on(ctrl, 'mouseout', function(){
Dom.removeClass(this, name + '_hover');
});
return ctrl;
};
var setup = function() {
Dom.batch($$('div#bd div.scrollable'), function(o) {
var r = Dom.getRegion(o);
if(o.scrollHeight <= (r.bottom - r.top)) {
return null;
}
/* first turn off the scroll bars */
cos.setStyles(o, {
'overflow' : 'hidden'
});
/* add the scroll controls */
var scrollerClasses = ['scroller'];
if(Dom.hasClass(o, 'black')) {
scrollerClasses.push('black');
}
/* scroller div element */
var scroller = cos.newEl({
'el' : 'div',
'parent' : document.body,
'class' : scrollerClasses
});
/* build the two buttons for vertcal scroll control */
var up_ctrl = buildCtrl(scroller, 'up_ctrl');
var dn_ctrl = buildCtrl(scroller, 'dn_ctrl');
/* assign scroll events */
var scrollObj = {
'content' : o,
'height' : o.scrollHeight,
'h' : ((r['bottom'] - r['top']) * (3/4))
};
/* assign click events */
Event.on(up_ctrl, 'click', scrollUp, scrollObj, true);
Event.on(dn_ctrl, 'click', scrollDown, scrollObj, true);
/* assign wheel events */
Event.on(o, 'DOMMouseScroll', wheelHandler, scrollObj, true);
Event.on(o, 'mousewheel', wheelHandler, scrollObj, true);
var fadeObj = {
'scroller' : scroller,
'scrollableDiv' : o
};
/* fade in right away */
repositionAndFadeIn(null, null, fadeObj);
/* prepare scrollers for vertical page adjustment */
cos.events.pageAdjust.vertical.begin.subscribe(fadeOut, fadeObj);
cos.events.pageAdjust.vertical.end.subscribe(repositionAndFadeIn, fadeObj);
});
};
var fadeOut = function(e, arr, obj) {
cos.setStyles(obj['scroller'], {
'opacity' : 0,
'visibility' : 'hidden'
});
};
var repositionAndFadeIn = function(e, arr, obj) {
var region = Dom.getRegion(obj['scrollableDiv']);
var scroller = obj['scroller'];
cos.safeCenter(scroller, region);
cos.setStyles(scroller, {
'opacity' : 0,
'visibility' : 'visible'
});
var appear = new Anim(scroller, {
'opacity' : { 'to' : 1 }
}, 1.0, YAHOO.util.Easing.easeOut);
appear.animate();
};
var wheelHandler = function(e, obj) {
/* if we do not stop the default scrolling
* it will scroll the whole page */
Event.stopEvent(e);
var delta;
/* normalize the delta
* from andrewdupont.net/2007/11/07/pseudo-custom-events-in-prototype-16/ */
if (e.wheelDelta) {/* IE & Opera */ delta = e.wheelDelta / 120; }
else if (e.detail) { /* W3C */ delta = -e.detail / 3; }
if (!delta) { return; }
if(delta > 0) { /* up */ scrollUp(null, obj); }
else { /* down */ scrollDown(null, obj); }
};
var scrollUp = function(e, obj, custom) {
scrollEvents.begin.fire();
if(e != null) {
Event.stopEvent(e);
}
var slideDown = new YAHOO.util.Scroll(obj['content'], {
'scroll': { 'by': [0, -obj['h']]}
}, 0.3, YAHOO.util.Easing.easeOut);
slideDown.onComplete.subscribe(function(){
scrollEvents.end.fire();
});
slideDown.animate();
};
var scrollDown = function(e, obj) {
scrollEvents.begin.fire();
if(e != null) {
Event.stopEvent(e);
}
var slideUp = new YAHOO.util.Scroll(obj['content'], {
'scroll': { 'by': [0, obj['h']]}
}, 0.3, YAHOO.util.Easing.easeOut);
slideUp.onComplete.subscribe(function(){
scrollEvents.end.fire();
});
slideUp.animate();
};
return function() {
Event.onDOMReady(setup);
scrollEvents = cos.events.newEventSet();
return {
'scrollEvents' : scrollEvents
}
}();
}();
Here is a what it looks like in action:

What I like about this code:
- The process:
- The scroller class self-invokes at the early script execution time
- The onDOMReady waits to call the initializer (this way there will be html elements to init)
- Then, the divs with scrollable class get batched
- for each div.scrollable a new set of scrollers is made
- They are placed
- The event listeners are assigned
- Also, the scrollers are overlays, meaning they have a non-default z-index and they are positioned absolutely
- There is a special function which I used to place them safely: cos.safeCenter(scroller, region);
Here is that method:
cos.safeCenter = function(item, region) {
var halfWidth = Dom.getViewportWidth() / 2;
var sWidth = cos.stripPx(Dom.getStyle(item, 'width'));
var sHeight = cos.stripPx(Dom.getStyle(item, 'height'));
var right = region['right'];
/* item on right half of window : or item on left half */
var marginLeft = ((right-sWidth) > halfWidth) ? ((right-sWidth) - halfWidth) : (0 - (halfWidth - (right-sWidth)));
var marginalAdjustments = {
'vert' : cos.stripPx(Dom.getStyle(item, 'marginBottom')),
'horz' : cos.stripPx(Dom.getStyle(item, 'marginRight'))
};
cos.setStyles(item, {
'top' : (region['bottom'] - sHeight - marginalAdjustments['vert']) + 'px',
'marginLeft' : (Math.floor(marginLeft) - marginalAdjustments['horz']) + 'px'
});
};
I am currently writing a blog article which covers this type of centering more in depth.
Labels: Javascript, JS, YUI

0 Comments:
Post a Comment
<< Home