Made awesome demo with inner containers translated and appearing when scrolled into view. Updated FAQ with Zepto and hash links. Added pictures of demos to the index page instead of linking to the nav.

pull/56/head
Glen Cheney 11 years ago
parent e99b70ea99
commit 38fdf25573

@ -39,6 +39,10 @@ demos:
slug: images
label: Using images
screenshot: images.webp
- url: 'demos/2013-08-25-animated'
slug: animated
label: Animated viewport entry
screenshot: animated.webp
shapes:
- shape: circle

@ -0,0 +1,31 @@
<div class="row-fluid m-row js-demos demo-list">
{% for post in site.demos limit:4 %}
<div class="span3 m-span3 figure-wrap js-demo">
<a href="{{ site.baseurl }}/{{ post.slug }}">
<figure>
<div class="keep-ratio four-three">
<img src="{{ site.baseurl }}/img/demos/{{ post.slug }}.webp" alt="{{ post.label }}" />
</div>
<figcaption>{{ post.label }}</figcaption>
</figure>
</a>
</div>
{% endfor %}
</div>
{% if site.demos | size > 4 %}
<div class="row-fluid m-row js-demos demo-list">
{% for post in site.demos limit:4 offset:4 %}
<div class="span3 m-span3 figure-wrap js-demo">
<a href="{{ site.baseurl }}/{{ post.slug }}">
<figure>
<div class="keep-ratio four-three">
<img src="{{ site.baseurl }}/img/demos/{{ post.slug }}.webp" alt="{{ post.label }}" />
</div>
<figcaption>{{ post.label }}</figcaption>
</figure>
</a>
</div>
{% endfor %}
</div>
{% endif %}

@ -21,20 +21,7 @@
</div>
<h3 class="site-nav__title">Demos</h3>
<div class="row-fluid m-row js-demos site-nav__demos">
{% for post in site.demos %}
<div class="span3 m-span3 figure-wrap js-demo">
<a href="{{ site.baseurl }}/{{ post.slug }}">
<figure>
<div class="keep-ratio four-three">
<img src="{{ site.baseurl }}/img/demos/{{ post.slug }}.webp" alt="{{ post.label }}" />
</div>
<figcaption>{{ post.label }}</figcaption>
</figure>
</a>
</div>
{% endfor %}
</div>
{% include demo-list.html %}
</div>
</div>

@ -8,10 +8,12 @@
{% if item.cols %}{% assign cols = item.cols %}{% else %}{% assign cols = 3 %}{% endif %}
{% assign description = item.description %}
<figure class="span{{ cols }} m-span3 picture-item{{ extras }}" data-groups='{{ groups }}' data-date-created="{{ item.date }}" data-title="{{ item.title }}">
<img src="{{ site.baseurl }}/img/{{ item.img }}" alt="" height="145" width="230" />
<div class="picture-item__details clearfix">
<figcaption class="picture-item__title pull-left"><a href="{{ site.baseurl }}/img/originals/{{ item.original }}" target="_blank">{{ item.title }}</a></figcaption>
<p class="picture-item__tags pull-right">{{ item.groups | join: ', ' }}</p>
<div class="picture-item__inner">
<img src="{{ site.baseurl }}/img/{{ item.img }}" alt="" height="145" width="230" />
<div class="picture-item__details clearfix">
<figcaption class="picture-item__title pull-left"><a href="{{ site.baseurl }}/img/originals/{{ item.original }}" target="_blank">{{ item.title }}</a></figcaption>
<p class="picture-item__tags pull-right">{{ item.groups | join: ', ' }}</p>
</div>
{% if description %}{% if description == 'longDescription' %}{% assign description = site.longDescription %}{% endif %}<p class="picture-item__description">{{ description }}</p>{% endif %}
</div>
{% if description %}{% if description == 'longDescription' %}{% assign description = site.longDescription %}{% endif %}<p class="picture-item__description">{{ description }}</p>{% endif %}
</figure>

