2 * HoverScroll jQuery Plugin
4 * Make an unordered list scrollable by hovering the mouse over it
6 * @author RasCarlito <carl.ogren@gmail.com>
12 * FREE BEER LICENSE VERSION 1.02
14 * The free beer license is a license to give free software to you and free
15 * beer (in)to the author(s).
18 * Released: 09-12-2010 11:31pm
21 * ----------------------------------------------------
23 * 0.2.4 - Added "Right to Left" option, only works with horizontal lists
24 * - Optimization of arrows opacity control (Thanks to Josef Körner)
26 * 0.2.3 - Added fixed arrows option
27 * - Binded startMoving and stopMoving functions to the HoverScrolls HTML object for external access
30 * - Backward compatibility with jQuery 1.1.x
31 * - Added test file to the archive
32 * - Bug fix: The right arrow appeared when it wasn't necessary (thanks to <admin at unix dot am>)
35 * - Backward compatibility with jQuery 1.2.x (thanks to Andy Mull for compatibility with jQuery >= 1.2.4)
36 * - Added information to the debug log
38 * 0.2.0 Added some new features
39 * - Direction indicator arrows
40 * - Permanent override of default parameters
43 * - Hover zones did not cover the complete container
45 * note: The css file has not changed therefore it is still versioned 0.1.0
47 * 0.1.0 First release of the plugin. Supports:
48 * - Horizontal and vertical lists
49 * - Width and height control
50 * - Debug log (doesn't show useful information for the moment)
57 * @param params {Object} Parameter list
59 * vertical {Boolean}, // Vertical list or not ?
60 * width {Integer}, // Width of list container
61 * height {Integer}, // Height of list container
62 * arrows {Boolean}, // Show direction indicators or not
63 * arrowsOpacity {Float}, // Arrows maximum opacity
64 * fixedArrows {Boolean}, // Display arrows permenantly, this overrides arrowsOpacity option
65 * debug {Boolean} // Debug output in firebug console
68 $.fn.hoverscroll = function(params) {
69 if (!params) { params = {}; }
71 // Extend default parameters
72 // note: empty object to prevent params object from overriding default params object
73 params = $.extend({}, $.fn.hoverscroll.params, params);
75 // Loop through all the elements
76 this.each(function() {
80 $.log('[HoverScroll] Trying to create hoverscroll on element ' + this.tagName + '#' + this.id);
83 // wrap ul list with a div.listcontainer
84 if (params.fixedArrows) {
85 $this.wrap('<div class="fixed-listcontainer"></div>')
88 $this.wrap('<div class="listcontainer"></div>');
91 $this.addClass('listitem');
92 //.addClass('ui-helper-clearfix');
94 // store handle to listcontainer
95 var listctnr = $this.parent();
97 // wrap listcontainer with a div.hoverscroll
98 listctnr.wrap('<div class="ui-widget-content hoverscroll' +
99 (params.rtl && !params.vertical ? " rtl" : "") + '"></div>');
100 //listctnr.wrap('<div class="hoverscroll"></div>');
102 // store hoverscroll container
103 var ctnr = listctnr.parent();
105 var leftArrow, rightArrow, topArrow, bottomArrow;
107 // Add arrow containers
109 if (!params.vertical) {
110 if (params.fixedArrows) {
111 leftArrow = '<div class="fixed-arrow left"></div>';
112 rightArrow = '<div class="fixed-arrow right"></div>';
114 listctnr.before(leftArrow).after(rightArrow);
117 leftArrow = '<div class="arrow left"></div>';
118 rightArrow = '<div class="arrow right"></div>';
120 listctnr.append(leftArrow).append(rightArrow);
124 if (params.fixedArrows) {
125 topArrow = '<div class="fixed-arrow top"></div>';
126 bottomArrow = '<div class="fixed-arrow bottom"></div>';
128 listctnr.before(topArrow).after(bottomArrow);
131 topArrow = '<div class="arrow top"></div>';
132 bottomArrow = '<div class="arrow bottom"></div>';
134 listctnr.append(topArrow).append(bottomArrow);
139 // Apply parameters width and height
140 ctnr.width(params.width).height(params.height);
142 if (params.arrows && params.fixedArrows) {
143 if (params.vertical) {
144 topArrow = listctnr.prev();
145 bottomArrow = listctnr.next();
147 listctnr.width(params.width)
148 .height(params.height - (topArrow.height() + bottomArrow.height()));
151 leftArrow = listctnr.prev();
152 rightArrow = listctnr.next();
154 listctnr.height(params.height)
155 .width(params.width - (leftArrow.width() + rightArrow.width()));
159 listctnr.width(params.width).height(params.height);
164 if (!params.vertical) {
165 ctnr.addClass('horizontal');
167 // Determine content width
168 $this.children().each(function() {
169 $(this).addClass('item');
171 if ($(this).outerWidth) {
172 size += $(this).outerWidth(true);
175 // jQuery < 1.2.x backward compatibility patch
176 size += $(this).width() + parseInt($(this).css('padding-left')) + parseInt($(this).css('padding-right'))
177 + parseInt($(this).css('margin-left')) + parseInt($(this).css('margin-right'));
180 // Apply computed width to listcontainer
184 $.log('[HoverScroll] Computed content width : ' + size + 'px');
187 // Retrieve container width instead of using the given params.width to include padding
188 if (ctnr.outerWidth) {
189 size = ctnr.outerWidth();
192 // jQuery < 1.2.x backward compatibility patch
193 size = ctnr.width() + parseInt(ctnr.css('padding-left')) + parseInt(ctnr.css('padding-right'))
194 + parseInt(ctnr.css('margin-left')) + parseInt(ctnr.css('margin-right'));
198 $.log('[HoverScroll] Computed container width : ' + size + 'px');
202 ctnr.addClass('vertical');
204 // Determine content height
205 $this.children().each(function() {
206 $(this).addClass('item')
207 if ($(this).css('display') != "none"){
208 if ($(this).outerHeight) {
209 size += $(this).outerHeight(true);
212 // jQuery < 1.2.x backward compatibility patch
213 size += $(this).height() + parseInt($(this).css('padding-top')) + parseInt($(this).css('padding-bottom'))
214 + parseInt($(this).css('margin-bottom')) + parseInt($(this).css('margin-bottom'));
218 // Apply computed height to listcontainer
222 $.log('[HoverScroll] Computed content height : ' + size + 'px');
225 // Retrieve container height instead of using the given params.height to include padding
226 if (ctnr.outerHeight) {
227 size = ctnr.outerHeight();
230 // jQuery < 1.2.x backward compatibility patch
231 size = ctnr.height() + parseInt(ctnr.css('padding-top')) + parseInt(ctnr.css('padding-bottom'))
232 + parseInt(ctnr.css('margin-top')) + parseInt(ctnr.css('margin-bottom'));
236 $.log('[HoverScroll] Computed container height : ' + size + 'px');
240 // Define hover zones on container
241 var arrowHeight = $(this).parent().find('.arrow.top').height();
244 if(params.hoverZone == "gradual") {
246 1: {action: 'move', from: 0, to: 0.06 * size, direction: -1 , speed: 16},
247 2: {action: 'move', from: 0.06 * size, to: 0.15 * size, direction: -1 , speed: 8},
248 3: {action: 'move', from: 0.15 * size, to: 0.25 * size, direction: -1 , speed: 4},
249 4: {action: 'move', from: 0.25 * size, to: 0.4 * size, direction: -1 , speed: 2},
250 5: {action: 'stop', from: 0.4 * size, to: 0.6 * size},
251 6: {action: 'move', from: 0.6 * size, to: 0.75 * size, direction: 1 , speed: 2},
252 7: {action: 'move', from: 0.75 * size, to: 0.85 * size, direction: 1 , speed: 4},
253 8: {action: 'move', from: 0.85 * size, to: 0.94 * size, direction: 1 , speed: 8},
254 9: {action: 'move', from: 0.94 * size, to: size, direction: 1 , speed: 16}
258 1: {action: 'move', from: 0, to: arrowHeight, direction: -1 , speed: 16},
259 2: {action: 'move', from: size-arrowHeight, to: size, direction: 1 , speed: 16}
263 // Store default state values in container
264 ctnr[0].isChanging = false;
265 ctnr[0].direction = 0;
270 * Check mouse position relative to hoverscroll container
271 * and trigger actions according to the zone table
273 * @param x {Integer} Mouse X event position
274 * @param y {Integer} Mouse Y event position
276 function checkMouse(x, y) {
277 x = x - ctnr.offset().left;
278 y = y - ctnr.offset().top;
281 if (!params.vertical) {pos = x;}
285 if (pos >= zone[i].from && pos < zone[i].to) {
286 if (zone[i].action == 'move') {startMoving(zone[i].direction, zone[i].speed);}
294 * Sets the opacity of the left|top and right|bottom
295 * arrows according to the scroll position.
297 function setArrowOpacity() {
298 if (!params.arrows || params.fixedArrows) {return;}
303 if (!params.vertical) {
304 maxScroll = listctnr[0].scrollWidth - listctnr.width();
305 scroll = listctnr[0].scrollLeft;
308 maxScroll = listctnr[0].scrollHeight - listctnr.height();
309 scroll = listctnr[0].scrollTop;
311 var limit = params.arrowsOpacity;
313 // Optimization of opacity control by Josef Körner
314 // Initialize opacity; keep it between its extremas (0 and limit) we don't need to check limits after init
315 var opacity = (scroll / maxScroll) * limit;
317 if (opacity > limit) { opacity = limit; }
318 if (isNaN(opacity)) { opacity = 0; }
320 // Check if the arrows are needed
321 // Thanks to <admin at unix dot am> for fixing the bug that displayed the right arrow when it was not needed
324 $('div.arrow.left, div.arrow.top', ctnr).hide();
326 $('div.arrow.right, div.arrow.bottom', ctnr).show().css('opacity', limit);
330 if (opacity >= limit || maxScroll <= 0) {
331 $('div.arrow.right, div.arrow.bottom', ctnr).hide();
336 $('div.arrow.left, div.arrow.top', ctnr).show().css('opacity', opacity);
337 $('div.arrow.right, div.arrow.bottom', ctnr).show().css('opacity', (limit - opacity));
339 // End of optimization
344 * Start scrolling the list with a given speed and direction
346 * @param direction {Integer} Direction of the displacement, either -1|1
347 * @param speed {Integer} Speed of the displacement (20 being very fast)
349 function startMoving(direction, speed) {
350 if (ctnr[0].direction != direction) {
352 $.log('[HoverScroll] Starting to move. direction: ' + direction + ', speed: ' + speed);
356 ctnr[0].direction = direction;
357 ctnr[0].isChanging = true;
360 if (ctnr[0].speed != speed) {
362 $.log('[HoverScroll] Changed speed: ' + speed);
365 ctnr[0].speed = speed;
370 * Stop scrolling the list
372 function stopMoving() {
373 if (ctnr[0].isChanging) {
375 $.log('[HoverScroll] Stoped moving');
378 ctnr[0].isChanging = false;
379 ctnr[0].direction = 0;
381 clearTimeout(ctnr[0].timer);
386 * Move the list one step in the given direction and speed
389 if (ctnr[0].isChanging == false) {return;}
394 if (!params.vertical) {scrollSide = 'scrollLeft';}
395 else {scrollSide = 'scrollTop';}
397 listctnr[0][scrollSide] += ctnr[0].direction * ctnr[0].speed;
398 ctnr[0].timer = setTimeout(function() {move();}, 50);
401 // Initialize "right to left" option if specified
402 if (params.rtl && !params.vertical) {
403 listctnr[0].scrollLeft = listctnr[0].scrollWidth - listctnr.width();
406 // Bind actions to the hoverscroll container
408 // Bind checkMouse to the mousemove
409 .mousemove(function(e) {checkMouse(e.pageX, e.pageY);})
410 // Bind stopMoving to the mouseleave
411 // jQuery 1.2.x backward compatibility, thanks to Andy Mull!
412 // replaced .mouseleave(...) with .bind('mouseleave', ...)
413 .bind('mouseleave, mouseout', function() {stopMoving();});
415 // Bind the startMoving and stopMoving functions
416 // to the HTML object for external access
417 this.startMoving = startMoving;
418 this.stopMoving = stopMoving;
420 if (params.arrows && !params.fixedArrows) {
421 // Initialise arrow opacity
426 $('.arrowleft, .arrowright, .arrowtop, .arrowbottom', ctnr).hide();
434 // Backward compatibility with jQuery 1.1.x
436 $.fn.offset = function() {
437 this.left = this.top = 0;
439 if (this[0] && this[0].offsetParent) {
442 this.left += obj.offsetLeft;
443 this.top += obj.offsetTop;
444 } while (obj = obj.offsetParent);
454 * HoverScroll default parameters
456 $.fn.hoverscroll.params = {
457 vertical: false, // Display the list vertically or not
458 width: 400, // Width of the list
459 height: 50, // Height of the list
460 arrows: true, // Display arrows to the left and top or the top and bottom
461 arrowsOpacity: 0.7, // Maximum opacity of the arrows if fixedArrows
462 fixedArrows: false, // Fix the displayed arrows to the side of the list
463 rtl: false, // Set display mode to "Right to Left"
464 debug: false, // Display some debugging information in firebug console
465 hoverZone: 'gradual' // Display some debugging information in firebug console
469 $.fn.hoverscroll.destroy = function(el) {
470 var container = el.parent().parent(),
471 originalContainer = container.parent();
473 $(el).prependTo(originalContainer)
474 .removeClass('listitem')
475 .removeAttr("style");
479 //console.log(el.parent().parent());
483 * Log errors to consoles (firebug, opera) if exist, else uses alert()
486 try {console.log.apply(console, arguments);}
488 try {opera.postError.apply(opera, arguments);}
490 // alert(Array.prototype.join.call(arguments, " "));