Last active
August 29, 2015 14:07
-
-
Save josimard/ae00cc2512707c8c0cd5 to your computer and use it in GitHub Desktop.
jQuery window scrolling utilities (to scroll window and check scroll position of elements)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Window scrolling SINGLETON utilities (AMD wrapped) | |
* | |
* IMPORTANT: for ScrollUtil.scrollTo to work, the height of the document must not be fixed. | |
* | |
* So instead of css "body, html {height:100%}" use "body, html {min-height:100%}" | |
* | |
* dependencies: | |
* - jQuery | |
* | |
* @author jo@josimard.com | |
* | |
* Updates @ https://gist.github.com/josimard/ae00cc2512707c8c0cd5 | |
*/ | |
define([], function () | |
{ | |
// Static variables | |
var bodyElement; | |
// Constructor | |
function ScrollUtil() | |
{ | |
// Pubic global properties | |
this.offsetY = 0; | |
// ScrollUtil.minScrollFromTop | |
// Values under this one will make the window scroll to top | |
this.minScrollFromTop = 300; | |
this.defaultEasing = (jQuery.easing["easeInOutQuint"]) ? "easeInOutQuint" : "swing"; | |
this.defaultTime = 400; | |
} | |
/** | |
* | |
* ScrollUtil.scrollTo() | |
* | |
* Scrolls the window to an element position using jQuery.animate | |
* Example use: | |
* ScrollUtil.scrollTo($("#element"), {time:1200}); | |
* | |
* To Scroll to the top: | |
* ScrollUtil.scrollTo("top"); | |
* | |
* Also a jQuery plugin: | |
* $("#element").ScrollUtilScrollTo(); | |
*/ | |
ScrollUtil.prototype.scrollTo = function(element, params) | |
{ | |
// Default params | |
params = $.extend({ | |
offsetY: this.offsetY, | |
center: false, // center element vertically | |
time: this.defaultTime, | |
easing: this.defaultEasing | |
}, params); | |
var destY = 0; | |
if(element!="top") | |
{ | |
var offsetY = (params.offsetY>0 || params.offsetY<0 || params.offsetY===0) ? params.offsetY : 0; | |
destY = Math.ceil( $(element).offset().top - offsetY ); | |
if(params.center) | |
{ | |
var maxDestY = destY; | |
var centerOffset = - Math.floor( (ScrollUtil.device.height - $(element).height() ) *0.5) + offsetY; | |
destY+=centerOffset; | |
// Avoid clipping offset | |
if(destY > maxDestY) destY = maxDestY; | |
} | |
if(destY < ScrollUtil.minScrollFromTop) destY = 0; | |
} | |
if(!bodyElement) | |
{ | |
bodyElement = $("body")[0]; | |
} | |
if(params.target) this.target = params.target; | |
// Some browsers apply the "overall" scroll to document.documentElement (the <html> element) and others to document.body (the <body> element). | |
// For compatibility with both, you have to apply the scrolling to both. | |
// http://stackoverflow.com/questions/12222485/why-to-use-html-body-for-scrolltop-instead-of-just-html | |
// http://stackoverflow.com/questions/9041406/jquery-scrolltop-inconsistent-across-browsers | |
if(!this.target) | |
{ | |
this.target = $("body, html"); | |
} | |
// Some older chrome and mobile devices will not work with this: | |
//if(document.documentElement) this.target = document.documentElement; | |
this.cancel(); | |
// Already scrolled to? | |
if(Math.abs($(bodyElement).scrollTop() - destY)<40) | |
{ | |
this.anim = $(this.target).animate({scrollTop: destY}, { | |
duration: 0 | |
}); | |
return false; | |
} | |
// Animated | |
$(bodyElement).addClass("on-scroll-anim"); | |
this.anim = $(this.target).animate({ | |
scrollTop: destY | |
}, { | |
duration: params.time, | |
easing: params.easing, | |
complete: this.onAnimComplete.bind(this) | |
}); | |
this.settings = params; | |
if(!(params.cancellable===false)) | |
{ | |
clearTimeout(this.cancelTimeout); | |
this.cancelTimeout = setTimeout(this.onCancelTimeout.bind(this), 400); | |
} | |
return this.anim; | |
} | |
ScrollUtil.prototype.onAnimComplete = function() | |
{ | |
$(bodyElement).removeClass("on-scroll-anim"); | |
this.cancel(); | |
} | |
ScrollUtil.prototype.onCancelTimeout = function() | |
{ | |
$(window).on('mousewheel', this.cancel.bind(this)); | |
} | |
ScrollUtil.prototype.cancel = function() | |
{ | |
if(this.anim!=null) | |
{ | |
if(this.settings && $.isFunction(this.settings.complete)) this.settings.complete(this.anim); | |
this.anim.stop(true, false); | |
this.anim = null; | |
this.settings = null; | |
} | |
if(this.cancelTimeout) | |
{ | |
clearTimeout(this.cancelTimeout); | |
this.cancelTimeout=null; | |
$(window).off('mousewheel', this.cancel); | |
} | |
} | |
/** | |
* ScrollUtil.isScrolledInto() | |
* Check if an element is visible in the window from the scroll position | |
* | |
* Example use: | |
* ScrollUtil.isScrolledInto($("#element")); | |
* | |
* Fixed height use: | |
* For larger elements, Will check if the first 100 pixels of the element are visible | |
* | |
* ScrollUtil.isScrolledInto($("#element"), {height:100}); | |
* | |
* Padding use: | |
* Will enlarge the vertical area on which the element will be visible | |
* | |
* ScrollUtil.isScrolledInto($("#element"), {padding:100}); | |
* | |
* Returns true or false | |
*/ | |
ScrollUtil.prototype.isScrolledInto = function(elem, params) | |
{ | |
var windowTop = $(window).scrollTop(); | |
var windowBottom = windowTop + $(window).height(); | |
var elemTop = $(elem).offset().top; | |
var elemBottom = elemTop + $(elem).height(); | |
if(params) | |
{ | |
if(params.height>0) | |
{ | |
elemBottom=elemTop+params.height; | |
} | |
if(params.padding>0) | |
{ | |
elemTop+=params.padding; | |
elemBottom-=params.padding; | |
} | |
} | |
// If fixed height check if is visible from the bottom when element is cropped | |
if(params.height>0 && elemTop<windowTop && windowBottom-elemBottom-params.height-params.padding < params.height+params.padding) | |
{ | |
return true; | |
} | |
//console.log( "windowTop: "+windowTop + ", windowBottom: "+windowBottom+", elemTop: "+elemTop+", elemBottom: "+elemBottom); | |
return ( (elemTop >= windowTop || (params.padding>0 && elemTop >= windowTop-params.padding)) && (elemBottom <= windowBottom) ); | |
} | |
/** | |
* | |
* Will check if the element is visible over an interval and call params.onVisible when visible on screen | |
* | |
* Example use: | |
* var scrollSpy = ScrollUtil.spyScroll($('#element'), { | |
interval: 500, // interal timeout | |
minTime: 1500, // minimum exposition time to trigger onVisible | |
visibleClass:"viewport-visible", | |
hiddenClass:"viewport-hidden", | |
onVisible: function() { | |
console.log("Visible!"); | |
}, | |
onHidden: function() { | |
console.log("Hidden!"); | |
}, | |
height: 400, // ScrollUtil.isScrolledInto() height | |
padding: 100, // ScrollUtil.isScrolledInto() padding | |
}); | |
spyScroll.cancel(); // To cancel the interval | |
* | |
* @param params: same as ScrollUtil.isScrolledInto plus: | |
* - params.onVisible: callback when visible (will trigger multiple times when persistent) | |
* - params.interval: interval timeout | |
* - params.minTime: minimum exposition time to trigger onVisible | |
* - params.persisent: if check should be persistent, test can be cancelled using | |
*/ | |
ScrollUtil.prototype.spyScroll = function(element, params) | |
{ | |
return new ScrollSpy(element, params); | |
} | |
function ScrollSpy(element, params) | |
{ | |
// Defaults | |
this.params = jQuery.extend({ | |
persistent:true, | |
interval:500, | |
expositionTime:0, | |
visibleClass:"viewport-visible", | |
hiddenClass:"viewport-hidden" | |
}, params); | |
this.element = element; | |
//console.log(this.params) | |
this.interval = setInterval(this.doCheckScroll.bind(this), this.params.interval); | |
this.expositionTime = 0; | |
this.isScrolledInto = ScrollUtil.instance.isScrolledInto(this.element, this.params); | |
this.syncClasses(); | |
this.isScrolledInto = null; | |
} | |
ScrollSpy.prototype.syncClasses = function(skipTime) | |
{ | |
if(this.isScrolledInto) | |
{ | |
if(this.params.visibleClass) $(this.element).addClass(this.params.visibleClass); | |
if(this.params.hiddenClass) $(this.element).removeClass(this.params.hiddenClass); | |
} else { | |
if(this.params.visibleClass) $(this.element).removeClass(this.params.visibleClass); | |
if(this.params.hiddenClass) $(this.element).addClass(this.params.hiddenClass); | |
} | |
} | |
ScrollSpy.prototype.doCheckScroll = function(skipTime) | |
{ | |
var isScrolledInto = ScrollUtil.instance.isScrolledInto(this.element, this.params); | |
if( isScrolledInto ) | |
{ | |
// Minimum exposition time? | |
if(this.params.minTime>0) | |
{ | |
this.expositionTime+=this.params.interval; | |
//console.log(expositionTime) | |
if(!this.params.minTime || this.expositionTime >= this.params.minTime) | |
{ | |
this.onVisible(); | |
} | |
} else { | |
this.onVisible(); | |
} | |
} | |
// Not visible | |
else { | |
if(this.params.minTime>0) this.expositionTime-=this.interval; | |
this.onHidden(); | |
} | |
} | |
ScrollSpy.prototype.onVisible = function() | |
{ | |
if(this.isScrolledInto) return; | |
this.isScrolledInto = true; | |
this.syncClasses(); | |
if(this.params.onVisible) this.params.onVisible(this.element, this); | |
if(!this.params.persistent) | |
{ | |
this.cancel(); | |
} | |
this.expositionTime = 0; | |
} | |
ScrollSpy.prototype.onHidden = function() | |
{ | |
if(this.isScrolledInto===false) return; | |
this.isScrolledInto = false; | |
this.syncClasses(); | |
if(this.params.onHidden) this.params.onHidden(this.element, this); | |
this.expositionTime = 0; | |
} | |
ScrollSpy.prototype.cancel = function() | |
{ | |
clearInterval(this.interval); | |
} | |
ScrollUtil.instance = new ScrollUtil(); | |
return ScrollUtil.instance; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment