// Sticky /* Smart Resize */ ( function ( $, sr ) { 'use strict'; // debouncing function from John Hann // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ var debounce = function ( func, threshold, execAsap ) { var timeout; return function debounced() { var obj = this, args = arguments; function delayed() { if ( !execAsap ) func.apply( obj, args ); timeout = null; } if ( timeout ) clearTimeout( timeout ); else if ( execAsap ) func.apply( obj, args ); timeout = setTimeout( delayed, threshold || 100 ); }; }; // smartresize jQuery.fn[ sr ] = function ( fn ) { return fn ? this.bind( 'resize', debounce( fn ) ) : this.trigger( sr ); }; } )( jQuery, 'smartresize' ); ( function ( $ ) { 'use strict'; // jQuery Pin plugin $.fn.themePin = function ( options ) { var scrollY = 0, lastScrollY = 0, elements = [], disabled = false, $window = $( window ), fixedSideTop = [], fixedSideBottom = [], prevDataTo = []; options = options || {}; var recalculateLimits = function () { for ( var i = 0, len = elements.length; i < len; i++ ) { var $this = elements[ i ]; if ( options.minWidth && $window.width() <= options.minWidth ) { if ( $this.parent().is( ".pin-wrapper" ) ) { $this.unwrap(); } $this.css( { width: "", left: "", top: "", position: "" } ); disabled = true; continue; } else { disabled = false; } var $container = options.containerSelector ? ( $this.closest( options.containerSelector ).length ? $this.closest( options.containerSelector ) : $( options.containerSelector ) ) : $( document.body ); var offset = $this.offset(); var containerOffset = $container.offset(); if ( typeof containerOffset == 'undefined' ) { continue; } var parentOffset = $this.parent().offset(); if ( !$this.parent().is( ".pin-wrapper" ) ) { $this.wrap( "
" ); } var pad = $.extend( { top: 0, bottom: 0 }, options.padding || {} ); var pt = parseInt( $this.parent().parent().css( 'padding-top' ) ), pb = parseInt( $this.parent().parent().css( 'padding-bottom' ) ); if ( typeof options.paddingOffsetTop != 'undefined' ) { pad.top += parseInt( options.paddingOffsetTop, 10 ); } else { pad.top += 18; } if ( typeof options.paddingOffsetBottom != 'undefined' ) { pad.bottom = parseInt( options.paddingOffsetBottom, 10 ); } else { pad.bottom = 0; } var bb = $this.css( 'border-bottom' ), h = $this.outerHeight(); $this.css( 'border-bottom', '1px solid transparent' ); var o_h = $this.outerHeight() - h - 1; $this.css( 'border-bottom', bb ); $this.css( { width: $this.outerWidth() <= $this.parent().width() ? $this.outerWidth() : $this.parent().width() } ); $this.parent().css( "height", $this.outerHeight() + o_h ); if ( $this.outerHeight() <= $window.height() ) { $this.data( "themePin", { pad: pad, from: ( options.containerSelector ? containerOffset.top : offset.top ) - pad.top + pt, pb: pb, parentTop: parentOffset.top - pt, offset: o_h } ); } else { $this.data( "themePin", { pad: pad, fromFitTop: ( options.containerSelector ? containerOffset.top : offset.top ) - pad.top + pt, from: ( options.containerSelector ? containerOffset.top : offset.top ) + $this.outerHeight() - $window.height() + pt, pb: pb, parentTop: parentOffset.top - pt, offset: o_h } ); } } }; var onScroll = function () { if ( disabled ) { return; } scrollY = $window.scrollTop(); var window_height = window.innerHeight || $window.height(); for ( var i = 0, len = elements.length; i < len; i++ ) { var $this = $( elements[ i ] ), data = $this.data( "themePin" ), sidebarTop; if ( !data ) { // Removed element continue; } var $container = options.containerSelector ? ( $this.closest( options.containerSelector ).length ? $this.closest( options.containerSelector ) : $( options.containerSelector ) ) : $( document.body ), this_height = $this.outerHeight(), isFitToTop = ( this_height + data.pad.top ) <= window_height; data.end = $container.offset().top + $container.height(); if ( isFitToTop ) { data.to = $container.offset().top + $container.height() - this_height - data.pad.bottom - data.pb; } else { data.to = $container.offset().top + $container.height() - window_height - data.pb; data.to2 = $container.height() - this_height - data.pad.bottom - data.pb; } if ( prevDataTo[ i ] === 0 ) { prevDataTo[ i ] = data.to; } if ( prevDataTo[ i ] != data.to ) { if ( fixedSideBottom[ i ] && this_height + $this.offset().top + data.pad.bottom < scrollY + window_height ) { fixedSideBottom[ i ] = false; } } if ( isFitToTop ) { var from = data.from - data.pad.bottom, to = data.to - data.pad.top - data.offset; if ( typeof data.fromFitTop != 'undefined' && data.fromFitTop ) { from = data.fromFitTop - data.pad.bottom; } if ( from + this_height > data.end || from >= to ) { $this.css( { position: "", top: "", left: "" } ); if ( options.activeClass ) { $this.removeClass( options.activeClass ); } continue; } if ( scrollY > from && scrollY < to ) { !( $this.css( "position" ) == "fixed" ) && $this.css( { left: $this.offset().left, top: data.pad.top } ).css( "position", "fixed" ); if ( options.activeClass ) { $this.addClass( options.activeClass ); } } else if ( scrollY >= to ) { $this.css( { left: "", top: to - data.parentTop + data.pad.top } ).css( "position", "absolute" ); if ( options.activeClass ) { $this.addClass( options.activeClass ); } } else { $this.css( { position: "", top: "", left: "" } ); if ( options.activeClass ) { $this.removeClass( options.activeClass ); } } } else if ( ( this_height + data.pad.top + data.pad.bottom ) > window_height || fixedSideTop[ i ] || fixedSideBottom[ i ] ) { var padTop = parseInt( $this.parent().parent().css( 'padding-top' ) ); // Reset the sideSortables style when scrolling to the top. if ( scrollY + data.pad.top - padTop <= data.parentTop ) { $this.css( { position: "", top: "", bottom: "", left: "" } ); fixedSideTop[ i ] = fixedSideBottom[ i ] = false; if ( options.activeClass ) { $this.removeClass( options.activeClass ); } } else if ( scrollY >= data.to ) { $this.css( { left: "", top: data.to2, bottom: "" } ).css( "position", "absolute" ); if ( options.activeClass ) { $this.addClass( options.activeClass ); } } else { // When scrolling down. if ( scrollY > lastScrollY ) { if ( fixedSideTop[ i ] ) { // Let it scroll. fixedSideTop[ i ] = false; sidebarTop = $this.offset().top - data.parentTop; $this.css( { left: "", top: sidebarTop, bottom: "" } ).css( "position", "absolute" ); if ( options.activeClass ) { $this.addClass( options.activeClass ); } } else if ( !fixedSideBottom[ i ] && this_height + $this.offset().top + data.pad.bottom < scrollY + window_height ) { // Pin the bottom. fixedSideBottom[ i ] = true; !( $this.css( "position" ) == "fixed" ) && $this.css( { left: $this.offset().left, bottom: data.pad.bottom, top: "" } ).css( "position", "fixed" ); if ( options.activeClass ) { $this.addClass( options.activeClass ); } } // When scrolling up. } else if ( scrollY < lastScrollY ) { if ( fixedSideBottom[ i ] ) { // Let it scroll. fixedSideBottom[ i ] = false; sidebarTop = $this.offset().top - data.parentTop; $this.css( { left: "", top: sidebarTop, bottom: "" } ).css( "position", "absolute" ); if ( options.activeClass ) { $this.addClass( options.activeClass ); } } else if ( !fixedSideTop[ i ] && $this.offset().top >= scrollY + data.pad.top ) { // Pin the top. fixedSideTop[ i ] = true; !( $this.css( "position" ) == "fixed" ) && $this.css( { left: $this.offset().left, top: data.pad.top, bottom: '' } ).css( "position", "fixed" ); if ( options.activeClass ) { $this.addClass( options.activeClass ); } } } else { fixedSideTop[ i ] = false; fixedSideTop[ i ] = false; } } } else { // If the sidebar container is smaller than the viewport, then pin/unpin the top when scrolling. if ( scrollY >= ( data.parentTop - data.pad.top ) ) { $this.css( { position: 'fixed', top: data.pad.top } ); } else { $this.css( { position: "", top: "", bottom: "", left: "" } ); } fixedSideTop[ i ] = fixedSideBottom[ i ] = false; } prevDataTo[ i ] = data.to; } lastScrollY = scrollY; }; var recalculateLeft = function ( timeout ) { if ( typeof timeout == 'undefined' ) { timeout = 400; } for ( var i = 0, len = elements.length; i < len; i++ ) { var $this = $( elements[ i ] ), data = $this.data( "themePin" ); if ( !data ) { // Removed element continue; } var $container = options.containerSelector ? ( $this.closest( options.containerSelector ).length ? $this.closest( options.containerSelector ) : $( options.containerSelector ) ) : $( document.body ); if ( $this.css( "position" ) == "fixed" ) { var offset = $this.offset().top - $container.offset().top; $this.css( { 'position': 'absolute', left: '', top: offset, bottom: '' } ); } } }; var update = function () { recalculateLimits(); onScroll(); }; this.each( function () { var $this = $( this ), data = $( this ).data( 'themePin' ) || {}; if ( data && data.update ) { return; } elements.push( $this ); $( "img", this ).one( "load", recalculateLimits ); data.update = update; $( this ).data( 'themePin', data ); fixedSideTop.push( false ); fixedSideBottom.push( false ); prevDataTo.push( 0 ); } ); $window.on( 'touchmove scroll', onScroll ); recalculateLimits(); $window.on( 'load', update ); $( this ).on( 'recalc.pin', function () { recalculateLimits(); onScroll(); } ); $( this ).on( 'recalc.pin.left', function ( e, timeout ) { recalculateLeft( timeout ); } ); return this; }; var instanceName = '__sticky'; var Sticky = function ( $el, opts ) { return this.initialize( $el, opts ); }; Sticky.defaults = { autoInit: false, minWidth: 767, padding: { top: 0, bottom: 0 }, offsetTop: 0, offsetBottom: 0 }; Sticky.prototype = { initialize: function ( $el, opts ) { if ( $el.data( instanceName ) ) { return this; } this.$el = $el; this .setData() .setOptions( opts ) .build(); return this; }, setData: function () { this.$el.data( instanceName, this ); return this; }, setOptions: function ( opts ) { this.options = $.extend( true, {}, Sticky.defaults, opts, { wrapper: this.$el } ); return this; }, build: function () { if ( !( $.isFunction( $.fn.themePin ) ) ) { return this; } var self = this, $el = this.options.wrapper, stickyResizeTrigger; $el.themePin( this.options ); $( window ).smartresize( function () { if ( stickyResizeTrigger ) { clearTimeout( stickyResizeTrigger ); } stickyResizeTrigger = setTimeout( function () { $el.trigger( 'recalc.pin' ); }, 800 ); var $parent = $el.parent(); $el.outerWidth( $parent.width() ); if ( $el.css( 'position' ) == 'fixed' ) { $el.css( 'left', $parent.offset().left ); } } ); return this; } }; // jquery plugin $.fn.themeSticky = function ( opts ) { return this.map( function () { var $this = $( this ); if ( $this.data( instanceName ) ) { $this.trigger( 'recalc.pin' ); setTimeout( function () { $this.trigger( 'recalc.pin' ); }, 800 ); return $this.data( instanceName ); } else { return new Sticky( $this, opts ); } } ); } } ).apply( this, [ jQuery ] );