/*global jQuery:false*/ /** * enscroll.js - jQuery plugin to add custom scrollbars to HTML block elements * Copyright (C) 2012 Jason T. Stoudt * Released under the MIT license * http://enscrollplugin.com/license.html **/ ;(function( $, win, doc, undefined ) { var defaultSettings = { verticalScrolling: true, horizontalScrolling: false, verticalScrollerSide: 'right', showOnHover: false, scrollIncrement: 20, minScrollbarLength: 40, pollChanges: true, drawCorner: true, drawScrollButtons: false, clickTrackToScroll: true, easingDuration: 500, propagateWheelEvent: true, verticalTrackClass: 'vertical-track', horizontalTrackClass: 'horizontal-track', horizontalHandleClass: 'horizontal-handle', verticalHandleClass: 'vertical-handle', scrollUpButtonClass: 'scroll-up-btn', scrollDownButtonClass: 'scroll-down-btn', scrollLeftButtonClass: 'scroll-left-btn', scrollRightButtonClass: 'scroll-right-btn', cornerClass: 'scrollbar-corner', zIndex: 1, addPaddingToPane: true, horizontalHandleHTML: '
', verticalHandleHTML: '
' }, preventDefault = function( event ) { if ( event.preventDefault ) { event.preventDefault(); } else { event.returnValue = false; } if ( event.stopPropagation ) { event.stopPropagation(); } else { event.cancelBubble = true; } }, // normalize requestAnimationFrame function and polyfill if needed reqAnimFrame = win.requestAnimationFrame || win.mozRequestAnimationFrame || win.webkitRequestAnimationFrame || win.oRequestAnimationFrame || win.msRequestAnimationFrame || function( f ) { setTimeout( f, 17 ); }, getComputedValue = function( elem, property ) { var w = $( elem ).css( property ), matches = /^-?\d+/.exec( w ); return matches ? +matches[0] : 0; }, testScrollHeight = function( nodeName ) { var styles = { width: '5px', height: '1px', overflow: 'hidden', padding: '8px 0', visibility: 'hidden', whiteSpace: 'pre-line', font: '10px/1 serif' }, pane = document.createElement( nodeName ), textNode = document.createTextNode( 'a\na' ), result, attr; for ( attr in styles ) { pane.style[ attr ] = styles[ attr ]; } pane.appendChild( textNode ); document.body.appendChild( pane ); result = ( pane.scrollHeight < 28 ); document.body.removeChild( pane ); return result; }, PI_OVER_2 = 0.5 * Math.PI, TEN_LOG2 = 10 * Math.log( 2 ), easeOutSin = function( c, d, t ) { var b = PI_OVER_2 / d, a = c * b; return Math.round( a * Math.cos( b * t )); }, easeOutExpo = function( c, d, t ) { return Math.round( c * TEN_LOG2 * Math.pow( 2, -10 * t / d + 1 ) / d ); }, timeFromPosition = function( b, c, d, x ) { return 2 * d / Math.PI * Math.asin(( x - b ) / c ); }, showScrollbars = function( scheduleHide ) { var data = $( this ).data( 'enscroll' ), that = this, settings = data.settings, hideScrollbars = function() { var data = $( this ).data( 'enscroll' ), settings = data.settings; if ( data && settings.showOnHover ) { if ( settings.verticalScrolling && $( data.verticalTrackWrapper ).is( ':visible' )) { $( data.verticalTrackWrapper ).stop().fadeTo( 275, 0 ); } if ( settings.horizontalScrolling && $( data.horizontalTrackWrapper ).is( ':visible' )) { $( data.horizontalTrackWrapper ).stop().fadeTo( 275, 0 ); } data._fadeTimer = null; } }; if ( data && settings.showOnHover ) { if ( data._fadeTimer ) { clearTimeout( data._fadeTimer ); } else { if ( settings.verticalScrolling && $( data.verticalTrackWrapper ).is( ':visible' )) { $( data.verticalTrackWrapper ).stop().fadeTo( 275, 1 ); } if ( settings.horizontalScrolling && $( data.horizontalTrackWrapper ).is( ':visible' )) { $( data.horizontalTrackWrapper ).stop().fadeTo( 275, 1 ); } } if ( scheduleHide !== false ) { data._fadeTimer = setTimeout(function() { hideScrollbars.call( that ); }, 1750); } } }, scrollVertical = function( pane, dy ) { var $pane = $( pane ), data = $pane.data( 'enscroll' ), y0 = $pane.scrollTop(); if ( data && data.settings.verticalScrolling ) { $pane.scrollTop( y0 + dy ); if ( data.settings.showOnHover ) { showScrollbars.call( pane ); } } }, scrollHorizontal = function( pane, dx ) { var $pane = $( pane ), data = $pane.data( 'enscroll' ), x0 = $pane.scrollLeft(); if ( data && data.settings.horizontalScrolling ) { $pane.scrollLeft( x0 + dx ); if ( data.settings.showOnHover ) { showScrollbars.call( pane ); } } }, scrollAnimate = function( $pane ) { var data = $pane.data( 'enscroll' ), d = data._duration, c, curPos, t; if ( data._scrollingX === true ) { c = data._endX - data._startX; if ( c === 0 ) { data._scrollingX = false; } else { curPos = $pane.scrollLeft(); t = timeFromPosition( data._startX, c, d, curPos ); if ( c > 0 ) { if ( curPos >= data._endX || curPos < data._startX ) { data._scrollingX = false; } else { scrollHorizontal( $pane, Math.max( 1, easeOutSin( c, d, t ))); reqAnimFrame( function() { scrollAnimate( $pane ); }); } } else { if ( curPos <= data._endX || curPos > data._startX ) { data._scrollingX = false; } else { scrollHorizontal( $pane, Math.min( -1, easeOutSin( c, d, t ))); reqAnimFrame( function() { scrollAnimate( $pane ); }); } } } } if ( data._scrollingY === true ) { c = data._endY - data._startY; if ( c === 0 ) { data._scrollingY = false; } else { curPos = $pane.scrollTop(); t = timeFromPosition( data._startY, c, d, curPos ); if ( c > 0 ) { if ( curPos >= data._endY || curPos < data._startY ) { data._scrollingY = false; } else { scrollVertical( $pane, Math.max( 1, easeOutSin( c, d, t ))); reqAnimFrame( function() { scrollAnimate( $pane ); }); } } else { if ( curPos <= data._endY || curPos > data._startY ) { data._scrollingY = false; } else { scrollVertical( $pane, Math.min( -1, easeOutSin( c, d, t ))); reqAnimFrame( function() { scrollAnimate( $pane ); }); } } } } }, paneScrolled = function() { var $this = $( this ), data = $this.data( 'enscroll' ), handle, track, pct; if ( data ) { if ( data.settings.verticalScrolling ) { track = $( data.verticalTrackWrapper ).find( '.enscroll-track' )[0]; handle = track.firstChild; pct = $this.scrollTop() / ( this.scrollHeight - (data._scrollHeightNoPadding ? $this.height() : $this.innerHeight())); pct = isNaN( pct ) ? 0 : pct; handle.style.top = ( pct * ( $( track ).height() - $( handle ).outerHeight() )) + 'px'; } if ( data.settings.horizontalScrolling ) { track = $( data.horizontalTrackWrapper ).find( '.enscroll-track' )[0]; handle = track.firstChild; pct = $this.scrollLeft() / ( this.scrollWidth - $this.innerWidth() ); pct = isNaN( pct ) ? 0 : pct; handle.style.left = ( pct * ( $( track ).width() - $( handle ).innerWidth() )) + 'px'; } } }, startVerticalDrag = function( event ) { // only handle events for left mouse button dragging if ( event.which !== 1 ) { return; } var pane = event.data.pane, $pane = $( pane ), data = $pane.data( 'enscroll' ), dragging = true, $track, handle, handleY, oldHandleY, mouseYOffset, trackYOffset, bodyCursor, trackDiff, paneDiff, moveHandle = function() { if ( !dragging ) { return; } if ( handleY !== oldHandleY ) { if ( !data._scrollingY ) { data._scrollingY = true; data._startY = $pane.scrollTop(); reqAnimFrame( function() { scrollAnimate( $pane ); }); } handle.style.top = handleY + 'px'; data._endY = handleY * paneDiff / trackDiff; oldHandleY = handleY; } reqAnimFrame( moveHandle ); if ( data.settings.showOnHover ) { showScrollbars.call( pane ); } }, moveDrag = function( event ) { if ( dragging ) { handleY = event.clientY - trackYOffset - mouseYOffset; handleY = Math.min( handleY < 0 ? 0 : handleY, trackDiff ); } return false; }, endDrag = function() { dragging = false; doc.body.style.cursor = bodyCursor; this.style.cursor = ''; $track.removeClass( 'dragging' ); $( doc.body ) .off( 'mousemove.enscroll.vertical' ) .off( 'mouseup.enscroll.vertical' ); $( doc ).off( 'mouseout.enscroll.vertical' ); $pane.on( 'scroll.enscroll.pane', function( event ) { paneScrolled.call( this, event ); }); return false; }; $track = $( data.verticalTrackWrapper ).find( '.enscroll-track' ); handle = $track.children().first()[0]; handleY = parseInt( handle.style.top, 10 ); paneDiff = pane.scrollHeight - (data._scrollHeightNoPadding ? $(pane).height() : $(pane).innerHeight()); mouseYOffset = event.clientY - $( handle ).offset().top; trackDiff = $track.height() - $( handle ).outerHeight(); trackYOffset = $track.offset().top; $pane.off( 'scroll.enscroll.pane' ); $( doc.body ).on({ 'mousemove.enscroll.vertical': moveDrag, 'mouseup.enscroll.vertical': function( event ) { endDrag.call( handle, event ); } }); $( doc ).on( 'mouseout.enscroll.vertical', function( event ) { if ( event.target.nodeName && event.target.nodeName.toUpperCase() === 'HTML' ) { endDrag.call( handle, event ); } }); if ( !$track.hasClass( 'dragging' ) ) { $track.addClass( 'dragging' ); bodyCursor = $( doc.body ).css( 'cursor' ); this.style.cursor = doc.body.style.cursor = 'ns-resize'; } reqAnimFrame( moveHandle ); return false; }, startHorizontalDrag = function( event ) { // dragging the scrollbar handle only works with left mouse button if ( event.which !== 1 ) { return; } var pane = event.data.pane, $pane = $( pane ), data = $( pane ).data( 'enscroll' ), dragging = true, $track, handle, handleX, oldHandleX, paneDiff, mouseXOffset, trackXOffset, bodyCursor, trackDiff, moveHandle = function() { if ( !dragging ) { return; } if ( handleX !== oldHandleX ) { if ( !data._scrollingX ) { data._scrollingX = true; data._startX = $pane.scrollLeft(); reqAnimFrame( function() { scrollAnimate( $pane ); }); } handle.style.left = handleX + 'px'; data._endX = handleX * paneDiff / trackDiff; oldHandleX = handleX; } reqAnimFrame( moveHandle ); if ( data.settings.showOnHover ) { showScrollbars.call( pane ); } }, moveDrag = function( event ) { if ( dragging ) { handleX = event.clientX - trackXOffset - mouseXOffset; handleX = Math.min( handleX < 0 ? 0 : handleX, trackDiff ); } return false; }, endDrag = function() { dragging = false; $track.removeClass('dragging'); doc.body.style.cursor = bodyCursor; this.style.cursor = ''; $track.removeClass( 'dragging' ); $( doc.body ) .off( 'mousemove.enscroll.horizontal' ) .off( 'mouseup.enscroll.horizontal' ); $( doc ).off ( 'mouseout.enscroll.horizontal' ); $pane.on( 'scroll.enscroll.pane', function( event ) { paneScrolled.call( this, event ); }); return false; }; $track = $( data.horizontalTrackWrapper ).find( '.enscroll-track' ); handle = $track.children().first()[0]; handleX = parseInt( handle.style.left, 10 ); paneDiff = pane.scrollWidth - $( pane ).innerWidth(); mouseXOffset = event.clientX - $( handle ).offset().left; trackDiff = $track.width() - $( handle ).outerWidth(); trackXOffset = $track.offset().left; $pane.off( 'scroll.enscroll.pane' ); $( doc.body ).on({ 'mousemove.enscroll.horizontal': moveDrag, 'mouseup.enscroll.horizontal': function( event ) { endDrag.call( handle, event ); } }); $( doc ).on( 'mouseout.enscroll.horizontal', function( event ) { if ( event.target.nodeName && event.target.nodeName.toUpperCase() === 'HTML' ) { endDrag.call( handle, event ); } }); if ( !$track.hasClass( 'dragging' ) ) { $track.addClass( 'dragging' ); bodyCursor = $( 'body' ).css( 'cursor' ); this.style.cursor = doc.body.style.cursor = 'ew-resize'; } reqAnimFrame( moveHandle ); return false; }, scrollAnimateHorizontal = function( $pane, delta ) { var data = $pane.data( 'enscroll' ), curPos = $pane.scrollLeft(), scrollMax = $pane[0].scrollWidth - $pane.innerWidth(); if ( !data.settings.horizontalScrolling || data._scrollingY ) { return false; } if ( !data._scrollingX ) { data._scrollingX = true; data._startX = curPos; data._endX = data._startX; reqAnimFrame( function() { scrollAnimate( $pane ); }); } data._endX = delta > 0 ? Math.min( curPos + delta, scrollMax ) : Math.max( 0, curPos + delta ); return delta < 0 && curPos > 0 || delta > 0 && curPos < scrollMax; }, scrollAnimateVertical = function( $pane, delta ) { var data = $pane.data( 'enscroll' ), curPos = $pane.scrollTop(), scrollMax = $pane[0].scrollHeight - (data._scrollHeightNoPadding ? $pane.height() : $pane.innerHeight()); if ( !data.settings.verticalScrolling || data._scrollingX ) { return false; } if ( !data._scrollingY ) { data._scrollingY = true; data._startY = curPos; data._endY = data._startY; reqAnimFrame( function() { scrollAnimate( $pane ); }); } data._endY = delta > 0 ? Math.min( curPos + delta, scrollMax ) : Math.max( 0, curPos + delta ); return delta < 0 && curPos > 0 || delta > 0 && curPos < scrollMax; }, mouseScroll = function( event ) { var $pane = $( this ), data = $pane.data( 'enscroll' ), scrollIncrement = data.settings.scrollIncrement, deltaX = 'deltaX' in event ? -event.deltaX : 'wheelDeltaX' in event ? event.wheelDeltaX : 0, deltaY = 'deltaY' in event ? -event.deltaY : 'wheelDeltaY' in event ? event.wheelDeltaY : 'wheelDelta' in event ? event.wheelDelta : 0, delta; if ( Math.abs( deltaX ) > Math.abs( deltaY ) && deltaX !== 0 ) { delta = ( deltaX > 0 ? -scrollIncrement : scrollIncrement ) << 2; if ( scrollAnimateHorizontal( $pane, delta ) || !data.settings.propagateWheelEvent ) { preventDefault( event ); } } else if ( deltaY !== 0 ) { delta = ( deltaY > 0 ? -scrollIncrement : scrollIncrement ) << 2; if ( scrollAnimateVertical( $pane, delta ) || !data.settings.propagateWheelEvent ) { preventDefault( event ); } } }, keyHandler = function( event ) { var $this = $( this ), data = $this.data( 'enscroll' ), scrollIncrement; // dont' have key events if this element is a user-input element if (/(input)|(select)|(textarea)/i.test( this.nodeName )) { return; } // don't handle events that have just bubbled up if ( event.target === this && data ) { scrollIncrement = data.settings.scrollIncrement; switch( event.keyCode ) { case 32: // space case 34: // page down scrollAnimateVertical( $this, $this.height() ); return false; case 33: // page up scrollAnimateVertical( $this, -$this.height() ); return false; case 35: // end scrollAnimateVertical( $this, this.scrollHeight ); return false; case 36: // home scrollAnimateVertical( $this, -this.scrollHeight ); return false; case 37: // left scrollAnimateHorizontal( $this, -scrollIncrement ); return false; case 38: // up scrollAnimateVertical( $this, -scrollIncrement ); return false; case 39: // right scrollAnimateHorizontal( $this, scrollIncrement ); return false; case 40: // down scrollAnimateVertical( $this, scrollIncrement ); return false; } return true; } }, dragHandler = function() { var pane = this, settings = $( pane ).data( 'enscroll' ).settings, dragging = true, deltaX = 0, deltaY = 0, paneTop = $( pane ).offset().top, paneBottom = paneTop + $( pane ).outerHeight(), paneLeft = $( pane ).offset().left, paneRight = paneLeft + $( pane ).outerWidth(), dragMove = function( event ) { var x = event.pageX, y = event.pageY; deltaX = x < paneLeft ? x - paneLeft : x > paneRight ? x - paneRight : 0; deltaY = y < paneTop ? y - paneTop : y > paneBottom ? y - paneBottom : 0; }, dragPoll = function() { if ( settings.horizontalScrolling && deltaX ) { scrollHorizontal(pane, parseInt( deltaX / 4, 10 )); } if ( settings.verticalScrolling && deltaY ) { scrollVertical( pane, parseInt( deltaY / 4, 10 )); } if ( dragging ) { reqAnimFrame( dragPoll ); } }, dragEnd = function() { dragging = false; $( doc ) .off( 'mousemove.enscroll.pane' ) .off( 'mouseup.enscroll.pane' ); }; reqAnimFrame( dragPoll ); $( doc ).on({ 'mousemove.enscroll.pane': dragMove, 'mouseup.enscroll.pane': dragEnd }); }, touchStart = function( event ) { var touchX, touchY, touchAxis, touchX0, touchY0, touchStarted, touchDelta, pane = this, touchMove = function( event ) { touchX = event.touches[0].clientX; touchY = event.touches[0].clientY; if ( !touchAxis ) { touchAxis = touchY === touchY0 && touchX === touchX0 ? undefined : Math.abs( touchY0 - touchY ) > Math.abs( touchX0 - touchX ) ? 'y' : 'x'; } preventDefault( event ); }, touchPoll = function() { if ( !touchStarted ) { return; } if ( touchAxis === 'y' ) { scrollVertical( pane, touchY0 - touchY ); touchDelta = touchY0 - touchY; touchY0 = touchY; } else if ( touchAxis === 'x' ) { scrollHorizontal( pane, touchX0 - touchX ); touchDelta = touchX0 - touchX; touchX0 = touchX; } reqAnimFrame( touchPoll ); }, touchEnd = function() { var t = 0, d = Math.abs( touchDelta * 1.5 ); this.removeEventListener( 'touchmove', touchMove, false ); this.removeEventListener( 'touchend', touchEnd, false ); touchStarted = false; reqAnimFrame( function touchFinish() { var dx; if ( t === d || touchStarted ) { return; } dx = easeOutExpo( touchDelta, d, t ); if ( !isNaN( dx ) && dx !== 0 ) { t += 1; if ( touchAxis === 'y' ) { scrollVertical( pane, dx ); } else { scrollHorizontal( pane, dx ); } reqAnimFrame( touchFinish ); } }); }; if ( event.touches.length === 1 ) { touchX0 = event.touches[0].clientX; touchY0 = event.touches[0].clientY; touchStarted = true; this.addEventListener( 'touchmove', touchMove, false ); this.addEventListener( 'touchend', touchEnd, false ); reqAnimFrame( touchPoll ); } }, api = { reposition: function() { return this.each(function() { var $this = $( this ), data = $this.data( 'enscroll' ), positionElem = function( elem, x, y ) { elem.style.left = x + 'px'; elem.style.top = y + 'px'; }, corner, trackWrapper, offset; if ( data ) { offset = $this.position(); corner = data.corner; if ( data.settings.verticalScrolling ) { trackWrapper = data.verticalTrackWrapper; positionElem( trackWrapper, ( data.settings.verticalScrollerSide === 'right' ? offset.left + $this.outerWidth() - $( trackWrapper ).width() - getComputedValue( this, 'border-right-width' ) : offset.left + getComputedValue( this, 'border-left-width' )), offset.top + getComputedValue( this, 'border-top-width' )); } if ( data.settings.horizontalScrolling ) { trackWrapper = data.horizontalTrackWrapper; positionElem( trackWrapper, offset.left + getComputedValue( this, 'border-left-width' ), offset.top + $this.outerHeight() - $( trackWrapper ).height() - getComputedValue( this, 'border-bottom-width' )); } if ( corner ) { positionElem( corner, offset.left + $this.outerWidth() - $( corner ).outerWidth() - getComputedValue( this, 'border-right-width' ), offset.top + $this.outerHeight() - $( corner ).outerHeight() - getComputedValue( this, 'border-bottom-width' )); } } }); }, resize: function() { return this.each(function() { var $this = $( this ), data = $this.data( 'enscroll' ), settings, paneHeight, paneWidth, trackWrapper, pct, track, trackWidth, trackHeight, $scrollUpBtn, $scrollDownBtn, $scrollLeftBtn, $scrollRightBtn, handle, handleWidth, handleHeight, prybar; if ( !data ) { return true; } settings = data.settings; if ( $this.is( ':visible' )) { if ( settings.verticalScrolling ) { trackWrapper = data.verticalTrackWrapper; paneHeight = $this.innerHeight(); pct = paneHeight / this.scrollHeight; track = $( trackWrapper ).find( '.enscroll-track' )[0]; $scrollUpBtn = $( trackWrapper ).find( '.' + settings.scrollUpButtonClass ); $scrollDownBtn = $(trackWrapper).find( '.' + settings.scrollDownButtonClass ); trackHeight = settings.horizontalScrolling ? paneHeight - $( data.horizontalTrackWrapper ).find( '.enscroll-track' ).outerHeight() : paneHeight; trackHeight -= $( track ).outerHeight() - $( track ).height() + $scrollUpBtn.outerHeight() + $scrollDownBtn.outerHeight(); handle = track.firstChild; handleHeight = Math.max( pct * trackHeight, settings.minScrollbarLength ); handleHeight -= $( handle ).outerHeight() - $( handle ).height(); // hide the track first -- this causes less reflows and // fixes an IE8 bug that prevents background images // from being redrawn trackWrapper.style.display = 'none'; track.style.height = trackHeight + 'px'; handle.style.height = handleHeight + 'px'; if ( pct < 1 ) { pct = $this.scrollTop() / ( this.scrollHeight - $this.height() ); handle.style.top = ( pct * ( trackHeight - handleHeight ) ) + 'px'; trackWrapper.style.display = 'block'; } } if ( settings.horizontalScrolling ) { trackWrapper = data.horizontalTrackWrapper; paneWidth = $this.innerWidth(); pct = paneWidth / this.scrollWidth; track = $( trackWrapper ).find( '.enscroll-track' )[0]; $scrollLeftBtn = $( trackWrapper ).find( '.' + settings.scrollLeftButtonClass ); $scrollRightBtn = $( trackWrapper ).find( '.' + settings.scrollRightButtonClass ); trackWidth = settings.verticalScrolling ? paneWidth - $( data.verticalTrackWrapper ).find( '.enscroll-track' ).outerWidth() : paneWidth; trackWidth -= $( track ).outerWidth() - $( track ).width() + $scrollLeftBtn.outerWidth() + $scrollRightBtn.outerWidth(); handle = track.firstChild; handleWidth = Math.max( pct * trackWidth, settings.minScrollbarLength); handleWidth -= $( handle ).outerWidth() - $( handle ).width(); // see comment above trackWrapper.style.display = 'none'; track.style.width = trackWidth + 'px'; handle.style.width = handleWidth + 'px'; if ( pct < 1 ) { pct = $this.scrollLeft() / ( this.scrollWidth - $this.width() ); handle.style.left = ( pct * ( trackWidth - handleWidth ) ) + 'px'; trackWrapper.style.display = 'block'; } if ( data._prybar ) { prybar = data._prybar; this.removeChild( prybar ); if ( settings.verticalScrolling ) { prybar.style.width = ( this.scrollWidth + $( data.verticalTrackWrapper ).find( '.enscroll-track' ).outerWidth()) + 'px'; this.appendChild( prybar ); } } } if ( data.corner ) { data.corner.style.display = data.verticalTrackWrapper && data.horizontalTrackWrapper && $( data.verticalTrackWrapper ).is( ':visible' ) && $( data.horizontalTrackWrapper ).is( ':visible' ) ? '' : 'none'; } } else { if ( settings.verticalScrolling ) { data.verticalTrackWrapper.style.display = 'none'; } if ( settings.horizontalScrolling ) { data.horizontalTrackWrapper.style.display = 'none'; } if ( data.corner ) { data.corner.style.display = 'none'; } } }); }, startPolling: function() { return this.each(function() { var data = $( this ).data( 'enscroll' ), pane = this, $pane = $( pane ), paneWidth = -1, paneHeight = -1, paneScrollWidth = -1, paneScrollHeight = -1, paneOffset, paneChangeListener = function() { if ( data.settings.pollChanges ) { var sw = pane.scrollWidth, sh = pane.scrollHeight, pw = $pane.width(), ph = $pane.height(), offset = $pane.offset(); if ( data.settings.verticalScrolling && ( ph !== paneHeight || sh !== paneScrollHeight ) || data.settings.horizontalScrolling && ( pw !== paneWidth || sw !== paneScrollWidth ) ) { paneScrollWidth = sw; paneScrollHeight = sh; api.resize.call( $pane ); } if ( paneOffset.left !== offset.left || paneOffset.top !== offset.top || pw !== paneWidth || ph !== paneHeight ) { paneOffset = offset; paneWidth = pw; paneHeight = ph; api.reposition.call( $pane ); } setTimeout( paneChangeListener, 350 ); } }; if ( data ) { data.settings.pollChanges = true; paneScrollHeight = pane.scrollHeight; paneScrollWidth = pane.scrollWidth; paneOffset = $pane.offset(); paneChangeListener(); } }); }, stopPolling: function() { return this.each(function() { var data = $( this ).data( 'enscroll' ); if ( data ) { data.settings.pollChanges = false; } }); }, destroy: function() { return this.each(function() { var $this = $( this ), data = $this.data( 'enscroll' ), trackWrapper, mouseScrollHandler; if ( data ) { api.stopPolling.call( $this ); mouseScrollHandler = data._mouseScrollHandler; if ( data.settings.verticalScrolling ) { trackWrapper = data.verticalTrackWrapper; $( trackWrapper ).remove(); trackWrapper = null; } if ( data.settings.horizontalScrolling ) { trackWrapper = data.horizontalTrackWrapper; $( trackWrapper ).remove(); trackWrapper = null; } // clear the fade timer to prevent an error being thrown // when the plugin object is destroyed while the fading // scrollbar is visible - shoutout to gpurves if ( data._fadeTimer ) { clearTimeout( data._fadeTimer ); } if ( data.corner ) { $( data.corner ).remove(); } if ( data._prybar && data._prybar.parentNode && data._prybar.parentNode === this ) { $( data._prybar ).remove(); } this.setAttribute( 'style', data._style || '' ); if ( !data._hadTabIndex ) { $this.removeAttr( 'tabindex' ); } $this .off( 'scroll.enscroll.pane' ) .off( 'keydown.enscroll.pane' ) .off( 'mouseenter.enscroll.pane' ) .off( 'mousedown.enscroll.pane' ) .data( 'enscroll', null ); if ( this.removeEventListener ) { this.removeEventListener( 'wheel', mouseScrollHandler, false ); this.removeEventListener( 'mousewheel', mouseScrollHandler, false ); this.removeEventListener( 'touchstart', touchStart, false ); } else if ( this.detachEvent ) { this.detachEvent( 'onmousewheel', mouseScrollHandler ); } $( win ).off( 'resize.enscroll.window' ); } }); } }; $.fn.enscroll = function( opts ) { var settings; // handle API method calls if ( api[opts] ) { return api[opts].call( this ); } // otherwise, initialize the enscroll element // use default settings, and overwrite defaults with options passed in settings = $.extend( {}, defaultSettings, opts ); return this.each( function() { // don't apply this plugin when both scrolling settings are false if ( !settings.verticalScrolling && !settings.horizontalScrolling ) { return; } var $this = $( this ), pane = this, oldStyle = $this.attr( 'style' ), hadTabIndex = true, horizontalTrackWrapper, verticalTrackWrapper, horizontalTrack, verticalTrack, horizontalHandle, verticalHandle, verticalUpButton, verticalDownButton, horizontalLeftButton, horizontalRightButton, trackHeight, trackWidth, corner, outline, tabindex, outlineWidth, prybar, paddingSide, trackWrapperCSS = { 'position': 'absolute', 'z-index': settings.zIndex, 'margin': 0, 'padding': 0 }, // closures to bind events to handlers mouseScrollHandler = function( event ) { mouseScroll.call( pane, event ); }, addHandleHTML = function( handle, html ) { if ( typeof html === 'string' ) { $( handle ).html( html ); } else { handle.appendChild( html ); } }; // if we want vertical scrolling, create and initialize // the horizontal scrollbar and its components if ( settings.verticalScrolling ) { verticalTrackWrapper = doc.createElement( 'div' ); verticalTrack = doc.createElement( 'div' ); verticalHandle = doc.createElement( 'a' ); $( verticalTrack ) .css( 'position', 'relative' ) .addClass( 'enscroll-track' ) .addClass( settings.verticalTrackClass ) .appendTo( verticalTrackWrapper ); if ( settings.drawScrollButtons ) { verticalUpButton = doc.createElement( 'a' ); verticalDownButton = doc.createElement( 'a' ); $( verticalUpButton ) .css({ 'display': 'block', 'text-decoration': 'none' }) .attr( 'href', '' ) .html( ' ' ) .addClass( settings.scrollUpButtonClass ) .on( 'click', function() { scrollVertical( pane, -settings.scrollIncrement ); return false; }) .insertBefore( verticalTrack ); $( verticalDownButton ) .css({ 'display': 'block', 'text-decoration': 'none' }) .attr( 'href', '' ) .html( ' ' ) .on( 'click', function() { scrollVertical( pane, settings.scrollIncrement ); return false; }) .addClass( settings.scrollDownButtonClass ) .appendTo( verticalTrackWrapper ); } if ( settings.clickTrackToScroll ) { $( verticalTrack ).on( 'click', function( event ) { if ( event.target === this ) { scrollAnimateVertical( $this, event.pageY > $( verticalHandle ).offset().top ? $this.height() : -$this.height() ); } }); } $( verticalHandle ) .css({ 'position': 'absolute', 'z-index': 1 }) .attr( 'href', '' ) .addClass( settings.verticalHandleClass ) .mousedown( { pane: this }, startVerticalDrag ) .click( function() { return false; }) .appendTo( verticalTrack ); addHandleHTML( verticalHandle, settings.verticalHandleHTML ); $( verticalTrackWrapper ) .css( trackWrapperCSS ) .insertAfter( this ); if ( settings.showOnHover ) { $( verticalTrackWrapper ) .css( 'opacity', 0 ) .on( 'mouseover.enscroll.vertical', function() { showScrollbars.call( pane, false ); }) .on( 'mouseout.enscroll.vertical', function() { showScrollbars.call( pane ); }); } trackWidth = $( verticalTrack ).outerWidth(); // move the content in the pane over to make room for // the vertical scrollbar if ( settings.addPaddingToPane ) { if ( settings.verticalScrollerSide === 'right' ) { paddingSide = { 'padding-right': ( getComputedValue( this, 'padding-right' ) + trackWidth ) + 'px' }; } else { paddingSide = { 'padding-left': ( getComputedValue( this, 'padding-left' ) + trackWidth ) + 'px' }; } $this.css($.extend({ 'width': ( $this.width() - trackWidth ) + 'px' }, paddingSide)); } try { outlineWidth = parseInt( $this.css( 'outline-width' ), 10 ); if (( outlineWidth === 0 || isNaN( outlineWidth )) && $this.css( 'outline-style') === 'none' ) { $this.css( 'outline', 'none' ); } } catch( ex ) { $this.css( 'outline', 'none' ); } } // if we want horizontal scrolling, create the elements for and // initialize the horizontal track and handle if ( settings.horizontalScrolling ) { horizontalTrackWrapper = doc.createElement( 'div' ); horizontalTrack = doc.createElement( 'div' ); horizontalHandle = doc.createElement( 'a' ); $( horizontalTrack ) .css({ 'position': 'relative', 'z-index': 1 }) .addClass( 'enscroll-track' ) .addClass( settings.horizontalTrackClass ) .appendTo( horizontalTrackWrapper ); if ( settings.drawScrollButtons ) { horizontalLeftButton = doc.createElement( 'a' ); horizontalRightButton = doc.createElement( 'a' ); $( horizontalLeftButton ) .css( 'display', 'block' ) .attr( 'href', '' ) .on( 'click', function() { scrollHorizontal( pane, -settings.scrollIncrement ); return false; }) .addClass( settings.scrollLeftButtonClass ) .insertBefore( horizontalTrack ); $( horizontalRightButton ) .css( 'display', 'block' ) .attr( 'href', '' ) .on( 'click', function() { scrollHorizontal( pane, settings.scrollIncrement ); return false; }) .addClass( settings.scrollRightButtonClass ) .appendTo( horizontalTrackWrapper ); } if ( settings.clickTrackToScroll ) { $( horizontalTrack).on( 'click', function( event ) { if ( event.target === this ) { scrollAnimateHorizontal( $this, event.pageX > $(horizontalHandle).offset().left ? $this.width() : -$this.width() ); } }); } $( horizontalHandle ) .css({ 'position': 'absolute', 'z-index': 1 }) .attr( 'href', '' ) .addClass( settings.horizontalHandleClass ) .click( function() { return false; }) .mousedown( { pane: this }, startHorizontalDrag ) .appendTo( horizontalTrack ); addHandleHTML( horizontalHandle, settings.horizontalHandleHTML ); $( horizontalTrackWrapper ) .css( trackWrapperCSS ) .insertAfter( this ); if ( settings.showOnHover ) { $( horizontalTrackWrapper ) .css( 'opacity', 0 ) .on( 'mouseover.enscroll.horizontal', function() { showScrollbars.call( pane, false ); }) .on( 'mouseout.enscroll.horizontal', function() { showScrollbars.call( pane ); }); } trackHeight = $( horizontalTrack ).outerHeight(); if ( settings.addPaddingToPane ) { $this.css({ 'height': ( $this.height() - trackHeight ) + 'px', 'padding-bottom': ( parseInt( $this.css( 'padding-bottom' ), 10 ) + trackHeight ) + 'px' }); } // we need to add an element to the pane in order to // stretch to the scrollWidth of the pane so the content // scrolls horizontally beyond the vertical scrollbar if ( settings.verticalScrolling ) { prybar = document.createElement( 'div' ); $( prybar ) .css({ 'width': '1px', 'height': '1px', 'visibility': 'hidden', 'padding': 0, 'margin': '-1px' }) .appendTo( this ); } } if ( settings.verticalScrolling && settings.horizontalScrolling && settings.drawCorner ) { corner = doc.createElement( 'div' ); $( corner ) .addClass( settings.cornerClass ) .css( trackWrapperCSS ) .insertAfter( this ); } // add a tabindex attribute to the pane if it doesn't already have one // if the element does not have a tabindex in IE6, undefined is returned, // all other browsers return an empty string tabindex = $this.attr( 'tabindex' ); if ( !tabindex ) { $this.attr( 'tabindex', 0 ); hadTabIndex = false; } // if the outline style is not specified in IE6/7/8, null is returned // all other browsers return an empty string try { outline = $this.css( 'outline' ); if ( !outline || outline.length < 1 ) { $this.css( 'outline', 'none' ); } } catch(ex) { $this.css( 'outline', 'none' ); } // register an handler that listens for the pane to scroll, and // sync the scrollbars' positions $this .on({ 'scroll.enscroll.pane': function( event ) { paneScrolled.call( this, event ); }, 'keydown.enscroll.pane': keyHandler, 'mousedown.enscroll.pane': dragHandler }) .css( 'overflow', 'hidden' ) // store the data we need for handling events and destruction .data( 'enscroll', { settings: settings, horizontalTrackWrapper: horizontalTrackWrapper, verticalTrackWrapper: verticalTrackWrapper, corner: corner, _prybar: prybar, _mouseScrollHandler: mouseScrollHandler, _hadTabIndex: hadTabIndex, _style: oldStyle, _scrollingX: false, _scrollingY: false, _startX: 0, _startY: 0, _endX: 0, _endY: 0, _duration: parseInt( settings.easingDuration / 16.66666, 10 ), _scrollHeightNoPadding: testScrollHeight( this.nodeName ) }); // reposition the scrollbars if the window is resized $( win ).on( 'resize.enscroll.window', function() { api.reposition.call( $this ); }); // if showOnHover is set, attach the hover listeners if ( settings.showOnHover ) { $this.on( 'mouseenter.enscroll.pane', function() { showScrollbars.call( this ); }); } // listen for mouse wheel and touch events and scroll appropriately if ( this.addEventListener ) { if ( 'onwheel' in this || 'WheelEvent' in win && navigator.userAgent.toLowerCase().indexOf( 'msie' ) >= 0 ) { this.addEventListener( 'wheel', mouseScrollHandler, false ); } else if ( 'onmousewheel' in this ) { this.addEventListener( 'mousewheel', mouseScrollHandler, false ); } this.addEventListener( 'touchstart', touchStart, false ); } else if ( this.attachEvent ) { // oldie love this.attachEvent( 'onmousewheel', mouseScrollHandler ); } // start polling for changes in dimension and position if ( settings.pollChanges ) { api.startPolling.call( $this ); } api.resize.call( $this ); api.reposition.call( $this ); }); }; }( jQuery, window, document ));