1/* 2* Curtain.js - Create an unique page transitioning system 3* --- 4* Version: 2 5* Copyright 2011, Victor Coulon (http://victorcoulon.fr) 6* Released under the MIT Licence 7*/ 8 9(function ( $, window, document, undefined ) { 10 11 var pluginName = 'curtain', 12 defaults = { 13 scrollSpeed: 400, 14 bodyHeight: 0, 15 linksArray: [], 16 mobile: false, 17 scrollButtons: {}, 18 controls: null, 19 curtainLinks: '.curtain-links', 20 enableKeys: true, 21 easing: 'swing', 22 disabled: false, 23 nextSlide: function() {}, 24 prevSlide: function() {} 25 }; 26 27 // The actual plugin constructor 28 function Plugin( element, options ) { 29 var self = this; 30 31 // Public attributes 32 this.element = element; 33 this.options = $.extend( {}, defaults, options) ; 34 35 this._defaults = defaults; 36 this._name = pluginName; 37 this._ignoreHashChange = false; 38 39 this.init(); 40 } 41 42 Plugin.prototype = { 43 init: function () { 44 var self = this; 45 46 // Cache element 47 this.$element = $(this.element); 48 this.$li = $(this.element).find('>li'); 49 this.$liLength = this.$li.length; 50 self.$windowHeight = $(window).height(); 51 self.$elDatas = {}; 52 self.$document = $(document); 53 self.$window = $(window); 54 55 56 self.webkit = (navigator.userAgent.indexOf('Chrome') > -1 || navigator.userAgent.indexOf("Safari") > -1); 57 $.msie = /msie/.test(navigator.userAgent.toLowerCase()); 58 $.mozilla = /firefox/.test(navigator.userAgent.toLowerCase()); 59 $.Android = (navigator.userAgent.match(/Android/i)); 60 $.iPhone = ((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i))); 61 $.iPad = ((navigator.userAgent.match(/iPad/i))); 62 $.iOs4 = (/OS [1-4]_[0-9_]+ like Mac OS X/i.test(navigator.userAgent)); 63 64 /*if($.iPhone || $.iPad || $.Android || self.options.disabled){ 65 this.options.mobile = true; 66 this.$li.css({position:'relative'}); 67 this.$element.find('.fixed').css({position:'absolute'}); 68 }*/ 69 70 if(this.options.mobile){ 71 this.scrollEl = this.$element; 72 } else if($.mozilla || $.msie) { 73 this.scrollEl = $('html'); 74 } else { 75 this.scrollEl = $('body'); 76 } 77 78 if(self.options.controls){ 79 self.options.scrollButtons['up'] = $(self.options.controls).find('[href="#up"]'); 80 self.options.scrollButtons['down'] = $(self.options.controls).find('[href="#down"]'); 81 82 if(!$.iOs4 && ($.iPhone || $.iPad)){ 83 self.$element.css({ 84 position:'fixed', 85 top:0, 86 left:0, 87 right:0, 88 bottom:0, 89 '-webkit-overflow-scrolling':'touch', 90 overflow:'auto' 91 }); 92 $(self.options.controls).css({position:'absolute'}); 93 } 94 } 95 96 // When all image is loaded 97 var callbackImageLoaded = function(){ 98 self.setDimensions(); 99 self.$li.eq(0).addClass('current'); 100 101 self.setCache(); 102 103 if(!self.options.mobile){ 104 if(self.$li.eq(1).length) 105 self.$li.eq(1).nextAll().addClass('hidden'); 106 } 107 108 self.setEvents(); 109 self.setLinks(); 110 self.isHashIsOnList(location.hash.substring(1)); 111 }; 112 113 if(self.$element.find('img').length) 114 self.imageLoaded(callbackImageLoaded); 115 else 116 callbackImageLoaded(); 117 118 }, 119 // Events 120 scrollToPosition: function (direction){ 121 var position = null, 122 self = this; 123 124 if(self.scrollEl.is(':animated')){ 125 return false;
126 } 127 128 if(direction === 'up' || direction === 'down'){ 129 // Keyboard event 130 var $next = (direction === 'up') ? self.$current.prev() : self.$current.next(); 131 132 // Step in the current panel ? 133 if(self.$step){ 134 135 if(!self.$current.find('.current-step').length){ 136 self.$step.eq(0).addClass('current-step'); 137 } 138 139 var $nextStep = (direction === 'up') ? self.$current.find('.current-step').prev('.step') : self.$current.find('.current-step').next('.step'); 140 141 if($nextStep.length) { 142 position = (self.options.mobile) ? $nextStep.position().top + self.$elDatas[self.$current.index()]['data-position'] : $nextStep.position().top + self.$elDatas[self.$current.index()]['data-position']; 143 } 144 } 145 146 position = position || ((self.$elDatas[$next.index()] === undefined) ? null : self.$elDatas[$next.index()]['data-position']); 147 148 if(position !== null){ 149 self.scrollEl.animate({ 150 scrollTop: position 151 }, self.options.scrollSpeed, self.options.easing); 152 } 153 154 } else if(direction === 'top'){ 155 self.scrollEl.animate({ 156 scrollTop:0 157 }, self.options.scrollSpeed, self.options.easing); 158 } else if(direction === 'bottom'){ 159 self.scrollEl.animate({ 160 scrollTop:self.options.bodyHeight 161 }, self.options.scrollSpeed, self.options.easing); 162 } else { 163 var index = $("#"+direction).index(), 164 speed = Math.abs(self.currentIndex-index) * (this.options.scrollSpeed*4) / self.$liLength; 165 166 self.scrollEl.animate({ 167 scrollTop:self.$elDatas[index]['data-position'] || null 168 }, (speed <= self.options.scrollSpeed) ? self.options.scrollSpeed : speed, this.options.easing); 169 } 170 171 }, 172 scrollEvent: function() { 173 var self = this, 174 docTop = self.$document.scrollTop(); 175 176 if(docTop < self.currentP && self.currentIndex > 0){ 177 // Scroll to top 178 self._ignoreHashChange = true; 179 180 if(self.$current.prev().attr('id')) 181 self.setHash(self.$current.prev().attr('id')); 182 183 self.$current 184 .removeClass('current') 185 .css( (self.webkit) ? {'-webkit-transform': 'translateY(0px) translateZ(0)'} : {marginTop: 0} ) 186 .nextAll().addClass('hidden').end() 187 .prev().addClass('current').removeClass('hidden'); 188 189 self.setCache(); 190 self.options.prevSlide(); 191 192 } else if(docTop < (self.currentP + self.currentHeight)){ 193 194 // Animate the current pannel during the scroll 195 if(self.webkit) 196 self.$current.css({'-webkit-transform': 'translateY('+(-(docTop-self.currentP))+'px) translateZ(0)' }); 197 else 198 self.$current.css({marginTop: -(docTop-self.currentP) }); 199 200 // If there is a fixed element in the current panel 201 if(self.$fixedLength){ 202 var dataTop = parseInt(self.$fixed.attr('data-top'), 10); 203 204 if(docTop + self.$windowHeight >= self.currentP + self.currentHeight){ 205 self.$fixed.css({ 206 position: 'fixed' 207 }); 208 } else { 209 self.$fixed.css({ 210 position: 'absolute', 211 marginTop: Math.abs(docTop-self.currentP) 212 }); 213 } 214 } 215 216 // If there is a step element in the current panel 217 if(self.$stepLength){ 218 $.each(self.$step, function(i,el){ 219 if(($(el).position().top+self.currentP) <= docTop+5 && $(el).position().top + self.currentP + $(el).height() >= docTop+5){ 220 if(!$(el).hasClass('current-step')){ 221 self.$step.removeClass('current-step'); 222 $(el).addClass('current-step'); 223 return false;
224 } 225 } 226 }); 227 } 228 229 230 if(self.parallaxBg){ 231 var tr = docTop * -0.07; 232 233 self.parallaxBg.css({ 234 '-webkit-transform' : 'translate3d(0,' + tr + 'px,0)', 235 '-moz-transform' : 'translate3d(0,' + tr + 'px,0)', 236 'transform' : 'translate3d(0,' + tr + 'px,0)' 237 }); 238 } 239 240 if(self.$fade.length){ 241 self.$fade.css({ 242 'z-index': self.z - 1, 243 'opacity': Math.abs(1- ( (docTop-self.currentP) / self.$windowHeight)) 244 }); 245 } 246 247 if(self.$slowScroll.length){ 248 self.$slowScroll.css({ 249 'margin-top' : (docTop / self.$slowScroll.attr('data-slow-scroll')) 250 }); 251 } 252 253 } else { 254 // Scroll bottom 255 self._ignoreHashChange = true; 256 if(self.$current.next().attr('id')) 257 self.setHash(self.$current.next().attr('id')); 258 259 self.$current.removeClass('current') 260 .addClass('hidden') 261 .next('li').addClass('current').next('li').removeClass('hidden'); 262 263 self.setCache(); 264 self.options.nextSlide(); 265 } 266 267 }, 268 scrollMobileEvent: function() { 269 var self = this, 270 docTop = self.$element.scrollTop(); 271 272 if(docTop+10 < self.currentP && self.currentIndex > 0){ 273 274 // Scroll to top 275 self._ignoreHashChange = true; 276 277 if(self.$current.prev().attr('id')) 278 self.setHash(self.$current.prev().attr('id')); 279 280 self.$current.removeClass('current').prev().addClass('current'); 281 self.setCache(); 282 self.options.prevSlide(); 283 } else if(docTop+10 < (self.currentP + self.currentHeight)){ 284 285 // If there is a step element in the current panel 286 if(self.$stepLength){ 287 $.each(self.$step, function(i,el){ 288 if(($(el).position().top+self.currentP) <= docTop && (($(el).position().top+self.currentP) + $(el).outerHeight()) >= docTop){ 289 if(!$(el).hasClass('current-step')){ 290 self.$step.removeClass('current-step'); 291 $(el).addClass('current-step'); 292 } 293 } 294 }); 295 } 296 297 } else { 298 299 // Scroll bottom 300 self._ignoreHashChange = true; 301 if(self.$current.next().attr('id')) 302 self.setHash(self.$current.next().attr('id')); 303 304 self.$current.removeClass('current').next().addClass('current'); 305 self.setCache(); 306 self.options.nextSlide(); 307 } 308 309 310 }, 311 // Setters 312 setDimensions: function(){ 313 var self = this, 314 levelHeight = 0, 315 cover = false, 316 height = null; 317 318 self.$windowHeight = self.$window.height(); 319 320 this.$li.each(function(index) { 321 var $self = $(this); 322 cover = $self.hasClass('cover'); 323 324 if(cover){ 325 $self.css({height: self.$windowHeight, zIndex: 20-index}) 326 .attr('data-height',self.$windowHeight) 327 .attr('data-position',levelHeight); 328 329 self.$elDatas[$self.index()] = { 330 'data-height': parseInt(self.$windowHeight,10), 331 'data-position': parseInt(levelHeight, 10) 332 }; 333 334 levelHeight += self.$windowHeight; 335 336 } else{ 337 height = ($self.outerHeight() <= self.$windowHeight) ? self.$windowHeight : $self.outerHeight(); 338 $self.css({minHeight: height, zIndex: 20-index}) 339 .attr('data-height',height) 340 .attr('data-position',levelHeight); 341 342 self.$elDatas[$self.index()] = { 343 'data-height': parseInt(height, 10), 344 'data-position': parseInt(levelHeight, 10) 345 }; 346 347 levelHeight += height; 348 } 349 350 if($self.find('.fixed').length){ 351 var top = $self.find('.fixed').css('top'); 352 $self.find('.fixed').attr('data-top', top); 353 } 354 }); 355 if(!this.options.mobile) 356 this.setBodyHeight(); 357 }, 358 setEvents: function() { 359 var self = this; 360 361 $(window).on('resize', function(){ 362 self.setDimensions(); 363 }); 364 365 if(self.options.mobile) { 366 self.$element.on('scroll', function(){ 367 self.scrollMobileEvent(); 368 }); 369 } else { 370 self.$window.on('scroll', function(){ 371 self.scrollEvent(); 372 }); 373 } 374 375 if(self.options.enableKeys) { 376 self.$document.on('keydown', function(e){ 377 if(e.keyCode === 38 || e.keyCode === 37) { 378 self.scrollToPosition('up'); 379 e.preventDefault(); 380 return false;
381 } 382 if(e.keyCode === 40 || e.keyCode === 39){ 383 self.scrollToPosition('down'); 384 e.preventDefault(); 385 return false; 386 } 387 // Home button 388 if(e.keyCode === 36){ 389 self.scrollToPosition('top'); 390 e.preventDefault(); 391 return false; 392 } 393 // End button 394 if(e.keyCode === 35){ 395 self.scrollToPosition('bottom'); 396 e.preventDefault(); 397 return false; 398 } 399 }); 400 } 401 402 if(self.options.scrollButtons){ 403 if(self.options.scrollButtons.up){ 404 self.options.scrollButtons.up.on('click', function(e){ 405 e.preventDefault(); 406 self.scrollToPosition('up'); 407 }); 408 } 409 if(self.options.scrollButtons.down){ 410 self.options.scrollButtons.down.on('click', function(e){ 411 e.preventDefault(); 412 self.scrollToPosition('down'); 413 }); 414 } 415 } 416 417 if(self.options.curtainLinks){ 418 $(self.options.curtainLinks).on('click', function(e){ 419 e.preventDefault(); 420 var href = $(this).attr('href'); 421 422 if(!self.isHashIsOnList(href.substring(1)) && position) 423 return false; 424 var position = self.$elDatas[$(href).index()]['data-position'] || null; 425 426 if(position){ 427 self.scrollEl.animate({ 428 scrollTop:position 429 }, self.options.scrollSpeed, self.options.easing); 430 } 431 return false; 432 }); 433 } 434 435 self.$window.on("hashchange", function(event){ 436 if(self._ignoreHashChange === false){ 437 self.isHashIsOnList(location.hash.substring(1)); 438 } 439 self._ignoreHashChange = false; 440 }); 441 }, 442 setBodyHeight: function(){ 443 var h = 0; 444 445 for (var key in this.$elDatas) { 446 var obj = this.$elDatas[key]; 447 h += obj['data-height']; 448 } 449 450 this.options.bodyHeight = h; 451 $('body').height(h); 452 }, 453 setLinks: function(){ 454 var self = this; 455 this.$li.each(function() { 456 var id = $(this).attr('id') || 0; 457 self.options.linksArray.push(id); 458 }); 459 }, 460 setHash: function(hash){ 461 // "HARD FIX" 462 el = $('[href=#'+hash+']'); 463 el.parent().siblings('li').removeClass('active'); 464 el.parent().addClass('active'); 465 466 if(history.pushState) { 467 history.pushState(null, null, '#'+hash); 468 } 469 else { 470 location.hash = hash; 471 } 472 }, 473 setCache: function(){ 474 var self = this; 475 var imageHolderClassName = self.$element.data('image-holder-name'); 476 var fadeElementClassName = self.$element.data('fade-element-name'); 477 self.$current = self.$element.find('.current'); 478 self.$fixed = self.$current.find('.fixed'); 479 self.$fixedLength = self.$fixed.length; 480 self.$step = self.$current.find('.step'); 481 self.$stepLength = self.$step.length; 482 self.currentIndex = self.$current.index(); 483 self.currentP = self.$elDatas[self.currentIndex]['data-position']; 484 self.currentHeight = self.$elDatas[self.currentIndex]['data-height']; 485 self.z = self.$element.find('.current').css('z-index'); 486 self.parallaxBg = self.$current.find(imageHolderClassName); 487 self.$fade = $(fadeElementClassName); 488 self.$slowScroll = self.$current.find('[data-slow-scroll]'); 489 }, 490 // Utils 491 isHashIsOnList: function(hash){ 492 var self = this; 493 $.each(self.options.linksArray, function(i,val){ 494 if(val === hash){ 495 self.scrollToPosition(hash); 496 return false;
497 } 498 }); 499 }, 500 readyElement: function(el,callback){ 501 var interval = setInterval(function(){ 502 if(el.length){ 503 callback(el.length); 504 clearInterval(interval); 505 } 506 },60); 507 }, 508 imageLoaded: function(callback){ 509 var self = this, 510 elems = self.$element.find('img'), 511 len = elems.length, 512 blank = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="; 513 514 elems.on('load.imgloaded',function(){ 515 if (--len <= 0 && this.src !== blank || $(this).not(':visible')){ 516 elems.off('load.imgloaded'); 517 callback.call(elems,this); 518 } 519 }).each(function(){ 520 if (this.complete || this.complete === undefined){ 521 var src = this.src; 522 this.src = blank; 523 this.src = src; 524 } 525 }); 526 } 527 }; 528 529 530 531 $.fn[pluginName] = function ( options ) { 532 return this.each(function () { 533 if (!$.data(this, 'plugin_' + pluginName)) { 534 $.data(this, 'plugin_' + pluginName, new Plugin( this, options )); 535 } 536 }); 537 }; 538 539 540})( jQuery, window, document );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.