File indexing completed on 2024-12-29 05:26:12
0001 /** 0002 * bookblock.js v2.0.1 0003 * http://www.codrops.com 0004 * 0005 * Licensed under the MIT license. 0006 * http://www.opensource.org/licenses/mit-license.php 0007 * 0008 * Copyright 2013, Codrops 0009 * http://www.codrops.com 0010 */ 0011 ;( function( window ) { 0012 0013 'use strict'; 0014 0015 // global 0016 var document = window.document, 0017 Modernizr = window.Modernizr; 0018 0019 // https://gist.github.com/edankwan/4389601 0020 Modernizr.addTest('csstransformspreserve3d', function () { 0021 var prop = Modernizr.prefixed('transformStyle'); 0022 var val = 'preserve-3d'; 0023 var computedStyle; 0024 if(!prop) return false; 0025 0026 prop = prop.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-'); 0027 0028 Modernizr.testStyles('#modernizr{' + prop + ':' + val + ';}', function (el, rule) { 0029 computedStyle = window.getComputedStyle ? getComputedStyle(el, null).getPropertyValue(prop) : ''; 0030 }); 0031 0032 return (computedStyle === val); 0033 }); 0034 0035 function extend( a, b ) { 0036 for( var key in b ) { 0037 if( b.hasOwnProperty( key ) ) { 0038 a[key] = b[key]; 0039 } 0040 } 0041 return a; 0042 } 0043 0044 function BookBlock( el, options ) { 0045 this.el = el; 0046 this.options = extend( this.defaults, options ); 0047 this._init(); 0048 } 0049 0050 BookBlock.prototype = { 0051 defaults : { 0052 // vertical or horizontal flip 0053 orientation : 'vertical', 0054 // ltr (left to right) or rtl (right to left) 0055 direction : 'ltr', 0056 // speed for the flip transition in ms 0057 speed : 1000, 0058 // easing for the flip transition 0059 easing : 'ease-in-out', 0060 // if set to true, both the flipping page and the sides will have an overlay to simulate shadows 0061 shadows : true, 0062 // opacity value for the "shadow" on both sides (when the flipping page is over it) 0063 // value : 0.1 - 1 0064 shadowSides : 0.2, 0065 // opacity value for the "shadow" on the flipping page (while it is flipping) 0066 // value : 0.1 - 1 0067 shadowFlip : 0.1, 0068 // if we should show the first item after reaching the end 0069 circular : false, 0070 // if we want to specify a selector that triggers the next() function. example: ´#bb-nav-next´ 0071 nextEl : '', 0072 // if we want to specify a selector that triggers the prev() function 0073 prevEl : '', 0074 // autoplay. If true it overwrites the circular option to true 0075 autoplay : false, 0076 // time (ms) between page switch, if autoplay is true 0077 interval : 3000, 0078 // callback after the flip transition 0079 // old is the index of the previous item 0080 // page is the current item´s index 0081 // isLimit is true if the current page is the last one (or the first one) 0082 onEndFlip : function(old, page, isLimit) { return false; }, 0083 // callback before the flip transition 0084 // page is the current item´s index 0085 onBeforeFlip : function(page) { return false; } 0086 }, 0087 _init : function() { 0088 // orientation class 0089 this.el.className += ' bb-' + this.options.orientation; 0090 // items 0091 this.items = Array.prototype.slice.call( this.el.querySelectorAll( '.bb-item' ) ); 0092 // total items 0093 this.itemsCount = this.items.length; 0094 // current item´s index 0095 this.currentIdx = 0; 0096 // previous item´s index 0097 this.previous = -1; 0098 // show first item 0099 this.current = this.items[ this.currentIdx ]; 0100 this.current.style.display = 'block'; 0101 // get width of this.el 0102 // this will be necessary to create the flipping layout 0103 this.elWidth = this.el.offsetWidth; 0104 var transEndEventNames = { 0105 'WebkitTransition': 'webkitTransitionEnd', 0106 'MozTransition': 'transitionend', 0107 'OTransition': 'oTransitionEnd', 0108 'msTransition': 'MSTransitionEnd', 0109 'transition': 'transitionend' 0110 }; 0111 this.transEndEventName = transEndEventNames[Modernizr.prefixed( 'transition' )]; 0112 // support css 3d transforms && css transitions && Modernizr.csstransformspreserve3d 0113 this.support = Modernizr.csstransitions && Modernizr.csstransforms3d && Modernizr.csstransformspreserve3d; 0114 // initialize/bind some events 0115 this._initEvents(); 0116 // start slideshow 0117 if ( this.options.autoplay ) { 0118 this.options.circular = true; 0119 this._startSlideshow(); 0120 } 0121 }, 0122 _initEvents : function() { 0123 0124 var self = this; 0125 0126 if ( this.options.nextEl !== '' ) { 0127 document.querySelector( this.options.nextEl ).addEventListener( 'click', function() { self._action( 'next' ); return false; } ); 0128 document.querySelector( this.options.nextEl ).addEventListener( 'touchstart', function() { self._action( 'next' ); return false; } ); 0129 } 0130 0131 if ( this.options.prevEl !== '' ) { 0132 document.querySelector( this.options.prevEl ).addEventListener( 'click', function() { self._action( 'prev' ); return false; } ); 0133 document.querySelector( this.options.prevEl ).addEventListener( 'touchstart', function() { self._action( 'prev' ); return false; } ); 0134 } 0135 0136 window.addEventListener( 'resize', function() { self._resizeHandler(); } ); 0137 0138 }, 0139 _action : function( dir, page ) { 0140 this._stopSlideshow(); 0141 this._navigate( dir, page ); 0142 }, 0143 _navigate : function( dir, page ) { 0144 0145 if ( this.isAnimating ) { 0146 return false; 0147 } 0148 0149 // callback trigger 0150 this.options.onBeforeFlip( this.currentIdx ); 0151 0152 this.isAnimating = true; 0153 // update current value 0154 this.current = this.items[ this.currentIdx ]; 0155 0156 if ( page !== undefined ) { 0157 this.currentIdx = page; 0158 } 0159 else if ( dir === 'next' && this.options.direction === 'ltr' || dir === 'prev' && this.options.direction === 'rtl' ) { 0160 if ( !this.options.circular && this.currentIdx === this.itemsCount - 1 ) { 0161 this.end = true; 0162 } 0163 else { 0164 this.previous = this.currentIdx; 0165 this.currentIdx = this.currentIdx < this.itemsCount - 1 ? this.currentIdx + 1 : 0; 0166 } 0167 } 0168 else if ( dir === 'prev' && this.options.direction === 'ltr' || dir === 'next' && this.options.direction === 'rtl' ) { 0169 if ( !this.options.circular && this.currentIdx === 0 ) { 0170 this.end = true; 0171 } 0172 else { 0173 this.previous = this.currentIdx; 0174 this.currentIdx = this.currentIdx > 0 ? this.currentIdx - 1 : this.itemsCount - 1; 0175 } 0176 } 0177 0178 this.nextItem = !this.options.circular && this.end ? this.current : this.items[ this.currentIdx ]; 0179 0180 this.items.forEach( function( el, i ) { el.style.display = 'none'; } ); 0181 if ( !this.support ) { 0182 this._layoutNoSupport( dir ); 0183 } else { 0184 this._layout( dir ); 0185 } 0186 0187 }, 0188 _layoutNoSupport : function(dir) { 0189 this.nextItem.style.display = 'block'; 0190 this.end = false; 0191 this.isAnimating = false; 0192 var isLimit = dir === 'next' && this.currentIdx === this.itemsCount - 1 || dir === 'prev' && this.currentIdx === 0; 0193 // callback trigger 0194 this.options.onEndFlip( this.previous, this.currentIdx, isLimit ); 0195 }, 0196 // creates the necessary layout for the 3d structure and triggers the transitions 0197 _layout : function(dir) { 0198 0199 var self = this, 0200 // basic structure: 1 element for the left side. 0201 s_left = this._addSide( 'left', dir ), 0202 // 1 element for the flipping/middle page 0203 s_middle = this._addSide( 'middle', dir ), 0204 // 1 element for the right side 0205 s_right = this._addSide( 'right', dir ), 0206 // overlays 0207 o_left = s_left.querySelector( 'div.bb-overlay' ), 0208 o_middle_f = s_middle.querySelector( 'div.bb-front' ).querySelector( 'div.bb-flipoverlay' ), 0209 o_middle_b = s_middle.querySelector( 'div.bb-back' ).querySelector( 'div.bb-flipoverlay' ), 0210 o_right = s_right.querySelector( 'div.bb-overlay' ), 0211 speed = this.end ? 400 : this.options.speed; 0212 0213 var fChild = this.items[0]; 0214 this.el.insertBefore( s_left, fChild ); 0215 this.el.insertBefore( s_middle, fChild ); 0216 this.el.insertBefore( s_right, fChild ); 0217 s_left.style.zIndex = 102; 0218 s_middle.style.zIndex = 103; 0219 s_right.style.zIndex = 101; 0220 0221 s_middle.style.transitionDuration = speed + 'ms'; 0222 s_middle.style.transitionTimingFunction = this.options.easing; 0223 0224 s_middle.addEventListener( this.transEndEventName, function( event ) { 0225 if ( (" " + event.target.className + " ").replace(/[\n\t]/g, " ").indexOf(" bb-page ") > -1 ) { 0226 Array.prototype.slice.call( self.el.querySelectorAll( '.bb-page' ) ).forEach( function( el, i ) { 0227 self.el.removeChild( el ); 0228 } ); 0229 self.nextItem.style.display = 'block'; 0230 self.end = false; 0231 self.isAnimating = false; 0232 var isLimit = dir === 'next' && self.currentIdx === self.itemsCount - 1 || dir === 'prev' && self.currentIdx === 0; 0233 // callback trigger 0234 self.options.onEndFlip( self.previous, self.currentIdx, isLimit ); 0235 } 0236 } ); 0237 0238 if ( dir === 'prev' ) { 0239 s_middle.className += ' bb-flip-initial'; 0240 } 0241 0242 // overlays 0243 if ( this.options.shadows && !this.end ) { 0244 if( dir === 'next' ) { 0245 o_middle_f.style.transition = 'opacity ' + this.options.speed / 2 + 'ms ' + 'linear'; 0246 o_middle_b.style.transition = 'opacity ' + this.options.speed / 2 + 'ms ' + 'linear' + ' ' + this.options.speed / 2 + 'ms'; 0247 o_middle_b.style.opacity = this.options.shadowFlip; 0248 o_left.style.transition = 'opacity ' + this.options.speed / 2 + 'ms ' + 'linear' + ' ' + this.options.speed / 2 + 'ms'; 0249 o_right.style.transition = 'opacity ' + this.options.speed / 2 + 'ms ' + 'linear'; 0250 o_right.style.opacity = this.options.shadowSides; 0251 } 0252 else if( dir === 'prev' ) { 0253 o_middle_f.style.transition = 'opacity ' + this.options.speed / 2 + 'ms ' + 'linear' + ' ' + this.options.speed / 2 + 'ms'; 0254 o_middle_f.style.opacity = this.options.shadowFlip; 0255 o_middle_b.style.transition = 'opacity ' + this.options.speed / 2 + 'ms ' + 'linear'; 0256 o_left.style.transition = 'opacity ' + this.options.speed / 2 + 'ms ' + 'linear'; 0257 o_left.style.opacity = this.options.shadowSides; 0258 o_right.style.transition = 'opacity ' + this.options.speed / 2 + 'ms ' + 'linear' + ' ' + this.options.speed / 2 + 'ms'; 0259 } 0260 } 0261 0262 setTimeout( function() { 0263 // first && last pages lift slightly up when we can't go further 0264 s_middle.className += self.end ? ' bb-flip-' + dir + '-end' : ' bb-flip-' + dir; 0265 0266 // overlays 0267 if ( self.options.shadows && !self.end ) { 0268 o_middle_f.style.opacity = dir === 'next' ? self.options.shadowFlip : 0; 0269 o_middle_b.style.opacity = dir === 'next' ? 0 : self.options.shadowFlip; 0270 o_left.style.opacity = dir === 'next' ? self.options.shadowSides : 0; 0271 o_right.style.opacity = dir === 'next' ? 0 : self.options.shadowSides; 0272 } 0273 }, 25 ); 0274 }, 0275 // adds the necessary sides (bb-page) to the layout 0276 _addSide : function( side, dir ) { 0277 var sideEl = document.createElement( 'div' ); 0278 sideEl.className = 'bb-page'; 0279 0280 switch (side) { 0281 case 'left': 0282 /* 0283 <div class="bb-page" style="z-index:102;"> 0284 <div class="bb-back"> 0285 <div class="bb-outer"> 0286 <div class="bb-content"> 0287 <div class="bb-inner"> 0288 dir==='next' ? [content of current page] : [content of next page] 0289 </div> 0290 </div> 0291 <div class="bb-overlay"></div> 0292 </div> 0293 </div> 0294 </div> 0295 */ 0296 var inner = dir === 'next' ? this.current.innerHTML : this.nextItem.innerHTML; 0297 sideEl.innerHTML = '<div class="bb-back"><div class="bb-outer"><div class="bb-content"><div class="bb-inner">' + inner + '</div></div><div class="bb-overlay"></div></div></div>'; 0298 break; 0299 case 'middle': 0300 /* 0301 <div class="bb-page" style="z-index:103;"> 0302 <div class="bb-front"> 0303 <div class="bb-outer"> 0304 <div class="bb-content"> 0305 <div class="bb-inner"> 0306 dir==='next' ? [content of current page] : [content of next page] 0307 </div> 0308 </div> 0309 <div class="bb-flipoverlay"></div> 0310 </div> 0311 </div> 0312 <div class="bb-back"> 0313 <div class="bb-outer"> 0314 <div class="bb-content"> 0315 <div class="bb-inner"> 0316 dir==='next' ? [content of next page] : [content of current page] 0317 </div> 0318 </div> 0319 <div class="bb-flipoverlay"></div> 0320 </div> 0321 </div> 0322 </div> 0323 */ 0324 var frontinner = dir === 'next' ? this.current.innerHTML : this.nextItem.innerHTML; 0325 var backinner = dir === 'next' ? this.nextItem.innerHTML : this.current.innerHTML; 0326 sideEl.innerHTML = '<div class="bb-front"><div class="bb-outer"><div class="bb-content"><div class="bb-inner">' + frontinner + '</div></div><div class="bb-flipoverlay"></div></div></div><div class="bb-back"><div class="bb-outer"><div class="bb-content" style="width:' + this.elWidth + 'px"><div class="bb-inner">' + backinner + '</div></div><div class="bb-flipoverlay"></div></div></div>'; 0327 break; 0328 case 'right': 0329 /* 0330 <div class="bb-page" style="z-index:101;"> 0331 <div class="bb-front"> 0332 <div class="bb-outer"> 0333 <div class="bb-content"> 0334 <div class="bb-inner"> 0335 dir==='next' ? [content of next page] : [content of current page] 0336 </div> 0337 </div> 0338 <div class="bb-overlay"></div> 0339 </div> 0340 </div> 0341 </div> 0342 */ 0343 var inner = dir === 'next' ? this.nextItem.innerHTML : this.current.innerHTML; 0344 sideEl.innerHTML = '<div class="bb-front"><div class="bb-outer"><div class="bb-content"><div class="bb-inner">' + inner + '</div></div><div class="bb-overlay"></div></div></div>'; 0345 break; 0346 } 0347 0348 return sideEl; 0349 }, 0350 _startSlideshow : function() { 0351 var self = this; 0352 this.slideshow = setTimeout( function() { 0353 self._navigate( 'next' ); 0354 if ( self.options.autoplay ) { 0355 self._startSlideshow(); 0356 } 0357 }, this.options.interval ); 0358 }, 0359 _stopSlideshow : function() { 0360 if ( this.options.autoplay ) { 0361 clearTimeout( this.slideshow ); 0362 this.options.autoplay = false; 0363 } 0364 }, 0365 // public method: flips next 0366 next : function() { 0367 this._action( this.options.direction === 'ltr' ? 'next' : 'prev' ); 0368 }, 0369 // public method: flips back 0370 prev : function() { 0371 this._action( this.options.direction === 'ltr' ? 'prev' : 'next' ); 0372 }, 0373 // public method: goes to a specific page 0374 jump : function( page ) { 0375 0376 page -= 1; 0377 0378 if ( page === this.currentIdx || page >= this.itemsCount || page < 0 ) { 0379 return false; 0380 } 0381 var dir; 0382 if( this.options.direction === 'ltr' ) { 0383 dir = page > this.currentIdx ? 'next' : 'prev'; 0384 } 0385 else { 0386 dir = page > this.currentIdx ? 'prev' : 'next'; 0387 } 0388 this._action( dir, page ); 0389 0390 }, 0391 // public method: goes to the last page 0392 last : function() { 0393 this.jump( this.itemsCount ); 0394 }, 0395 // public method: goes to the first page 0396 first : function() { 0397 this.jump( 1 ); 0398 }, 0399 // taken from https://github.com/desandro/vanilla-masonry/blob/master/masonry.js by David DeSandro 0400 // original debounce by John Hann 0401 // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ 0402 _resizeHandler : function() { 0403 var self = this; 0404 function delayed() { 0405 self._resize(); 0406 self._resizeTimeout = null; 0407 } 0408 if ( this._resizeTimeout ) { 0409 clearTimeout( this._resizeTimeout ); 0410 } 0411 this._resizeTimeout = setTimeout( delayed, 50 ); 0412 }, 0413 _resize : function() { 0414 // update width value 0415 this.elWidth = this.el.offsetWidth; 0416 }, 0417 // public method: check if isAnimating is true 0418 isActive: function() { 0419 return this.isAnimating; 0420 }, 0421 // public method: dynamically adds new elements 0422 // call this method after inserting new "bb-item" elements inside the BookBlock 0423 update : function () { 0424 var currentItem = this.items[ this.current ]; 0425 this.items = Array.prototype.slice.call( this.el.querySelectorAll( '.bb-item' ) ); 0426 this.itemsCount = this.items.length; 0427 this.currentIdx = this.items.indexOf( currentItem ); 0428 }, 0429 destroy : function() { 0430 if ( this.options.autoplay ) { 0431 this._stopSlideshow(); 0432 } 0433 this.el.className = this.el.className.replace(new RegExp("(^|\\s+)" + 'bb-' + this.options.orientation + "(\\s+|$)"), ' '); 0434 this.items.forEach( function( el, i ) { el.style.display = 'block'; } ); 0435 0436 if ( this.options.nextEl !== '' ) { 0437 this.options.nextEl.removeEventListener( 'click' ); 0438 this.options.nextEl.removeEventListener( 'touchstart' ); 0439 } 0440 0441 if ( this.options.prevEl !== '' ) { 0442 this.options.prevEl.removeEventListener( 'click' ); 0443 this.options.prevEl.removeEventListener( 'touchstart' ); 0444 } 0445 0446 window.removeEventListener( 'debouncedresize' ); 0447 } 0448 } 0449 0450 // add to global namespace 0451 window.BookBlock = BookBlock; 0452 0453 } )( window );