/** * cbpGridGallery.js v1.0.0 * http://www.codrops.com * * Licensed under the MIT license. * http://www.opensource.org/licenses/mit-license.php * * Copyright 2014, Codrops * http://www.codrops.com */ ;( function( window ) { 'use strict'; var docElem = window.document.documentElement, transEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd', 'transition': 'transitionend' }, transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ], support = { transitions : Modernizr.csstransitions, support3d : Modernizr.csstransforms3d }; function setTransform( el, transformStr ) { el.style.WebkitTransform = transformStr; el.style.msTransform = transformStr; el.style.transform = transformStr; } // from http://responsejs.com/labs/dimensions/ function getViewportW() { var client = docElem['clientWidth'], inner = window['innerWidth']; if( client < inner ) return inner; else return client; } function extend( a, b ) { for( var key in b ) { if( b.hasOwnProperty( key ) ) { a[key] = b[key]; } } return a; } function CBPGridGallery( el, options ) { this.el = el; this.options = extend( {}, this.options ); extend( this.options, options ); this._init(); } CBPGridGallery.prototype.options = { }; CBPGridGallery.prototype._init = function() { // main grid this.grid = this.el.querySelector( 'section.grid-wrap > ul.grid' ); // main grid items this.gridItems = [].slice.call( this.grid.querySelectorAll( 'li:not(.grid-sizer)' ) ); // items total this.itemsCount = this.gridItems.length; // slideshow grid this.slideshow = this.el.querySelector( 'section.slideshow > ul' ); // slideshow grid items this.slideshowItems = [].slice.call( this.slideshow.children ); // index of current slideshow item this.current = -1; // slideshow control buttons this.ctrlPrev = this.el.querySelector( 'section.slideshow > nav > span.nav-prev' ); this.ctrlNext = this.el.querySelector( 'section.slideshow > nav > span.nav-next' ); this.ctrlClose = this.el.querySelector( 'section.slideshow > nav > span.nav-close' ); // init masonry grid this._initMasonry(); // init events this._initEvents(); }; CBPGridGallery.prototype._initMasonry = function() { var grid = this.grid; imagesLoaded( grid, function() { new Masonry( grid, { itemSelector: 'li', columnWidth: grid.querySelector( '.grid-sizer' ) }); }); }; CBPGridGallery.prototype._initEvents = function() { var self = this; // open the slideshow when clicking on the main grid items this.gridItems.forEach( function( item, idx ) { item.addEventListener( 'click', function() { self._openSlideshow( idx ); } ); } ); // slideshow controls this.ctrlPrev.addEventListener( 'click', function() { self._navigate( 'prev' ); } ); this.ctrlNext.addEventListener( 'click', function() { self._navigate( 'next' ); } ); this.ctrlClose.addEventListener( 'click', function() { self._closeSlideshow(); } ); // window resize window.addEventListener( 'resize', function() { self._resizeHandler(); } ); // keyboard navigation events document.addEventListener( 'keydown', function( ev ) { if ( self.isSlideshowVisible ) { var keyCode = ev.keyCode || ev.which; switch (keyCode) { case 37: self._navigate( 'prev' ); break; case 39: self._navigate( 'next' ); break; case 27: self._closeSlideshow(); break; } } } ); // trick to prevent scrolling when slideshow is visible window.addEventListener( 'scroll', function() { if ( self.isSlideshowVisible ) { window.scrollTo( self.scrollPosition ? self.scrollPosition.x : 0, self.scrollPosition ? self.scrollPosition.y : 0 ); } else { self.scrollPosition = { x : window.pageXOffset || docElem.scrollLeft, y : window.pageYOffset || docElem.scrollTop }; } }); }; CBPGridGallery.prototype._openSlideshow = function( pos ) { this.isSlideshowVisible = true; this.current = pos; classie.addClass( this.el, 'slideshow-open' ); /* position slideshow items */ // set viewport items (current, next and previous) this._setViewportItems(); // add class "current" and "show" to currentItem classie.addClass( this.currentItem, 'current' ); classie.addClass( this.currentItem, 'show' ); // add class show to next and previous items // position previous item on the left side and the next item on the right side if( this.prevItem ) { classie.addClass( this.prevItem, 'show' ); var translateVal = Number( -1 * ( getViewportW() / 2 + this.prevItem.offsetWidth / 2 ) ); setTransform( this.prevItem, support.support3d ? 'translate3d(' + translateVal + 'px, 0, -150px)' : 'translate(' + translateVal + 'px)' ); } if( this.nextItem ) { classie.addClass( this.nextItem, 'show' ); var translateVal = Number( getViewportW() / 2 + this.nextItem.offsetWidth / 2 ); setTransform( this.nextItem, support.support3d ? 'translate3d(' + translateVal + 'px, 0, -150px)' : 'translate(' + translateVal + 'px)' ); } }; CBPGridGallery.prototype._navigate = function( dir ) { if( this.isAnimating ) return; if( dir === 'next' && this.current === this.itemsCount - 1 || dir === 'prev' && this.current === 0 ) { this._closeSlideshow(); return; } this.isAnimating = true; // reset viewport items this._setViewportItems(); var self = this, itemWidth = this.currentItem.offsetWidth, // positions for the centered/current item, both the side items and the incoming ones transformLeftStr = support.support3d ? 'translate3d(-' + Number( getViewportW() / 2 + itemWidth / 2 ) + 'px, 0, -150px)' : 'translate(-' + Number( getViewportW() / 2 + itemWidth / 2 ) + 'px)', transformRightStr = support.support3d ? 'translate3d(' + Number( getViewportW() / 2 + itemWidth / 2 ) + 'px, 0, -150px)' : 'translate(' + Number( getViewportW() / 2 + itemWidth / 2 ) + 'px)', transformCenterStr = '', transformOutStr, transformIncomingStr, // incoming item incomingItem; if( dir === 'next' ) { transformOutStr = support.support3d ? 'translate3d( -' + Number( (getViewportW() * 2) / 2 + itemWidth / 2 ) + 'px, 0, -150px )' : 'translate(-' + Number( (getViewportW() * 2) / 2 + itemWidth / 2 ) + 'px)'; transformIncomingStr = support.support3d ? 'translate3d( ' + Number( (getViewportW() * 2) / 2 + itemWidth / 2 ) + 'px, 0, -150px )' : 'translate(' + Number( (getViewportW() * 2) / 2 + itemWidth / 2 ) + 'px)'; } else { transformOutStr = support.support3d ? 'translate3d( ' + Number( (getViewportW() * 2) / 2 + itemWidth / 2 ) + 'px, 0, -150px )' : 'translate(' + Number( (getViewportW() * 2) / 2 + itemWidth / 2 ) + 'px)'; transformIncomingStr = support.support3d ? 'translate3d( -' + Number( (getViewportW() * 2) / 2 + itemWidth / 2 ) + 'px, 0, -150px )' : 'translate(-' + Number( (getViewportW() * 2) / 2 + itemWidth / 2 ) + 'px)'; } // remove class animatable from the slideshow grid (if it has already) classie.removeClass( self.slideshow, 'animatable' ); if( dir === 'next' && this.current < this.itemsCount - 2 || dir === 'prev' && this.current > 1 ) { // we have an incoming item! incomingItem = this.slideshowItems[ dir === 'next' ? this.current + 2 : this.current - 2 ]; setTransform( incomingItem, transformIncomingStr ); classie.addClass( incomingItem, 'show' ); } var slide = function() { // add class animatable to the slideshow grid classie.addClass( self.slideshow, 'animatable' ); // overlays: classie.removeClass( self.currentItem, 'current' ); var nextCurrent = dir === 'next' ? self.nextItem : self.prevItem; classie.addClass( nextCurrent, 'current' ); setTransform( self.currentItem, dir === 'next' ? transformLeftStr : transformRightStr ); if( self.nextItem ) { setTransform( self.nextItem, dir === 'next' ? transformCenterStr : transformOutStr ); } if( self.prevItem ) { setTransform( self.prevItem, dir === 'next' ? transformOutStr : transformCenterStr ); } if( incomingItem ) { setTransform( incomingItem, dir === 'next' ? transformRightStr : transformLeftStr ); } var onEndTransitionFn = function( ev ) { if( support.transitions ) { if( ev.propertyName.indexOf( 'transform' ) === -1 ) return false; this.removeEventListener( transEndEventName, onEndTransitionFn ); } if( self.prevItem && dir === 'next' ) { classie.removeClass( self.prevItem, 'show' ); } else if( self.nextItem && dir === 'prev' ) { classie.removeClass( self.nextItem, 'show' ); } if( dir === 'next' ) { self.prevItem = self.currentItem; self.currentItem = self.nextItem; if( incomingItem ) { self.nextItem = incomingItem; } } else { self.nextItem = self.currentItem; self.currentItem = self.prevItem; if( incomingItem ) { self.prevItem = incomingItem; } } self.current = dir === 'next' ? self.current + 1 : self.current - 1; self.isAnimating = false; }; if( support.transitions ) { self.currentItem.addEventListener( transEndEventName, onEndTransitionFn ); } else { onEndTransitionFn(); } }; setTimeout( slide, 25 ); } CBPGridGallery.prototype._closeSlideshow = function( pos ) { // remove class slideshow-open from the grid gallery elem classie.removeClass( this.el, 'slideshow-open' ); // remove class animatable from the slideshow grid classie.removeClass( this.slideshow, 'animatable' ); var self = this, onEndTransitionFn = function( ev ) { if( support.transitions ) { if( ev.target.tagName.toLowerCase() !== 'ul' ) return; this.removeEventListener( transEndEventName, onEndTransitionFn ); } // remove classes show and current from the slideshow items classie.removeClass( self.currentItem, 'current' ); classie.removeClass( self.currentItem, 'show' ); if( self.prevItem ) { classie.removeClass( self.prevItem, 'show' ); } if( self.nextItem ) { classie.removeClass( self.nextItem, 'show' ); } // also reset any transforms for all the items self.slideshowItems.forEach( function( item ) { setTransform( item, '' ); } ); self.isSlideshowVisible = false; }; if( support.transitions ) { this.el.addEventListener( transEndEventName, onEndTransitionFn ); } else { onEndTransitionFn(); } }; CBPGridGallery.prototype._setViewportItems = function() { this.currentItem = null; this.prevItem = null; this.nextItem = null; if( this.current > 0 ) { this.prevItem = this.slideshowItems[ this.current - 1 ]; } if( this.current < this.itemsCount - 1 ) { this.nextItem = this.slideshowItems[ this.current + 1 ]; } this.currentItem = this.slideshowItems[ this.current ]; } // taken from https://github.com/desandro/vanilla-masonry/blob/master/masonry.js by David DeSandro // original debounce by John Hann // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ CBPGridGallery.prototype._resizeHandler = function() { var self = this; function delayed() { self._resize(); self._resizeTimeout = null; } if ( this._resizeTimeout ) { clearTimeout( this._resizeTimeout ); } this._resizeTimeout = setTimeout( delayed, 50 ); } CBPGridGallery.prototype._resize = function() { if ( this.isSlideshowVisible ) { // update width value if( this.prevItem ) { var translateVal = Number( -1 * ( getViewportW() / 2 + this.prevItem.offsetWidth / 2 ) ); setTransform( this.prevItem, support.support3d ? 'translate3d(' + translateVal + 'px, 0, -150px)' : 'translate(' + translateVal + 'px)' ); } if( this.nextItem ) { var translateVal = Number( getViewportW() / 2 + this.nextItem.offsetWidth / 2 ); setTransform( this.nextItem, support.support3d ? 'translate3d(' + translateVal + 'px, 0, -150px)' : 'translate(' + translateVal + 'px)' ); } } } // add to global namespace window.CBPGridGallery = CBPGridGallery; })( window );