/* * Curtain.js - Create an unique page transitioning system * --- * Version: 1.4.1 * 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', nextSlide: null, prevSlide: null }; // 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'); $.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){ 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'}); } } // We'll check if our images are loaded var images = [], imagesLoaded = 0, loadAllImages = function loadAllImages(callback){ if(images.length === 0){ callback(); return false; } var img = new Image(); $(img).attr('src',images[imagesLoaded]).load(function(){ imagesLoaded++; if(imagesLoaded == images.length) callback(); else loadAllImages(callback); }); }; self.$element.find('img').each(function(i,el){ images.push(el.src); }); // When all image is loaded loadAllImages(function(){ self.setDimensions(); self.$li.eq(0).addClass('current'); // Cache self.$current = self.$element.find('.current'); self.$fixed = self.$current.find('.fixed'); self.$step = self.$current.find('.step'); self.currentP = parseInt(self.$current.attr('data-position'), 10); self.currentHeight = parseInt(self.$current.attr('data-height'), 10); if(!self.options.mobile){ if(self.$li.eq(1).length) self.$li.eq(1).nextAll().css({display:'none'}); } self.setEvents(); self.setLinks(); self.isHashIsOnList(location.hash.substring(1)); }); }, // Events scrollToPosition: function (direction){ var position = null, self = this; if($('html, body').is(':animated')){ return false; } if(direction === 'up' || direction == 'down'){ // Keyboard event var $current = this.$element.find('.current'), $next = (direction === 'up') ? $current.prev() : $current.next(); position = $next.attr('data-position') || null; // Step in the current panel ? if($current.find('.step').length){ if(!$current.find('.current-step').length) $current.find('.step').eq(0).addClass('current-step'); var $nextStep = (direction === 'up') ? $current.find('.current-step').prev('.step') : $current.find('.current-step').next('.step'); if($nextStep.length) { position = (this.options.mobile) ? $nextStep.position().top + parseInt($current.attr('data-position'), 10) : $nextStep.offset().top; } } if(position){ self.scrollEl.animate({ scrollTop:position }, this.options.scrollSpeed, this.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 { position = $("#"+direction).attr('data-position') || null; if(position){ self.scrollEl.animate({ scrollTop:position }, this.options.scrollSpeed, this.options.easing); } } }, scrollEvent: function() { var self = this, docTop = $(document).scrollTop(), windowHeight = $(window).height(); if(docTop < self.currentP && self.$current.index() > 0){ if (self.options.prevSlide) { self.options.prevSlide(); } // Scroll top self._ignoreHashChange = true; if(self.$current.prev().attr('id')) self.setHash(self.$current.prev().attr('id')); self.$current.removeClass('current').css({marginTop: 0}) .nextAll().css({display:'none'}).end() .prev().addClass('current').css({display:'block'}); // Cache self.$current = self.$element.find('.current'); self.$fixed = self.$current.find('.fixed'); self.$step = self.$current.find('.step'); self.currentP = parseInt(self.$current.attr('data-position'), 10); self.currentHeight = parseInt(self.$current.attr('data-height'), 10); } else if(docTop < (self.currentP + self.$current.height())){ // Animate the current pannel during the scroll var position = -(docTop-self.currentP); self.$current.css({marginTop:position}); // If there is a fixed element in the current panel if(self.$fixed.length){ var dataTop = parseInt(self.$fixed.attr('data-top'), 10); if((docTop-self.currentP+windowHeight) >= self.currentHeight && self.$fixed.css('position') === 'fixed'){ self.$fixed.css({ position: 'absolute', top: Math.abs(docTop-self.currentP + dataTop) }); } else if((docTop-self.currentP+windowHeight) <= self.currentHeight && self.$fixed.css('position') === 'absolute'){ self.$fixed.css({ position: 'fixed', top: dataTop }); } } // If there is a step element in the current panel if(self.$step.length){ $.each(self.$step, function(i,el){ if($(el).offset().top <= docTop+5 && ($(el).offset().top + $(el).outerHeight()) >= docTop+5){ if(!$(el).hasClass('current-step')){ self.$step.removeClass('current-step'); $(el).addClass('current-step'); } } }); } } else { if (self.options.nextSlide) { self.options.nextSlide(); } // Scroll bottom self._ignoreHashChange = true; if(self.$current.next().attr('id')) self.setHash(self.$current.next().attr('id')); self.$current.removeClass('current') .css({display:'none'}) .next().addClass('current').nextAll().css({display:'block'}); // Cache self.$current = self.$element.find('.current'); self.$fixed = self.$current.find('.fixed'); self.$step = self.$current.find('.step'); self.currentP = parseInt(self.$current.attr('data-position'), 10); self.currentHeight = parseInt(self.$current.attr('data-height'), 10); } }, scrollMobileEvent: function() { var self = this; var docTop = self.$element.scrollTop(), $current = self.$element.find('.current'), $step = $current.find('.step'), currentP = parseInt($current.attr('data-position'), 10), currentHeight = parseInt($current.attr('data-height'), 10), windowHeight = $(window).height(); if(docTop+10 < currentP && $current.index() > 0){ $current.removeClass('current').prev().addClass('current'); } else if(docTop+10 < (currentP + $current.height())){ // If there is a step element in the current panel if($step.length){ $.each($step, function(i,el){ if(($(el).position().top+currentP) <= docTop && (($(el).position().top+currentP) + $(el).outerHeight()) >= docTop){ if(!$(el).hasClass('current-step')){ $step.removeClass('current-step'); $(el).addClass('current-step'); } } }); } } else { $current.removeClass('current').next().addClass('current'); } }, // Setters setDimensions: function(){ var windowHeight = $(window).height(), levelHeight = 0, cover = false, height = null; this.$li.each(function(index) { var $self = $(this); cover = $self.hasClass('cover'); if(cover){ $self.css({height: windowHeight, zIndex: 999-index}) .attr('data-height',windowHeight) .attr('data-position',levelHeight); levelHeight += windowHeight; } else{ height = ($self.outerHeight() <= windowHeight) ? windowHeight : $self.outerHeight(); $self.css({minHeight: height, zIndex: 999-index}) .attr('data-height',height) .attr('data-position',levelHeight); 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 { $(window).on('scroll', function(){ self.scrollEvent(); }); } if(self.options.enableKeys) { $(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 = $(href).attr('data-position') || null; if(position){ self.scrollEl.animate({ scrollTop:position }, self.options.scrollSpeed, self.options.easing); } return false; }); } if ("onhashchange" in window) { window.addEventListener("hashchange", function(){ if(self._ignoreHashChange === false){ self.isHashIsOnList(location.hash.substring(1)); } self._ignoreHashChange = false; }, false); } }, setBodyHeight: function(){ var h = 0; this.$li.each(function() { h += $(this).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){ if(history.pushState) { history.pushState(null, null, '#'+hash); } else { location.hash = hash; } }, // 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); } }; $.fn[pluginName] = function ( options ) { return this.each(function () { if (!$.data(this, 'plugin_' + pluginName)) { $.data(this, 'plugin_' + pluginName, new Plugin( this, options )); } }); }; })( jQuery, window, document );