/* * 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, nextSlide: function() {}, prevSlide: function() {} }; // 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(); } 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); $.msie = /msie/.test(navigator.userAgent.toLowerCase()); $.mozilla = /firefox/.test(navigator.userAgent.toLowerCase()); $.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($.mozilla || $.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)); }; 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(); self.options.prevSlide(); } 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) }); // If there is a fixed element in the current panel if(self.$fixedLength){ var dataTop = parseInt(self.$fixed.attr('data-top'), 10); if(docTop + self.$windowHeight >= self.currentP + self.currentHeight){ 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; } } }); } if(self.parallaxBg){ var tr = docTop * -0.07; self.parallaxBg.css({ '-webkit-transform' : 'translate3d(0,' + tr + 'px,0)', '-moz-transform' : 'translate3d(0,' + tr + 'px,0)', 'transform' : 'translate3d(0,' + tr + 'px,0)' }); } if(self.$fade.length){ self.$fade.css({ 'z-index': self.z - 1, 'opacity': Math.abs(1- ( (docTop-self.currentP) / self.$windowHeight)) }); } if(self.$slowScroll.length){ self.$slowScroll.css({ 'margin-top' : (docTop / self.$slowScroll.attr('data-slow-scroll')) }); } } 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(); self.options.nextSlide(); } }, 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(); self.options.prevSlide(); } 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(); self.options.nextSlide(); } }, // 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: 20-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: 20-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; var imageHolderClassName = self.$element.data('image-holder-name'); var fadeElementClassName = self.$element.data('fade-element-name'); 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.currentIndex = self.$current.index(); self.currentP = self.$elDatas[self.currentIndex]['data-position']; self.currentHeight = self.$elDatas[self.currentIndex]['data-height']; self.z = self.$element.find('.current').css('z-index'); self.parallaxBg = self.$current.find(imageHolderClassName); self.$fade = $(fadeElementClassName); self.$slowScroll = self.$current.find('[data-slow-scroll]'); }, // 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 || $(this).not(':visible')){ 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 );