249 lines
7.9 KiB
JavaScript
249 lines
7.9 KiB
JavaScript
|
/* appear.js 1.0.3 */
|
||
|
appear = (function(){
|
||
|
'use strict';
|
||
|
var scrollLastPos = null, scrollTimer = 0, scroll = {};
|
||
|
|
||
|
function track(){
|
||
|
var newPos = window.scrollY || window.pageYOffset; // pageYOffset for IE9
|
||
|
if ( scrollLastPos != null ){
|
||
|
scroll.velocity = newPos - scrollLastPos;
|
||
|
scroll.delta = (scroll.velocity >= 0) ? scroll.velocity : (-1 * scroll.velocity);
|
||
|
|
||
|
}
|
||
|
scrollLastPos = newPos;
|
||
|
if(scrollTimer){
|
||
|
clearTimeout(scrollTimer);
|
||
|
}
|
||
|
scrollTimer = setTimeout(function(){
|
||
|
scrollLastPos = null;
|
||
|
}, 30);
|
||
|
}
|
||
|
addEventListener('scroll', track, false);
|
||
|
|
||
|
// determine if a given element (plus an additional "bounds" area around it) is in the viewport
|
||
|
function viewable(el, bounds){
|
||
|
var rect = el.getBoundingClientRect();
|
||
|
return (
|
||
|
(rect.top + rect.height) >= 0 &&
|
||
|
(rect.left + rect.width) >= 0 &&
|
||
|
(rect.bottom - rect.height) <= ( (window.innerHeight || document.documentElement.clientHeight) + bounds) &&
|
||
|
(rect.right - rect.width) <= ( (window.innerWidth || document.documentElement.clientWidth) + bounds)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return function(obj){
|
||
|
|
||
|
return (function(obj){
|
||
|
var initd = false, elements = [], elementsLength, reappear = [],
|
||
|
appeared = 0, disappeared = 0, timer, deltaSet, opts = {}, done;
|
||
|
|
||
|
// handle debouncing a function for better performance on scroll
|
||
|
function debounce(fn, delay) {
|
||
|
return function () {
|
||
|
var self = this, args = arguments;
|
||
|
clearTimeout(timer);
|
||
|
|
||
|
timer = setTimeout(function () {
|
||
|
fn.apply(self, args);
|
||
|
}, delay);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// called on scroll and resize event, so debounce the actual function that does
|
||
|
// the heavy work of determining if an item is viewable and then "appearing" it
|
||
|
function checkAppear() {
|
||
|
if(scroll.delta < opts.delta.speed) {
|
||
|
if(!deltaSet) {
|
||
|
deltaSet = true;
|
||
|
doCheckAppear();
|
||
|
setTimeout(function(){
|
||
|
deltaSet = false;
|
||
|
}, opts.delta.timeout);
|
||
|
}
|
||
|
}
|
||
|
(debounce(function() {
|
||
|
doCheckAppear();
|
||
|
}, opts.debounce)());
|
||
|
}
|
||
|
|
||
|
function begin() {
|
||
|
// initial appear check before any scroll or resize event
|
||
|
doCheckAppear();
|
||
|
|
||
|
// add relevant listeners
|
||
|
addEventListener('scroll', checkAppear, false);
|
||
|
addEventListener('resize', checkAppear, false);
|
||
|
}
|
||
|
|
||
|
function end() {
|
||
|
elements = [];
|
||
|
if(timer) {
|
||
|
clearTimeout(timer);
|
||
|
}
|
||
|
removeListeners();
|
||
|
}
|
||
|
|
||
|
function removeListeners() {
|
||
|
|
||
|
removeEventListener('scroll', checkAppear, false);
|
||
|
removeEventListener('resize', checkAppear, false);
|
||
|
}
|
||
|
|
||
|
function doCheckAppear() {
|
||
|
if(done) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
elements.forEach(function(n, i){
|
||
|
if(n && viewable(n, opts.bounds)) {
|
||
|
// only act if the element is eligible to reappear
|
||
|
if(reappear[i]) {
|
||
|
// mark this element as not eligible to appear
|
||
|
reappear[i] = false;
|
||
|
// increment the count of appeared items
|
||
|
appeared++;
|
||
|
|
||
|
// call the appear fn
|
||
|
if(opts.appear) {
|
||
|
opts.appear(n);
|
||
|
}
|
||
|
// if not tracking reappears or disappears, need to remove node here
|
||
|
if(!opts.disappear && !opts.reappear) {
|
||
|
// stop tracking this node, which is now viewable
|
||
|
elements[i] = null;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if(reappear[i] === false) {
|
||
|
if(opts.disappear) {
|
||
|
opts.disappear(n);
|
||
|
}
|
||
|
// increment the dissappeared count
|
||
|
disappeared++;
|
||
|
|
||
|
// if not tracking reappears, need to remove node here
|
||
|
if(!opts.reappear) {
|
||
|
// stop tracking this node, which is now viewable
|
||
|
elements[i] = null;
|
||
|
}
|
||
|
}
|
||
|
// element is out of view and eligible to be appeared again
|
||
|
reappear[i] = true;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// remove listeners if all items have (re)appeared
|
||
|
if(!opts.reappear && (!opts.appear || opts.appear && appeared === elementsLength) && (!opts.disappear || opts.disappear && disappeared === elementsLength)) {
|
||
|
// ensure done is only called once (could be called from a trailing debounce/throttle)
|
||
|
done = true;
|
||
|
removeListeners();
|
||
|
// all items have appeared, so call the done fn
|
||
|
if(opts.done){
|
||
|
opts.done();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function init() {
|
||
|
// make sure we only init once
|
||
|
if(initd) {
|
||
|
return;
|
||
|
}
|
||
|
initd = true;
|
||
|
|
||
|
// call the obj init fn
|
||
|
if(opts.init) {
|
||
|
opts.init();
|
||
|
}
|
||
|
// get the elements to work with
|
||
|
var els;
|
||
|
if(typeof opts.elements === 'function') {
|
||
|
els = opts.elements();
|
||
|
} else {
|
||
|
els = opts.elements;
|
||
|
}
|
||
|
if(els) {
|
||
|
// put elements into an array object to work with
|
||
|
elementsLength = els.length;
|
||
|
for(var i = 0; i < elementsLength; i += 1) {
|
||
|
elements.push(els[i]);
|
||
|
reappear.push(true);
|
||
|
}
|
||
|
begin();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return function(obj) {
|
||
|
obj = obj || {};
|
||
|
|
||
|
// assign the fn to execute when a node is visible
|
||
|
opts = {
|
||
|
// a function to be run when the dom is ready (allows for any setup work)
|
||
|
init: obj.init,
|
||
|
// either an array of elements or a function that will return an htmlCollection
|
||
|
elements: obj.elements,
|
||
|
// function to call when an element is "viewable", will be passed the element to work with
|
||
|
appear: obj.appear,
|
||
|
// function to call when an element is no longer "viewable", will be passed the element to work with
|
||
|
disappear: obj.disappear,
|
||
|
// function to call when all the elements have "appeared"
|
||
|
done: obj.done,
|
||
|
// keep tracking the elements
|
||
|
reappear: obj.reappear,
|
||
|
// the extra border around an element to make it viewable outside of the true viewport
|
||
|
bounds: obj.bounds || 0,
|
||
|
// the debounce timeout
|
||
|
debounce: obj.debounce || 50,
|
||
|
// appear.js will also check for items on continuous slow scrolling
|
||
|
// you can controll how slow the scrolling should be (deltaSpeed)
|
||
|
// and when it will check again (deltaTimeout) after it has inspected the dom/viewport;
|
||
|
delta: {
|
||
|
speed: obj.deltaSpeed || 50,
|
||
|
timeout: obj.deltaTimeout || 500
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// add an event listener to init when dom is ready
|
||
|
addEventListener('DOMContentLoaded', init, false);
|
||
|
|
||
|
var isIE10 = false;
|
||
|
if (Function('/*@cc_on return document.documentMode===10@*/')()){
|
||
|
isIE10 = true;
|
||
|
}
|
||
|
var completeOrLoaded = document.readyState === 'complete' || document.readyState === 'loaded';
|
||
|
|
||
|
// call init if document is ready to be worked with and we missed the event
|
||
|
if (isIE10) {
|
||
|
if (completeOrLoaded) {
|
||
|
init();
|
||
|
}
|
||
|
} else {
|
||
|
if (completeOrLoaded || document.readyState === 'interactive') {
|
||
|
init();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
// manually fire check for visibility of tracked elements
|
||
|
trigger: function trigger(){
|
||
|
doCheckAppear();
|
||
|
},
|
||
|
// pause tracking of elements
|
||
|
pause: function pause(){
|
||
|
removeListeners();
|
||
|
},
|
||
|
// resume tracking of elements after a pause
|
||
|
resume: function resume(){
|
||
|
begin();
|
||
|
},
|
||
|
// provide a means to stop monitoring all elements
|
||
|
destroy: function destroy() {
|
||
|
end();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
};
|
||
|
}()(obj));
|
||
|
};
|
||
|
}());
|