Skip to content

Instantly share code, notes, and snippets.

@josimard
Last active August 29, 2015 14:07
Show Gist options
  • Save josimard/ae00cc2512707c8c0cd5 to your computer and use it in GitHub Desktop.
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)
/**
* 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