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 self.$current.css({ 232 'background-position-y': docTop * self.parallaxBg 233 }); 234 } 235 236 if(self.$fade.length){ 237 self.$fade.css({ 238 'opacity': 1-(docTop/ self.$fade.attr('data-fade')) 239 }); 240 } 241 242 if(self.$slowScroll.length){ 243 self.$slowScroll.css({ 244 'margin-top' : (docTop / self.$slowScroll.attr('data-slow-scroll')) 245 }); 246 } 247 248 } else { 249 // Scroll bottom 250 self._ignoreHashChange = true; 251 if(self.$current.next().attr('id')) 252 self.setHash(self.$current.next().attr('id')); 253 254 self.$current.removeClass('current') 255 .addClass('hidden') 256 .next('li').addClass('current').next('li').removeClass('hidden'); 257 258 self.setCache(); 259 self.options.nextSlide(); 260 } 261 262 }, 263 scrollMobileEvent: function() { 264 var self = this, 265 docTop = self.$element.scrollTop(); 266 267 if(docTop+10 < self.currentP && self.currentIndex > 0){ 268 269 // Scroll to top 270 self._ignoreHashChange = true; 271 272 if(self.$current.prev().attr('id')) 273 self.setHash(self.$current.prev().attr('id')); 274 275 self.$current.removeClass('current').prev().addClass('current'); 276 self.setCache(); 277 self.options.prevSlide(); 278 } else if(docTop+10 < (self.currentP + self.currentHeight)){ 279 280 // If there is a step element in the current panel 281 if(self.$stepLength){ 282 $.each(self.$step, function(i,el){ 283 if(($(el).position().top+self.currentP) <= docTop && (($(el).position().top+self.currentP) + $(el).outerHeight()) >= docTop){ 284 if(!$(el).hasClass('current-step')){ 285 self.$step.removeClass('current-step'); 286 $(el).addClass('current-step'); 287 } 288 } 289 }); 290 } 291 292 } else { 293 294 // Scroll bottom 295 self._ignoreHashChange = true; 296 if(self.$current.next().attr('id')) 297 self.setHash(self.$current.next().attr('id')); 298 299 self.$current.removeClass('current').next().addClass('current'); 300 self.setCache(); 301 self.options.nextSlide(); 302 } 303 304 305 }, 306 // Setters 307 setDimensions: function(){ 308 var self = this, 309 levelHeight = 0, 310 cover = false, 311 height = null; 312 313 self.$windowHeight = self.$window.height(); 314 315 this.$li.each(function(index) { 316 var $self = $(this); 317 cover = $self.hasClass('cover'); 318 319 if(cover){ 320 $self.css({height: self.$windowHeight, zIndex: 999-index}) 321 .attr('data-height',self.$windowHeight) 322 .attr('data-position',levelHeight); 323 324 self.$elDatas[$self.index()] = { 325 'data-height': parseInt(self.$windowHeight,10), 326 'data-position': parseInt(levelHeight, 10) 327 }; 328 329 levelHeight += self.$windowHeight; 330 331 } else{ 332 height = ($self.outerHeight() <= self.$windowHeight) ? self.$windowHeight : $self.outerHeight(); 333 $self.css({minHeight: height, zIndex: 999-index}) 334 .attr('data-height',height) 335 .attr('data-position',levelHeight); 336 337 self.$elDatas[$self.index()] = { 338 'data-height': parseInt(height, 10), 339 'data-position': parseInt(levelHeight, 10) 340 }; 341 342 levelHeight += height; 343 } 344 345 if($self.find('.fixed').length){ 346 var top = $self.find('.fixed').css('top'); 347 $self.find('.fixed').attr('data-top', top); 348 } 349 }); 350 if(!this.options.mobile) 351 this.setBodyHeight(); 352 }, 353 setEvents: function() { 354 var self = this; 355 356 $(window).on('resize', function(){ 357 self.setDimensions(); 358 }); 359 360 if(self.options.mobile) { 361 self.$element.on('scroll', function(){ 362 self.scrollMobileEvent(); 363 }); 364 } else { 365 self.$window.on('scroll', function(){ 366 self.scrollEvent(); 367 }); 368 } 369 370 if(self.options.enableKeys) { 371 self.$document.on('keydown', function(e){ 372 if(e.keyCode === 38 || e.keyCode === 37) { 373 self.scrollToPosition('up'); 374 e.preventDefault(); 375 return false;
376 } 377 if(e.keyCode === 40 || e.keyCode === 39){ 378 self.scrollToPosition('down'); 379 e.preventDefault(); 380 return false; 381 } 382 // Home button 383 if(e.keyCode === 36){ 384 self.scrollToPosition('top'); 385 e.preventDefault(); 386 return false; 387 } 388 // End button 389 if(e.keyCode === 35){ 390 self.scrollToPosition('bottom'); 391 e.preventDefault(); 392 return false; 393 } 394 }); 395 } 396 397 if(self.options.scrollButtons){ 398 if(self.options.scrollButtons.up){ 399 self.options.scrollButtons.up.on('click', function(e){ 400 e.preventDefault(); 401 self.scrollToPosition('up'); 402 }); 403 } 404 if(self.options.scrollButtons.down){ 405 self.options.scrollButtons.down.on('click', function(e){ 406 e.preventDefault(); 407 self.scrollToPosition('down'); 408 }); 409 } 410 } 411 412 if(self.options.curtainLinks){ 413 $(self.options.curtainLinks).on('click', function(e){ 414 e.preventDefault(); 415 var href = $(this).attr('href'); 416 417 if(!self.isHashIsOnList(href.substring(1)) && position) 418 return false; 419 var position = self.$elDatas[$(href).index()]['data-position'] || null; 420 421 if(position){ 422 self.scrollEl.animate({ 423 scrollTop:position 424 }, self.options.scrollSpeed, self.options.easing); 425 } 426 return false; 427 }); 428 } 429 430 self.$window.on("hashchange", function(event){ 431 if(self._ignoreHashChange === false){ 432 self.isHashIsOnList(location.hash.substring(1)); 433 } 434 self._ignoreHashChange = false; 435 }); 436 }, 437 setBodyHeight: function(){ 438 var h = 0; 439 440 for (var key in this.$elDatas) { 441 var obj = this.$elDatas[key]; 442 h += obj['data-height']; 443 } 444 445 this.options.bodyHeight = h; 446 $('body').height(h); 447 }, 448 setLinks: function(){ 449 var self = this; 450 this.$li.each(function() { 451 var id = $(this).attr('id') || 0; 452 self.options.linksArray.push(id); 453 }); 454 }, 455 setHash: function(hash){ 456 // "HARD FIX" 457 el = $('[href=#'+hash+']'); 458 el.parent().siblings('li').removeClass('active'); 459 el.parent().addClass('active'); 460 461 if(history.pushState) { 462 history.pushState(null, null, '#'+hash); 463 } 464 else { 465 location.hash = hash; 466 } 467 }, 468 setCache: function(){ 469 var self = this; 470 self.$current = self.$element.find('.current'); 471 self.$fixed = self.$current.find('.fixed'); 472 self.$fixedLength = self.$fixed.length; 473 self.$step = self.$current.find('.step'); 474 self.$stepLength = self.$step.length; 475 self.currentIndex = self.$current.index(); 476 self.currentP = self.$elDatas[self.currentIndex]['data-position']; 477 self.currentHeight = self.$elDatas[self.currentIndex]['data-height']; 478 479 self.parallaxBg = self.$current.attr('data-parallax-background'); 480 self.$fade = self.$current.find('[data-fade]'); 481 self.$slowScroll = self.$current.find('[data-slow-scroll]'); 482 483 }, 484 // Utils 485 isHashIsOnList: function(hash){ 486 var self = this; 487 $.each(self.options.linksArray, function(i,val){ 488 if(val === hash){ 489 self.scrollToPosition(hash); 490 return false;
491 } 492 }); 493 }, 494 readyElement: function(el,callback){ 495 var interval = setInterval(function(){ 496 if(el.length){ 497 callback(el.length); 498 clearInterval(interval); 499 } 500 },60); 501 }, 502 imageLoaded: function(callback){ 503 var self = this, 504 elems = self.$element.find('img'), 505 len = elems.length, 506 blank = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="; 507 508 elems.bind('load.imgloaded',function(){ 509 if (--len <= 0 && this.src !== blank || $(this).not(':visible')){ 510 elems.unbind('load.imgloaded'); 511 callback.call(elems,this); 512 } 513 }).each(function(){ 514 if (this.complete || this.complete === undefined){ 515 var src = this.src; 516 this.src = blank; 517 this.src = src; 518 } 519 }); 520 } 521 }; 522 523 524 525 $.fn[pluginName] = function ( options ) { 526 return this.each(function () { 527 if (!$.data(this, 'plugin_' + pluginName)) { 528 $.data(this, 'plugin_' + pluginName, new Plugin( this, options )); 529 } 530 }); 531 }; 532 533 534})( 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.