/* * Curtain.js - Create an unique page transitioning system * --- * Version: 2 * Copyright 2011, Victor Coulon (http://victorcoulon.fr) * Released under the MIT Licence */ (function ( $, window, document, undefined ) { var pluginName = 'curtain', defaults = { scrollSpeed: 400, bodyHeight: 0, linksArray: [], mobile: false, scrollButtons: {}, controls: null, curtainLinks: '.curtain-links', enableKeys: true, easing: 'swing', disabled: false }; // The actual plugin constructor function Plugin( element, options ) { var self = this; // Public attributes this.element = element; this.options = $.extend( {}, defaults, options) ; this._defaults = defaults; this._name = pluginName; this._ignoreHashChange = false; this.init(); // Public Functions this.insert = function(content){ if(Object.prototype.toString.call(content) !== '[object Object]') { throw new TypeError('Content must be an object'); } content.goTo = (content.goTo === true) ? true : false; // append the content to list var newEl = $(document.createElement('li')).attr('id', (content.htmlId) ? content.htmlId : null) .attr('class', (content.htmlClass) ? content.htmlClass : null) .html( (content.html) ? content.html : null ); $(self.element).append(newEl); // Append Content after an element OR at the end if(content.insertAfter && $(content.insertAfter).length) { $(self.element).find(content.insertAfter).after(newEl); } else { $(self.element).append(newEl); } // When the element is ready self.readyElement($(newEl), function(){ // re(init) cache elements self.$element = $(self.element); self.$li = $(self.element).find('>li'); // Mobile Fix if(self.options.mobile){ self.$li.css({position:'relative'}); self.$element.find('.fixed').css({position:'absolute'}); } self.setLinks(); // Set dimensions after loading images (or not) if($(newEl).find('img').length){ $(newEl).find('img').load(function(){ self.setDimensions(); }); } else { self.setDimensions(); } // Scroll to the new element if(content.goTo === true){ var position = $(newEl).attr('data-position') || null; self.scrollEl.animate({ scrollTop:position }, self.options.scrollSpeed, self.options.easing); } }); }; } Plugin.prototype = { init: function () { var self = this; // Cache element this.$element = $(this.element); this.$li = $(this.element).find('>li'); this.$liLength = this.$li.length; self.$windowHeight = $(window).height(); self.$elDatas = {}; self.$document = $(document); self.$window = $(window); //self.webkit = (navigator.userAgent.indexOf('Chrome') > -1 || navigator.userAgent.indexOf("Safari") > -1); $.Android = (navigator.userAgent.match(/Android/i)); $.iPhone = ((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i))); $.iPad = ((navigator.userAgent.match(/iPad/i))); $.iOs4 = (/OS [1-4]_[0-9_]+ like Mac OS X/i.test(navigator.userAgent)); if($.iPhone || $.iPad || $.Android || self.options.disabled){ this.options.mobile = true; this.$li.css({position:'relative'}); this.$element.find('.fixed').css({position:'absolute'}); } if(this.options.mobile){ this.scrollEl = this.$element; } else if($.browser.mozilla || $.browser.msie) { this.scrollEl = $('html'); } else { this.scrollEl = $('body'); } if(self.options.controls){ self.options.scrollButtons['up'] = $(self.options.controls).find('[href="#up"]'); self.options.scrollButtons['down'] = $(self.options.controls).find('[href="#down"]'); if(!$.iOs4 && ($.iPhone || $.iPad)){ self.$element.css({ position:'fixed', top:0, left:0, right:0, bottom:0, '-webkit-overflow-scrolling':'touch', overflow:'auto' }); $(self.options.controls).css({position:'absolute'}); } } // When all image is loaded var callbackImageLoaded = function(){ self.setDimensions(); self.$li.eq(0).addClass('current'); self.setCache(); if(!self.options.mobile){ if(self.$li.eq(1).length) self.$li.eq(1).nextAll().addClass('hidden'); } self.setEvents(); self.setLinks(); self.isHashIsOnList(location.hash.substring(1)); // Remove Loading Overlay $('.load-overlay').fadeOut(function(){ $('.load-overlay').css({marginLeft:'-6000px'}); }); }; if(self.$element.find('img').length) self.imageLoaded(callbackImageLoaded); else callbackImageLoaded(); }, // Events scrollToPosition: function (direction){ var position = null, self = this; if(self.scrollEl.is(':animated')){ return false; } if(direction === 'up' || direction == 'down'){ // Keyboard event var $next = (direction === 'up') ? self.$current.prev() : self.$current.next(); // Step in the current panel ? if(self.$step){ if(!self.$current.find('.current-step').length){ self.$step.eq(0).addClass('current-step'); } var $nextStep = (direction === 'up') ? self.$current.find('.current-step').prev('.step') : self.$current.find('.current-step').next('.step'); if($nextStep.length) { position = (self.options.mobile) ? $nextStep.position().top + self.$elDatas[self.$current.index()]['data-position'] : $nextStep.position().top + self.$elDatas[self.$current.index()]['data-position']; } } position = position || ((self.$elDatas[$next.index()] === undefined) ? null : self.$elDatas[$next.index()]['data-position']); if(position !== null){ self.scrollEl.animate({ scrollTop: position }, self.options.scrollSpeed, self.options.easing); } } else if(direction === 'top'){ self.scrollEl.animate({ scrollTop:0 }, self.options.scrollSpeed, self.options.easing); } else if(direction === 'bottom'){ self.scrollEl.animate({ scrollTop:self.options.bodyHeight }, self.options.scrollSpeed, self.options.easing); } else { var index = $("#"+direction).index(), speed = Math.abs(self.currentIndex-index) * (this.options.scrollSpeed*4) / self.$liLength; self.scrollEl.animate({ scrollTop:self.$elDatas[index]['data-position'] || null }, (speed <= self.options.scrollSpeed) ? self.options.scrollSpeed : speed, this.options.easing); } }, scrollEvent: function() { var self = this, docTop = self.$document.scrollTop(); if(docTop < self.currentP && self.currentIndex > 0){ // Scroll to top self._ignoreHashChange = true; if(self.$current.prev().attr('id')) self.setHash(self.$current.prev().attr('id')); self.$current .removeClass('current') .css( (self.webkit) ? {'-webkit-transform': 'translateY(0px) translateZ(0)'} : {marginTop: 0} ) .nextAll().addClass('hidden').end() .prev().addClass('current').removeClass('hidden'); self.setCache(); } else if(docTop < (self.currentP + self.currentHeight)){ // Animate the current pannel during the scroll if(self.webkit) { self.$current.css({'-webkit-transform': 'translateY('+(-(docTop-self.currentP))+'px) translateZ(0)' }); } else { self.$current.css({marginTop: -(docTop-self.currentP) }); // Look for Parallax elements and animate them if (self.hasParallax) { // Faster scrolling than text //self.$parallax.css({marginTop: -(((docTop - self.currentP) * 1) - (self.$parallax.position().top + self.$parallax.height())) }); // slower scrolling than main text // default scrolling speed var parallaxSpeed = .2; //console.log(self.$parallax[0]); // Check to see if element has a speed attribute and set the speed to that for(var i=0, n= self.$parallax.length; i= self.currentP + self.currentHeight - 200){ self.$fixed.css({ position: 'fixed' }); } else { self.$fixed.css({ position: 'absolute', marginTop: Math.abs(docTop-self.currentP) }); } } // If there is a step element in the current panel if(self.$stepLength){ $.each(self.$step, function(i,el){ if(($(el).position().top+self.currentP) <= docTop+5 && $(el).position().top + self.currentP + $(el).height() >= docTop+5){ if(!$(el).hasClass('current-step')){ self.$step.removeClass('current-step'); $(el).addClass('current-step'); return false; } } }); } } else { // Scroll bottom self._ignoreHashChange = true; if(self.$current.next().attr('id')) self.setHash(self.$current.next().attr('id')); self.$current.removeClass('current') .addClass('hidden') .next('li').addClass('current').next('li').removeClass('hidden'); self.setCache(); } }, scrollMobileEvent: function() { var self = this, docTop = self.$element.scrollTop(); if(docTop+10 < self.currentP && self.currentIndex > 0){ // Scroll to top self._ignoreHashChange = true; if(self.$current.prev().attr('id')) self.setHash(self.$current.prev().attr('id')); self.$current.removeClass('current').prev().addClass('current'); self.setCache(); } else if(docTop+10 < (self.currentP + self.currentHeight)){ // If there is a step element in the current panel if(self.$stepLength){ $.each(self.$step, function(i,el){ if(($(el).position().top+self.currentP) <= docTop && (($(el).position().top+self.currentP) + $(el).outerHeight()) >= docTop){ if(!$(el).hasClass('current-step')){ self.$step.removeClass('current-step'); $(el).addClass('current-step'); } } }); } } else { // Scroll bottom self._ignoreHashChange = true; if(self.$current.next().attr('id')) self.setHash(self.$current.next().attr('id')); self.$current.removeClass('current').next().addClass('current'); self.setCache(); } }, // Setters setDimensions: function(){ var self = this, levelHeight = 0, cover = false, height = null; self.$windowHeight = self.$window.height(); this.$li.each(function(index) { var $self = $(this); cover = $self.hasClass('cover'); if(cover){ $self.css({height: self.$windowHeight, zIndex: 999-index}) .attr('data-height',self.$windowHeight) .attr('data-position',levelHeight); self.$elDatas[$self.index()] = { 'data-height': parseInt(self.$windowHeight,10), 'data-position': parseInt(levelHeight, 10) }; levelHeight += self.$windowHeight; } else{ height = ($self.outerHeight() <= self.$windowHeight) ? self.$windowHeight : $self.outerHeight(); $self.css({minHeight: height, zIndex: 999-index}) .attr('data-height',height) .attr('data-position',levelHeight); self.$elDatas[$self.index()] = { 'data-height': parseInt(height, 10), 'data-position': parseInt(levelHeight, 10) }; levelHeight += height; } if($self.find('.fixed').length){ var top = $self.find('.fixed').css('top'); $self.find('.fixed').attr('data-top', top); } }); if(!this.options.mobile) this.setBodyHeight(); }, setEvents: function() { var self = this; $(window).on('resize', function(){ self.setDimensions(); }); if(self.options.mobile) { self.$element.on('scroll', function(){ self.scrollMobileEvent(); }); } else { self.$window.on('scroll', function(){ self.scrollEvent(); }); } if(self.options.enableKeys) { self.$document.on('keydown', function(e){ if(e.keyCode === 38 || e.keyCode === 37) { self.scrollToPosition('up'); e.preventDefault(); return false; } if(e.keyCode === 40 || e.keyCode === 39){ self.scrollToPosition('down'); e.preventDefault(); return false; } // Home button if(e.keyCode === 36){ self.scrollToPosition('top'); e.preventDefault(); return false; } // End button if(e.keyCode === 35){ self.scrollToPosition('bottom'); e.preventDefault(); return false; } }); } if(self.options.scrollButtons){ if(self.options.scrollButtons.up){ self.options.scrollButtons.up.on('click', function(e){ e.preventDefault(); self.scrollToPosition('up'); }); } if(self.options.scrollButtons.down){ self.options.scrollButtons.down.on('click', function(e){ e.preventDefault(); self.scrollToPosition('down'); }); } } if(self.options.curtainLinks){ $(self.options.curtainLinks).on('click', function(e){ e.preventDefault(); var href = $(this).attr('href'); if(!self.isHashIsOnList(href.substring(1)) && position) return false; var position = self.$elDatas[$(href).index()]['data-position'] || null; if(position){ self.scrollEl.animate({ scrollTop:position }, self.options.scrollSpeed, self.options.easing); } return false; }); } self.$window.on("hashchange", function(event){ if(self._ignoreHashChange === false){ self.isHashIsOnList(location.hash.substring(1)); } self._ignoreHashChange = false; }); }, setBodyHeight: function(){ var h = 0; for (var key in this.$elDatas) { var obj = this.$elDatas[key]; h += obj['data-height']; } this.options.bodyHeight = h; $('body').height(h); }, setLinks: function(){ var self = this; this.$li.each(function() { var id = $(this).attr('id') || 0; self.options.linksArray.push(id); }); }, setHash: function(hash){ // "HARD FIX" el = $('[href=#'+hash+']'); el.parent().siblings('li').removeClass('active'); el.parent().addClass('active'); if(history.pushState) { history.pushState(null, null, '#'+hash); } else { location.hash = hash; } }, setCache: function(){ var self = this; self.$current = self.$element.find('.current'); self.$fixed = self.$current.find('.fixed'); self.$fixedLength = self.$fixed.length; self.$step = self.$current.find('.step'); self.$stepLength = self.$step.length; self.$parallax = self.$current.find('.parallax'); self.hasParallax = self.$parallax.length > 0; self.$BGparallax = self.$current.find('.bg-parallax'); self.hasBGparallax = self.$BGparallax.length > 0; self.currentIndex = self.$current.index(); self.currentP = self.$elDatas[self.currentIndex]['data-position']; self.currentHeight = self.$elDatas[self.currentIndex]['data-height']; }, // Utils isHashIsOnList: function(hash){ var self = this; $.each(self.options.linksArray, function(i,val){ if(val === hash){ self.scrollToPosition(hash); return false; } }); }, readyElement: function(el,callback){ var interval = setInterval(function(){ if(el.length){ callback(el.length); clearInterval(interval); } },60); }, imageLoaded: function(callback){ var self = this, elems = self.$element.find('img'), len = elems.length, blank = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="; elems.bind('load.imgloaded',function(){ if (--len <= 0 && this.src !== blank){ elems.unbind('load.imgloaded'); callback.call(elems,this); } }).each(function(){ if (this.complete || this.complete === undefined){ var src = this.src; this.src = blank; this.src = src; } }); } }; $.fn[pluginName] = function ( options ) { return this.each(function () { if (!$.data(this, 'plugin_' + pluginName)) { $.data(this, 'plugin_' + pluginName, new Plugin( this, options )); } }); }; })( jQuery, window, document );