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.
parent
e99b70ea99
commit
38fdf25573
@ -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 %}
|
@ -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>’ 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 %}
|
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 |
@ -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();
|
||||
});
|
@ -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…
Reference in New Issue