@ -24,21 +24,21 @@ extraJS: [ "faq.js" ]
<section class="container-fluid">
<div class="row-fluid">
<div class="question js-question">
<article id="spaces" class="question js-question">
<div class="question__inner">
<h3 class="question__text">Why Does Shuffle leave empty spaces?</h3>
<p class="question__answer">The algorithm used to place items does not keep track of empty space nor try to fill them. If you require this functionality, I suggest <a target="_blank" href="http://packery.metafizzy.co/">packery</a>.</p>
</div>
</div>
</article>
<div class="question js-question">
<article id="images" class="question js-question">
<div class="question__inner">
<h3 class="question__text">Why are images overlapping?</h3>
<p class="question__answer">If the size of your items are dependent on images, they can overlap if shuffle is initialized before all the images have loaded. Check out <a href="{{ site.baseurl }}{% post_url demos/2013-05-03-images %}">this demo</a> to see how to fix it.</p>
</div>
</div>
</article>
<div class="question js-question">
<article id="isotope" class="question js-question">
<div class="question__inner">
<h3 class="question__text">What&rsquo;s the difference between Shuffle and Isotope?</h3>
<div class="question__answer">
@ -61,23 +61,31 @@ extraJS: [ "faq.js" ]
<p>They are <em>very</em> similar, but I think Shuffle's filtering and sorting are easier to customize, which is the main reason I created this plugin. Isotope does have a much larger community, more tests, and more stackoverflow answers though.</p>
</div>
</div>
</div>
</article>
<article id="zepto" class="question js-question">
<div class="question__inner">
<h3 class="question__text">Does Shuffle work with Zepto?</h3>
<p class="question__answer">Not at the moment.</p>
</div>
</article>
{% comment %}
<div class="question js-question">
<article class="question js-question">
<div class="question__inner">
<h3 class="question__text">Hi</h3>
<p class="question__answer">Check yo' self</p>
</div>
</div>
</article>
{% endcomment %}
<div class="question question--unanswered">
<article class="question question--unanswered">
<div class="question__inner">
<h3 class="question__text">Didn't find an answer?</h3>
<p class="question__answer">Try looking at the javascript files for the demos or searching the issues <a href="https://github.com/Vestride/Shuffle/issues">on GitHub</a>.</p>
</div>
</div>
</article>
</div>
</section>

@ -1,7 +1,7 @@
---
layout: default
title: Basic Shuffle Demo
description: A basic demo using shuffle with a masonry layout. This example also uses a sizer element
description: A basic demo using shuffle with a masonry layout. This example also uses a sizer element.
image: /demos/basic.jpg
bodyClass: basic
extraJS: [ "demos/homepage.js" ]

@ -0,0 +1,42 @@
---
layout: default
title: Animate In Demo
description: When elements enter the viewport, they transition in from zero opacity and from the bottom.
image: /demos/animated.jpg
extraJS: [ "viewport.js", "demos/animate-in.js" ]
---
<div class="container-fluid">
<div class="row-fluid">
<h2>Animated viewport entry</h2>
</div>
</div>
<div class="container-fluid">
<div id="grid" class="row-fluid m-row shuffle--container shuffle--fluid shuffle--animatein">
{% for item in site.items %}
{% assign item = item %}
{% include picture-item.html %}
{% endfor %}
{% for item in site.items %}
{% assign item = item %}
{% include picture-item.html %}
{% endfor %}
{% for item in site.items %}
{% assign item = item %}
{% include picture-item.html %}
{% endfor %}
<div class="span3 m-span3 shuffle__sizer"></div>
</div>
</div>
<div class="container-fluid">
<div class="row-fluid">
<h2>Shuffle.js</h2>
<p>{{ site.longDescription }}</p>
<p>This demo was inspired by <a href="http://tympanus.net/codrops/">codrops</a>&rsquo; demo <a href="http://tympanus.net/codrops/2013/07/02/loading-effects-for-grid-items-with-css-animations/">Loading effects for grid items with css animations</a>.</p>
</div>
</div>
{% include credit-jake.html %}

@ -67,47 +67,6 @@ ul ul {
}
}
.figure-wrap,
.figure-wrap img {
// Promote to its own layer. makes filters look ok on retina with images
-webkit-transform: translateZ(0);
-webkit-transition: .1s ease;
transition: .1s ease;
}
.site-nav__demos:hover .figure-wrap {
-webkit-transform: scale3d( 1, 1, 1 );
transform: scale3d( 1, 1, 1 );
img {
-webkit-filter: grayscale(1);
filter: grayscale(1);
}
}
.site-nav__demos:hover .figure-wrap.hovered {
-webkit-transform: scale3d( 1.05, 1.05, 1 );
transform: scale3d( 1.05, 1.05, 1 );
img {
-webkit-filter: none;
filter: none;
}
}
.figure-wrap:nth-child(4n + 1) {
margin-left: 0;
}
.figure-wrap > a {
display: block;
}
.figure-wrap figcaption {
margin-top: .5em;
}
.site-nav__band {
position: relative;
@ -142,6 +101,50 @@ nav > a {
}
.demo-list .figure-wrap,
.demo-list .figure-wrap img {
// Promote to its own layer. makes filters look ok on retina with images
-webkit-transform: translateZ(0);
-webkit-transition: .1s ease;
transition: .1s ease;
}
.demo-list:hover .figure-wrap {
-webkit-transform: scale3d( 1, 1, 1 );
transform: scale3d( 1, 1, 1 );
img {
-webkit-filter: grayscale(1);
filter: grayscale(1);
}
}
.demo-list:hover .figure-wrap:hover {
-webkit-transform: scale3d( 1.05, 1.05, 1 );
transform: scale3d( 1.05, 1.05, 1 );
img {
-webkit-filter: none;
filter: none;
}
}
.demo-list .figure-wrap:nth-child(4n + 1) {
margin-left: 0;
}
.demo-list .figure-wrap > a {
display: block;
}
.demo-list .figure-wrap figcaption {
margin-top: .5em;
}
// Filters
.filter__label {
margin: 0 0 3px;
@ -239,6 +242,10 @@ input.faq-search {
@media ( max-width: 47.9375em ) {
.demo-list + .demo-list {
margin-top: 1em;
}
.figure-wrap:nth-child(odd) {
margin-left: 0;
}

@ -1,4 +1,6 @@
@import "variables";
@import "compass";
/*=============================================*\
Some styles to show off masonry layout
\*=============================================*/
@ -8,8 +10,6 @@ $itemHeight: 220px;
.picture-item {
height: 220px;
margin-top: $pictureGutter;
overflow: hidden;
background: $clouds;
&.shuffle-item {
margin-left: 0; /* shuffle items shouldn't have a left margin*/
@ -35,7 +35,12 @@ $itemHeight: 220px;
.picture-item__description {
display: none;
}
}
.picture-item__inner {
background: $clouds;
height: 100%;
overflow: hidden;
}
img {
@ -63,6 +68,7 @@ $itemHeight: 220px;
}
}
/*
Shuffle needs either relative or absolute positioning on the container
It will set it for you, but it'll cause another style recalculation and layout.
@ -80,6 +86,27 @@ $itemHeight: 220px;
}
/* Animate in styles */
.shuffle--animatein {
overflow: visible;
}
.shuffle--animatein .picture-item__inner {
opacity: 0;
@include translate(0, 220px);
}
.shuffle--animatein .picture-item__inner--transition {
-webkit-transition: all .6s ease;
transition: all .6s ease;
}
.shuffle--animatein .picture-item.in .picture-item__inner {
opacity: 1;
@include translate(0, 0);
}
@media ( max-width: 47.9375em ) {

@ -4,8 +4,6 @@
.picture-item {
height: 220px;
margin-top: 24px;
overflow: hidden;
background: #ecf0f1;
}
.picture-item.shuffle-item {
margin-left: 0;
@ -27,6 +25,11 @@
.picture-item.span6:not(.picture-item--h2) .picture-item__description {
display: none;
}
.picture-item .picture-item__inner {
background: #ecf0f1;
height: 100%;
overflow: hidden;
}
.picture-item img {
display: block;
width: 100%;
@ -61,6 +64,34 @@
visibility: hidden;
}
/* Animate in styles */
.shuffle--animatein {
overflow: visible;
}
.shuffle--animatein .picture-item__inner {
opacity: 0;
-webkit-transform: translate(0, 220px);
-moz-transform: translate(0, 220px);
-ms-transform: translate(0, 220px);
-o-transform: translate(0, 220px);
transform: translate(0, 220px);
}
.shuffle--animatein .picture-item__inner--transition {
-webkit-transition: all .6s ease;
transition: all .6s ease;
}
.shuffle--animatein .picture-item.in .picture-item__inner {
opacity: 1;
-webkit-transform: translate(0, 0);
-moz-transform: translate(0, 0);
-ms-transform: translate(0, 0);
-o-transform: translate(0, 0);
transform: translate(0, 0);
}
@media (max-width: 47.9375em) {
.picture-item {
height: auto;

@ -1944,37 +1944,6 @@ ul ul {
.lt-ie8 .site-nav.collapsed .site-nav__tray {
display: none;
}
.site-nav .figure-wrap,
.site-nav .figure-wrap img {
-webkit-transform: translateZ(0);
-webkit-transition: .1s ease;
transition: .1s ease;
}
.site-nav .site-nav__demos:hover .figure-wrap {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
.site-nav .site-nav__demos:hover .figure-wrap img {
-webkit-filter: grayscale(1);
filter: grayscale(1);
}
.site-nav .site-nav__demos:hover .figure-wrap.hovered {
-webkit-transform: scale3d(1.05, 1.05, 1);
transform: scale3d(1.05, 1.05, 1);
}
.site-nav .site-nav__demos:hover .figure-wrap.hovered img {
-webkit-filter: none;
filter: none;
}
.site-nav .figure-wrap:nth-child(4n + 1) {
margin-left: 0;
}
.site-nav .figure-wrap > a {
display: block;
}
.site-nav .figure-wrap figcaption {
margin-top: .5em;
}
.site-nav .site-nav__band {
position: relative;
}
@ -2004,6 +1973,43 @@ nav > a {
margin: 5px 0;
}
.demo-list .figure-wrap,
.demo-list .figure-wrap img {
-webkit-transform: translateZ(0);
-webkit-transition: .1s ease;
transition: .1s ease;
}
.demo-list:hover .figure-wrap {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
.demo-list:hover .figure-wrap img {
-webkit-filter: grayscale(1);
filter: grayscale(1);
}
.demo-list:hover .figure-wrap:hover {
-webkit-transform: scale3d(1.05, 1.05, 1);
transform: scale3d(1.05, 1.05, 1);
}
.demo-list:hover .figure-wrap:hover img {
-webkit-filter: none;
filter: none;
}
.demo-list .figure-wrap:nth-child(4n + 1) {
margin-left: 0;
}
.demo-list .figure-wrap > a {
display: block;
}
.demo-list .figure-wrap figcaption {
margin-top: .5em;
}
.filter__label {
margin: 0 0 3px;
}
@ -2091,6 +2097,10 @@ input.faq-search:focus::-moz-input-placeholder {
}
@media (max-width: 47.9375em) {
.demo-list + .demo-list {
margin-top: 1em;
}
.figure-wrap:nth-child(odd) {
margin-left: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@ -67,7 +67,7 @@ prism: true
<section id="demos">
<div class="container-fluid">
<h2>Demos</h2>
<a href="#nav">In the nav</a>
{% include demo-list.html %}
</div>
</section>

@ -0,0 +1,99 @@
var DEMO = (function( $, Viewport ) {
'use strict';
var $grid = $('#grid'),
$gridItems = $grid.find('.picture-item'),
$sizer = $grid.find('.shuffle__sizer'),
shuffle,
init = function() {
// instantiate the plugin
$grid.shuffle({
itemSelector: '.picture-item',
sizer: $sizer
});
shuffle = $grid.data('shuffle');
addViewportItems();
setTimeout(function() {
listen();
addTransitionToItems();
}, 100);
},
addViewportItems = function() {
$gridItems.each(function() {
Viewport.add({
element: this,
threshold: 130,
enter: showItemsInViewport
});
});
},
// Only the items out of the viewport should transition. This way, the first visible ones
// will snap into place.
addTransitionToItems = function() {
$gridItems.find('.picture-item__inner').addClass('picture-item__inner--transition');
},
showItemsInViewport = function() {
$(this).addClass('in');
},
// Re layout shuffle when images load. This is only needed
// below 768 pixels because the .picture-item height is auto and therefore
// the height of the picture-item is dependent on the image
// I recommend using imagesloaded to determine when an image is loaded
// but that doesn't support IE7
listen = function() {
var debouncedLayout = $.throttle( 300, function() {
$grid.shuffle('update');
Viewport.refresh();
});
var $imgs = $grid.find('img');
// Get all images inside shuffle
$imgs.each(function() {
var proxyImage;
// Image already loaded
if ( this.complete && this.naturalWidth !== undefined ) {
return;
}
// If none of the checks above matched, simulate loading on detached element.
proxyImage = new Image();
$( proxyImage ).on('load', function() {
$(this).off('load');
debouncedLayout();
});
proxyImage.src = this.src;
});
// Because this method doesn't seem to be perfect.
setTimeout(function() {
debouncedLayout();
}, 500);
};
return {
init: init,
getShuffle: function() {
return shuffle;
}
};
}( jQuery, window.Viewport ));
$(document).ready(function() {
DEMO.init();
});

@ -7,6 +7,7 @@ var DEMO = (function( $ ) {
init = function() {
// None of these need to be executed synchronously
setTimeout(function() {
listen();
@ -130,6 +131,11 @@ var DEMO = (function( $ ) {
proxyImage.src = this.src;
});
// Because this method doesn't seem to be perfect.
setTimeout(function() {
debouncedLayout();
}, 500);
};
return {

@ -41,12 +41,34 @@ Modules.Support = (function( $ ) {
};
// Fill rAF
var rAF = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller
// fixes from Paul Irish and Tino Zijdel
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] ||
window[vendors[x]+'CancelRequestAnimationFrame'];
}
window.requestAnimationFrame = rAF;
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function(callback) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
return self;
}( jQuery ));
@ -122,15 +144,6 @@ Modules.Nav = (function( $ ) {
self.$trigger.on( 'click', $.proxy( self.toggle, self ) );
self.$window.on( 'resize', $.debounce( 250, $.proxy( self.onResize, self ) ) );
// This is a case for selectors level 4!
self.$el.find('.js-demos').on('mouseenter', '.js-demo', function( evt ) {
$( evt.currentTarget ).addClass('hovered');
});
self.$el.find('.js-demos').on('mouseleave', '.js-demo', function( evt ) {
$( evt.currentTarget ).removeClass('hovered');
});
return self;
};

@ -0,0 +1,369 @@
// Viewport Helper
// --------------------------------------------
//
// * **Class:** Viewport
// * **Version:** 1.0
// * **Modified:** 06/26/2013
// * **Author:** Glen Cheney
// * **Dependencies:** jQuery 1.7+, SONY Settings, throttle/debounce
//
// *Notes:*
//
// If you need to be notified when an element is scrolled into view, use this module.
// This module keeps track of all elements that want to be watched and caches their offsets
// and dimensions in order to keep scrolling as smooth as possible.
//
// *Example Usage:*
//
// Viewport.add({
// element: document.getElementById('some-wrapper'),
// threshold: '50%',
// enter: function( element ) {
// console.log('the top of "element" is 50% in view');
// },
// leave: function() {
// console.log('bottom of element has left the viewport');
// }
// });
//
// *Viewport.add parameters:*
//
// * `element` is a DOM element and `callback` is a function. `this` in the callback is the element.
// * Using an options object, a `threshold` can be set.
// It is either an integer value from the bottom of the window, a string percentage, or a float
// between 0 and 1 which represents the percent.
//
(function( $ ) {
'use strict';
var instance = null;
var $window = $(window);
var ViewportItem = function( options ) {
var self = this;
// Get defaults
$.extend( self, ViewportItem.options, options, ViewportItem.settings );
// The whole point is to have a callback function.
// Don't do anything if it's not given
if ( !$.isFunction( self.enter ) ) {
throw new TypeError('Viewport.add :: No `enter` function provided in Viewport options.');
}
// Threshold can be a percentage. Parse it.
if ( (typeof self.threshold === 'string' && self.threshold.indexOf('%') > -1 ) ) {
self.isThresholdPercentage = true;
self.threshold = parseFloat( self.threshold ) / 100;
} else if ( self.threshold < 1 && self.threshold > 0 ) {
self.isThresholdPercentage = true;
}
self.hasLeaveCallback = $.isFunction( self.leave );
self.$element = $( self.element );
// Cache element's offsets and dimensions
self.update();
};
ViewportItem.prototype.update = function() {
var self = this;
self.offset = self.$element.offset();
self.height = self.$element.height();
self.width = self.$element.width();
};
ViewportItem.options = {
threshold: 200,
delay: 0
};
ViewportItem.settings = {
triggered: false,
isThresholdPercentage: false
};
var Viewport = function() {
this.init();
};
Viewport.prototype = {
init : function() {
var self = this;
self.list = [];
self.lastScrollY = 0;
self.windowHeight = $window.height();
self.windowWidth = $window.width();
self.throttleTime = 100;
self.onResize();
self.bindEvents();
// What's nice here is that rAF won't execute until the user is on this tab,
// so if they open the page in a new tab which they aren't looking at,
// this will execute when they come back to that tab
self.willProcessNextFrame = true;
requestAnimationFrame(function() {
self.setScrollTop();
self.process();
self.willProcessNextFrame = false;
});
},
bindEvents : function() {
var self = this,
refresh;
// Updates offsets after a zero timeout
refresh = function() {
setTimeout(function refreshWithDelay() {
self.refresh();
}, 0);
};
// Listen for global resize
$window.on('resize.viewport', $.proxy( self.onResize, self ));
// Throttle scrolling because it doesn't need to be super accurate
$window.on('scroll.viewport', $.throttle( self.throttleTime, $.proxy( self.onScroll, self ) ));
self.hasActiveHandlers = true;
},
unbindEvents : function() {
$window.off('.viewport');
this.hasActiveHandlers = false;
},
maybeUnbindEvents : function() {
var self = this;
// Not currently watching anything, unbind events
if ( !self.list.length ) {
self.unbindEvents();
}
},
add : function( viewportItem ) {
var self = this;
self.list.push( viewportItem );
// Event handlers are removed if a callback is triggered and the
// watch list is empty. Because modules are instantiated asynchronously,
// another module could potentially add itself to the watch list when the events
// have been unbound.
// Check here if events have been unbound and bind them again if they have
if ( !self.hasActiveHandlers ) {
self.bindEvents();
}
if ( !self.willProcessNextFrame ) {
self.willProcessNextFrame = true;
requestAnimationFrame(function() {
self.willProcessNextFrame = false;
self.process();
});
}
},
saveDimensions : function() {
var self = this;
$.each(self.list, function( i, viewportItem ) {
viewportItem.update();
});
// self.documentHeight
self.windowHeight = $window.height();
self.windowWidth = $window.width();
},
// Throttled scroll event
onScroll : function() {
var self = this;
// No point in doing anything if there aren't any viewports to watch
if ( !self.list.length ) {
return;
}
// Save the new scroll top
self.setScrollTop();
self.process();
},
// Debounced resize event
onResize : function() {
this.refresh();
},
refresh : function() {
// No point in doing anything if there aren't any viewports to watch
if ( !this.list.length ) {
return;
}
// Update offsets and width/height for each viewport item
this.saveDimensions();
},
isInViewport : function( viewportItem ) {
var self = this,
offset = viewportItem.offset,
threshold = viewportItem.threshold,
percentage = threshold,
st = self.lastScrollY,
isTopInView;
if ( viewportItem.isThresholdPercentage ) {
threshold = 0;
}
// Other checks could be added here in the future
isTopInView = self.isTopInView( st, self.windowHeight, offset.top, viewportItem.height, threshold );
// If the top isn't in view with zero threshold,
// don't bother checking if it's at a percent of the window
if ( isTopInView && viewportItem.isThresholdPercentage ) {
isTopInView = self.isTopPastPercent( st, self.windowHeight, offset.top, viewportItem.height, percentage );
}
return isTopInView;
},
// If the top of the element (plus the threshold) is past the viewport's top
// and the top of the element (plus the threshold) is not past the viewport's bottom.
// Then the top is in view.
isTopInView : function( viewportTop, viewportHeight, elementTop, elementHeight, threshold ) {
var viewportBottom = viewportTop + viewportHeight;
return (elementTop + threshold) >= viewportTop && (elementTop + threshold) < viewportBottom;
},
isTopPastPercent : function( viewportTop, viewportHeight, elementTop, elementHeight, percentage ) {
var viewportBottom = viewportTop + viewportHeight,
distFromViewportBottomToElementTop = viewportBottom - elementTop,
percentFromBottom = distFromViewportBottomToElementTop / viewportHeight;
return percentFromBottom >= percentage;
},
isOutOfViewport : function( viewport, side ) {
var self = this,
offset = viewport.offset,
st = self.lastScrollY,
bool;
if ( side === 'bottom' ) {
bool = !self.isBottomInView( st, self.windowHeight, offset.top, viewport.height );
}
return bool;
},
isBottomInView : function( viewportTop, viewportHeight, elementTop, elementHeight ) {
var viewportBottom = viewportTop + viewportHeight,
elementBottom = elementTop + elementHeight;
return elementBottom > viewportTop && elementBottom <= viewportBottom;
},
triggerEnter : function( viewportItem ) {
var self = this;
// Queue up the callback with the delay. Default is 0
setTimeout(function() {
viewportItem.enter.call( viewportItem.element, viewportItem );
}, viewportItem.delay);
if ( $.isFunction( viewportItem.leave ) ) {
viewportItem.triggered = true;
// If the leave property is not a function,
// The module no longer needs to watch it, so remove from list
// However, the list may have been modified already in this loop, so find the
// index of the viewport item instead of using the loop index.
} else {
self.list.splice( $.inArray( viewportItem, self.list ), 1 );
}
// If there are no more, unbind from scroll and resize events
self.maybeUnbindEvents();
},
triggerLeave : function( viewportItem ) {
// var self = this;
// Queue up the callback with the delay. Default is 0
setTimeout(function() {
viewportItem.leave.call( viewportItem.element, viewportItem );
}, viewportItem.delay);
viewportItem.triggered = false;
},
setScrollTop : function() {
// Save the new scroll top
this.lastScrollY = $window.scrollTop();
},
process : function() {
var self = this,
// The list can possibly be modified mid loop,
// so the loop needs a copy of the variable which won't be modified
list = $.extend( [], self.list );
$.each(list, function( i, viewportItem ) {
var isInViewport = self.isInViewport( viewportItem ),
isBottomOutOfView = viewportItem.hasLeaveCallback && self.isOutOfViewport( viewportItem, 'bottom' );
// If the enter callback hasn't been triggerd and it's in the viewport,
// trigger the enter callback
if ( !viewportItem.triggered && isInViewport ) {
return self.triggerEnter( viewportItem );
}
// This viewport has already come into view once and now it is out of view
// It's not in view, the bottom is out of view, the list item's enter has been triggered
if ( !isInViewport && isBottomOutOfView && viewportItem.triggered ) {
return self.triggerLeave( viewportItem );
}
});
}
};
Viewport.add = function( options ) {
var instance = Viewport.getInstance();
return instance.add( new ViewportItem( options ) );
};
Viewport.refresh = function() {
Viewport.getInstance().refresh();
};
Viewport.getInstance = function() {
if ( !instance ) {
instance = new Viewport();
}
return instance;
};
window.Viewport = Viewport;
}( jQuery ));
Loading…
Cancel
Save