fullpage.js
Version:
Create beautiful fullscreen snap scrolling websites
1,297 lines (1,097 loc) • 157 kB
JavaScript
/*!
* fullPage 3.1.2
* https://github.com/alvarotrigo/fullPage.js
*
* @license GPLv3 for open source use only
* or Fullpage Commercial License for commercial use
* http://alvarotrigo.com/fullPage/pricing/
*
* Copyright (C) 2018 http://alvarotrigo.com/fullPage - A project by Alvaro Trigo
*/
(function( root, window, document, factory, undefined) {
if( typeof define === 'function' && define.amd ) {
// AMD. Register as an anonymous module.
define( function() {
root.fullpage = factory(window, document);
return root.fullpage;
} );
} else if( typeof exports === 'object' ) {
// Node. Does not work with strict CommonJS.
module.exports = factory(window, document);
} else {
// Browser globals.
window.fullpage = factory(window, document);
}
}(this, window, document, function(window, document){
'use strict';
// keeping central set of classnames and selectors
var WRAPPER = 'fullpage-wrapper';
var WRAPPER_SEL = '.' + WRAPPER;
// slimscroll
var SCROLLABLE = 'fp-scrollable';
var SCROLLABLE_SEL = '.' + SCROLLABLE;
// util
var RESPONSIVE = 'fp-responsive';
var NO_TRANSITION = 'fp-notransition';
var DESTROYED = 'fp-destroyed';
var ENABLED = 'fp-enabled';
var VIEWING_PREFIX = 'fp-viewing';
var ACTIVE = 'active';
var ACTIVE_SEL = '.' + ACTIVE;
var COMPLETELY = 'fp-completely';
var COMPLETELY_SEL = '.' + COMPLETELY;
// section
var SECTION_DEFAULT_SEL = '.section';
var SECTION = 'fp-section';
var SECTION_SEL = '.' + SECTION;
var SECTION_ACTIVE_SEL = SECTION_SEL + ACTIVE_SEL;
var TABLE_CELL = 'fp-tableCell';
var TABLE_CELL_SEL = '.' + TABLE_CELL;
var AUTO_HEIGHT = 'fp-auto-height';
var AUTO_HEIGHT_SEL = '.' + AUTO_HEIGHT;
var AUTO_HEIGHT_RESPONSIVE = 'fp-auto-height-responsive';
var AUTO_HEIGHT_RESPONSIVE_SEL = '.' + AUTO_HEIGHT_RESPONSIVE;
var NORMAL_SCROLL = 'fp-normal-scroll';
var NORMAL_SCROLL_SEL = '.' + NORMAL_SCROLL;
// section nav
var SECTION_NAV = 'fp-nav';
var SECTION_NAV_SEL = '#' + SECTION_NAV;
var SECTION_NAV_TOOLTIP = 'fp-tooltip';
var SECTION_NAV_TOOLTIP_SEL='.'+SECTION_NAV_TOOLTIP;
var SHOW_ACTIVE_TOOLTIP = 'fp-show-active';
// slide
var SLIDE_DEFAULT_SEL = '.slide';
var SLIDE = 'fp-slide';
var SLIDE_SEL = '.' + SLIDE;
var SLIDE_ACTIVE_SEL = SLIDE_SEL + ACTIVE_SEL;
var SLIDES_WRAPPER = 'fp-slides';
var SLIDES_WRAPPER_SEL = '.' + SLIDES_WRAPPER;
var SLIDES_CONTAINER = 'fp-slidesContainer';
var SLIDES_CONTAINER_SEL = '.' + SLIDES_CONTAINER;
var TABLE = 'fp-table';
// slide nav
var SLIDES_NAV = 'fp-slidesNav';
var SLIDES_NAV_SEL = '.' + SLIDES_NAV;
var SLIDES_NAV_LINK_SEL = SLIDES_NAV_SEL + ' a';
var SLIDES_ARROW = 'fp-controlArrow';
var SLIDES_ARROW_SEL = '.' + SLIDES_ARROW;
var SLIDES_PREV = 'fp-prev';
var SLIDES_PREV_SEL = '.' + SLIDES_PREV;
var SLIDES_ARROW_PREV = SLIDES_ARROW + ' ' + SLIDES_PREV;
var SLIDES_ARROW_PREV_SEL = SLIDES_ARROW_SEL + SLIDES_PREV_SEL;
var SLIDES_NEXT = 'fp-next';
var SLIDES_NEXT_SEL = '.' + SLIDES_NEXT;
var SLIDES_ARROW_NEXT = SLIDES_ARROW + ' ' + SLIDES_NEXT;
var SLIDES_ARROW_NEXT_SEL = SLIDES_ARROW_SEL + SLIDES_NEXT_SEL;
function initialise(containerSelector, options) {
var isOK = options && new RegExp('([\\d\\w]{8}-){3}[\\d\\w]{8}|^(?=.*?[A-Y])(?=.*?[a-y])(?=.*?[0-8])(?=.*?[#?!@$%^&*-]).{8,}$').test(options['li'+'cen'+'seK' + 'e' + 'y']) || document.domain.indexOf('al'+'varotri' +'go' + '.' + 'com') > -1;
// cache common elements
var $htmlBody = $('html, body');
var $html = $('html')[0];
var $body = $('body')[0];
//only once my friend!
if(hasClass($html, ENABLED)){ displayWarnings(); return; }
var FP = {};
// Creating some defaults, extending them with any options that were provided
options = deepExtend({
//navigation
menu: false,
anchors:[],
lockAnchors: false,
navigation: false,
navigationPosition: 'right',
navigationTooltips: [],
showActiveTooltip: false,
slidesNavigation: false,
slidesNavPosition: 'bottom',
scrollBar: false,
hybrid: false,
//scrolling
css3: true,
scrollingSpeed: 700,
autoScrolling: true,
fitToSection: true,
fitToSectionDelay: 1000,
easing: 'easeInOutCubic',
easingcss3: 'ease',
loopBottom: false,
loopTop: false,
loopHorizontal: true,
continuousVertical: false,
continuousHorizontal: false,
scrollHorizontally: false,
interlockedSlides: false,
dragAndMove: false,
offsetSections: false,
resetSliders: false,
fadingEffect: false,
normalScrollElements: null,
scrollOverflow: false,
scrollOverflowReset: false,
scrollOverflowHandler: window.fp_scrolloverflow ? window.fp_scrolloverflow.iscrollHandler : null,
scrollOverflowOptions: null,
touchSensitivity: 5,
touchWrapper: typeof containerSelector === 'string' ? $(containerSelector)[0] : containerSelector,
bigSectionsDestination: null,
//Accessibility
keyboardScrolling: true,
animateAnchor: true,
recordHistory: true,
//design
controlArrows: true,
controlArrowColor: '#fff',
verticalCentered: true,
sectionsColor : [],
paddingTop: 0,
paddingBottom: 0,
fixedElements: null,
responsive: 0, //backwards compabitility with responsiveWiddth
responsiveWidth: 0,
responsiveHeight: 0,
responsiveSlides: false,
parallax: false,
parallaxOptions: {
type: 'reveal',
percentage: 62,
property: 'translate'
},
cards: false,
cardsOptions: {
perspective: 100,
fadeContent: true,
fadeBackground: true
},
//Custom selectors
sectionSelector: SECTION_DEFAULT_SEL,
slideSelector: SLIDE_DEFAULT_SEL,
//events
v2compatible: false,
afterLoad: null,
onLeave: null,
afterRender: null,
afterResize: null,
afterReBuild: null,
afterSlideLoad: null,
onSlideLeave: null,
afterResponsive: null,
lazyLoading: true
}, options);
//flag to avoid very fast sliding for landscape sliders
var slideMoving = false;
var isTouchDevice = navigator.userAgent.match(/(iPhone|iPod|iPad|Android|playbook|silk|BlackBerry|BB10|Windows Phone|Tizen|Bada|webOS|IEMobile|Opera Mini)/);
var isTouch = (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0) || (navigator.maxTouchPoints));
var container = typeof containerSelector === 'string' ? $(containerSelector)[0] : containerSelector;
var windowsHeight = getWindowHeight();
var windowsWidth = getWindowWidth();
var isResizing = false;
var isWindowFocused = true;
var lastScrolledDestiny;
var lastScrolledSlide;
var canScroll = true;
var scrollings = [];
var controlPressed;
var startingSection;
var isScrollAllowed = {};
isScrollAllowed.m = { 'up':true, 'down':true, 'left':true, 'right':true };
isScrollAllowed.k = deepExtend({}, isScrollAllowed.m);
var MSPointer = getMSPointer();
var events = {
touchmove: 'ontouchmove' in window ? 'touchmove' : MSPointer.move,
touchstart: 'ontouchstart' in window ? 'touchstart' : MSPointer.down
};
var scrollBarHandler;
// taken from https://github.com/udacity/ud891/blob/gh-pages/lesson2-focus/07-modals-and-keyboard-traps/solution/modal.js
var focusableElementsString = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';
//cheks for passive event support
var g_supportsPassive = false;
try {
var opts = Object.defineProperty({}, 'passive', {
get: function() {
g_supportsPassive = true;
}
});
window.addEventListener("testPassive", null, opts);
window.removeEventListener("testPassive", null, opts);
} catch (e) {}
//timeouts
var resizeId;
var resizeHandlerId;
var afterSectionLoadsId;
var afterSlideLoadsId;
var scrollId;
var scrollId2;
var keydownId;
var g_doubleCheckHeightId;
var originals = deepExtend({}, options); //deep copy
var activeAnimation;
var g_initialAnchorsInDom = false;
var g_canFireMouseEnterNormalScroll = true;
var g_mediaLoadedId;
var g_transitionLapseId;
var extensions = [
'parallax',
'scrollOverflowReset',
'dragAndMove',
'offsetSections',
'fadingEffect',
'responsiveSlides',
'continuousHorizontal',
'interlockedSlides',
'scrollHorizontally',
'resetSliders',
'cards',
'dropEffect',
'waterEffect'
];
displayWarnings();
//easeInOutCubic animation included in the plugin
window.fp_easings = deepExtend(window.fp_easings, {
easeInOutCubic: function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;return c/2*((t-=2)*t*t + 2) + b;
}
});
/**
* Sets the autoScroll option.
* It changes the scroll bar visibility and the history of the site as a result.
*/
function setAutoScrolling(value, type){
//removing the transformation
if(!value){
silentScroll(0);
}
setVariableState('autoScrolling', value, type);
var element = $(SECTION_ACTIVE_SEL)[0];
if(options.autoScrolling && !options.scrollBar){
css($htmlBody, {
'overflow': 'hidden',
'height': '100%'
});
setRecordHistory(originals.recordHistory, 'internal');
//for IE touch devices
css(container, {
'-ms-touch-action': 'none',
'touch-action': 'none'
});
if(element != null){
//moving the container up
silentScroll(element.offsetTop);
}
}else{
css($htmlBody, {
'overflow' : 'visible',
'height' : 'initial'
});
var recordHistory = !options.autoScrolling ? false : originals.recordHistory;
setRecordHistory(recordHistory, 'internal');
//for IE touch devices
css(container, {
'-ms-touch-action': '',
'touch-action': ''
});
//scrolling the page to the section with no animation
if (element != null) {
var scrollSettings = getScrollSettings(element.offsetTop);
scrollSettings.element.scrollTo(0, scrollSettings.options);
}
}
}
/**
* Defines wheter to record the history for each hash change in the URL.
*/
function setRecordHistory(value, type){
setVariableState('recordHistory', value, type);
}
/**
* Defines the scrolling speed
*/
function setScrollingSpeed(value, type){
setVariableState('scrollingSpeed', value, type);
}
/**
* Sets fitToSection
*/
function setFitToSection(value, type){
setVariableState('fitToSection', value, type);
}
/**
* Sets lockAnchors
*/
function setLockAnchors(value){
options.lockAnchors = value;
}
/**
* Adds or remove the possibility of scrolling through sections by using the mouse wheel or the trackpad.
*/
function setMouseWheelScrolling(value){
if(value){
addMouseWheelHandler();
addMiddleWheelHandler();
}else{
removeMouseWheelHandler();
removeMiddleWheelHandler();
}
}
/**
* Adds or remove the possibility of scrolling through sections by using the mouse wheel/trackpad or touch gestures.
* Optionally a second parameter can be used to specify the direction for which the action will be applied.
*
* @param directions string containing the direction or directions separated by comma.
*/
function setAllowScrolling(value, directions){
if(typeof directions !== 'undefined'){
directions = directions.replace(/ /g,'').split(',');
directions.forEach(function (direction){
setIsScrollAllowed(value, direction, 'm');
});
}
else{
setIsScrollAllowed(value, 'all', 'm');
}
}
/**
* Adds or remove the mouse wheel hijacking
*/
function setMouseHijack(value){
if(value){
setMouseWheelScrolling(true);
addTouchHandler();
}else{
setMouseWheelScrolling(false);
removeTouchHandler();
}
}
/**
* Adds or remove the possibility of scrolling through sections by using the keyboard arrow keys
*/
function setKeyboardScrolling(value, directions){
if(typeof directions !== 'undefined'){
directions = directions.replace(/ /g,'').split(',');
directions.forEach(function(direction){
setIsScrollAllowed(value, direction, 'k');
});
}else{
setIsScrollAllowed(value, 'all', 'k');
options.keyboardScrolling = value;
}
}
/**
* Moves the page up one section.
*/
function moveSectionUp(){
var prev = prevUntil($(SECTION_ACTIVE_SEL)[0], SECTION_SEL);
//looping to the bottom if there's no more sections above
if (!prev && (options.loopTop || options.continuousVertical)) {
prev = last($(SECTION_SEL));
}
if (prev != null) {
scrollPage(prev, null, true);
}
}
/**
* Moves the page down one section.
*/
function moveSectionDown(){
var next = nextUntil($(SECTION_ACTIVE_SEL)[0], SECTION_SEL);
//looping to the top if there's no more sections below
if(!next &&
(options.loopBottom || options.continuousVertical)){
next = $(SECTION_SEL)[0];
}
if(next != null){
scrollPage(next, null, false);
}
}
/**
* Moves the page to the given section and slide with no animation.
* Anchors or index positions can be used as params.
*/
function silentMoveTo(sectionAnchor, slideAnchor){
setScrollingSpeed (0, 'internal');
moveTo(sectionAnchor, slideAnchor);
setScrollingSpeed (originals.scrollingSpeed, 'internal');
}
/**
* Moves the page to the given section and slide.
* Anchors or index positions can be used as params.
*/
function moveTo(sectionAnchor, slideAnchor){
var destiny = getSectionByAnchor(sectionAnchor);
if (typeof slideAnchor !== 'undefined'){
scrollPageAndSlide(sectionAnchor, slideAnchor);
}else if(destiny != null){
scrollPage(destiny);
}
}
/**
* Slides right the slider of the active section.
* Optional `section` param.
*/
function moveSlideRight(section){
moveSlide('right', section);
}
/**
* Slides left the slider of the active section.
* Optional `section` param.
*/
function moveSlideLeft(section){
moveSlide('left', section);
}
/**
* When resizing is finished, we adjust the slides sizes and positions
*/
function reBuild(resizing){
if(hasClass(container, DESTROYED)){ return; } //nothing to do if the plugin was destroyed
isResizing = true;
//updating global vars
windowsHeight = getWindowHeight();
windowsWidth = getWindowWidth();
var sections = $(SECTION_SEL);
for (var i = 0; i < sections.length; ++i) {
var section = sections[i];
var slidesWrap = $(SLIDES_WRAPPER_SEL, section)[0];
var slides = $(SLIDE_SEL, section);
//adjusting the height of the table-cell for IE and Firefox
if(options.verticalCentered){
css($(TABLE_CELL_SEL, section), {'height': getTableHeight(section) + 'px'});
}
css(section, {'height': windowsHeight + 'px'});
//adjusting the position fo the FULL WIDTH slides...
if (slides.length > 1) {
landscapeScroll(slidesWrap, $(SLIDE_ACTIVE_SEL, slidesWrap)[0]);
}
}
if(options.scrollOverflow){
scrollBarHandler.createScrollBarForAll();
}
var activeSection = $(SECTION_ACTIVE_SEL)[0];
var sectionIndex = index(activeSection, SECTION_SEL);
//isn't it the first section?
if(sectionIndex){
//adjusting the position for the current section
silentMoveTo(sectionIndex + 1);
}
isResizing = false;
if(isFunction( options.afterResize ) && resizing){
options.afterResize.call(container, window.innerWidth, window.innerHeight);
}
if(isFunction( options.afterReBuild ) && !resizing){
options.afterReBuild.call(container);
}
}
/**
* Determines whether fullpage.js is in responsive mode or not.
*/
function isResponsiveMode(){
return hasClass($body, RESPONSIVE);
}
/**
* Turns fullPage.js to normal scrolling mode when the viewport `width` or `height`
* are smaller than the set limit values.
*/
function setResponsive(active){
var isResponsive = isResponsiveMode();
if(active){
if(!isResponsive){
setAutoScrolling(false, 'internal');
setFitToSection(false, 'internal');
hide($(SECTION_NAV_SEL));
addClass($body, RESPONSIVE);
if(isFunction( options.afterResponsive )){
options.afterResponsive.call( container, active);
}
//when on page load, we will remove scrolloverflow if necessary
if(options.scrollOverflow){
scrollBarHandler.createScrollBarForAll();
}
}
}
else if(isResponsive){
setAutoScrolling(originals.autoScrolling, 'internal');
setFitToSection(originals.autoScrolling, 'internal');
show($(SECTION_NAV_SEL));
removeClass($body, RESPONSIVE);
if(isFunction( options.afterResponsive )){
options.afterResponsive.call( container, active);
}
}
}
if(container){
//public functions
FP.version = '3.1.1';
FP.setAutoScrolling = setAutoScrolling;
FP.setRecordHistory = setRecordHistory;
FP.setScrollingSpeed = setScrollingSpeed;
FP.setFitToSection = setFitToSection;
FP.setLockAnchors = setLockAnchors;
FP.setMouseWheelScrolling = setMouseWheelScrolling;
FP.setAllowScrolling = setAllowScrolling;
FP.setKeyboardScrolling = setKeyboardScrolling;
FP.moveSectionUp = moveSectionUp;
FP.moveSectionDown = moveSectionDown;
FP.silentMoveTo = silentMoveTo;
FP.moveTo = moveTo;
FP.moveSlideRight = moveSlideRight;
FP.moveSlideLeft = moveSlideLeft;
FP.fitToSection = fitToSection;
FP.reBuild = reBuild;
FP.setResponsive = setResponsive;
FP.getFullpageData = function(){ return options; };
FP.destroy = destroy;
FP.getActiveSection = getActiveSection;
FP.getActiveSlide = getActiveSlide;
FP.test = {
top: '0px',
translate3d: 'translate3d(0px, 0px, 0px)',
translate3dH: (function(){
var a = [];
for(var i = 0; i < $(options.sectionSelector, container).length; i++){
a.push('translate3d(0px, 0px, 0px)');
}
return a;
})(),
left: (function(){
var a = [];
for(var i = 0; i < $(options.sectionSelector, container).length; i++){
a.push(0);
}
return a;
})(),
options: options,
setAutoScrolling: setAutoScrolling
};
//functions we want to share across files but which are not
//mean to be used on their own by developers
FP.shared = {
afterRenderActions: afterRenderActions,
isNormalScrollElement: false
};
window.fullpage_api = FP;
//using jQuery initialization? Creating the $.fn.fullpage object
if(options.$){
Object.keys(FP).forEach(function (key) {
options.$.fn.fullpage[key] = FP[key];
});
}
init();
bindEvents();
}
function init(){
//if css3 is not supported, it will use jQuery animations
if(options.css3){
options.css3 = support3d();
}
options.scrollBar = options.scrollBar || options.hybrid;
setOptionsFromDOM();
prepareDom();
setAllowScrolling(true);
setMouseHijack(true);
setAutoScrolling(options.autoScrolling, 'internal');
responsive();
//setting the class for the body element
setBodyClass();
if(document.readyState === 'complete'){
scrollToAnchor();
}
window.addEventListener('load', scrollToAnchor);
//if we use scrollOverflow we'll fire afterRender in the scrolloverflow file
if(!options.scrollOverflow){
afterRenderActions();
}
doubleCheckHeight();
}
function bindEvents(){
//when scrolling...
window.addEventListener('scroll', scrollHandler);
//detecting any change on the URL to scroll to the given anchor link
//(a way to detect back history button as we play with the hashes on the URL)
window.addEventListener('hashchange', hashChangeHandler);
// on window focus
window.addEventListener('focus', focusHandler);
//when opening a new tab (ctrl + t), `control` won't be pressed when coming back.
window.addEventListener('blur', blurHandler);
//when resizing the site, we adjust the heights of the sections, slimScroll...
window.addEventListener('resize', resizeHandler);
//Sliding with arrow keys, both, vertical and horizontal
document.addEventListener('keydown', keydownHandler);
//to prevent scrolling while zooming
document.addEventListener('keyup', keyUpHandler);
//Scrolls to the section when clicking the navigation bullet
//simulating the jQuery .on('click') event using delegation
['click', 'touchstart'].forEach(function(eventName){
document.addEventListener(eventName, delegatedEvents);
});
/**
* Applying normalScroll elements.
* Ignoring the scrolls over the specified selectors.
*/
if(options.normalScrollElements){
['mouseenter', 'touchstart'].forEach(function(eventName){
forMouseLeaveOrTouch(eventName, false);
});
['mouseleave', 'touchend'].forEach(function(eventName){
forMouseLeaveOrTouch(eventName, true);
});
}
}
function delegatedEvents(e){
var target = e.target;
if(target && closest(target, SECTION_NAV_SEL + ' a')){
sectionBulletHandler.call(target, e);
}
else if(matches(target, SECTION_NAV_TOOLTIP_SEL)){
tooltipTextHandler.call(target);
}
else if(matches(target, SLIDES_ARROW_SEL)){
slideArrowHandler.call(target, e);
}
else if(matches(target, SLIDES_NAV_LINK_SEL) || closest(target, SLIDES_NAV_LINK_SEL) != null){
slideBulletHandler.call(target, e);
}
else if(closest(target, options.menu + ' [data-menuanchor]')){
menuItemsHandler.call(target, e);
}
}
function forMouseLeaveOrTouch(eventName, allowScrolling){
//a way to pass arguments to the onMouseEnterOrLeave function
document['fp_' + eventName] = allowScrolling;
document.addEventListener(eventName, onMouseEnterOrLeave, true); //capturing phase
}
function onMouseEnterOrLeave(e) {
var type = e.type;
var isInsideOneNormalScroll = false;
var isUsingScrollOverflow = options.scrollOverflow;
//onMouseLeave will use the destination target, not the one we are moving away from
var target = type === 'mouseleave' ? e.toElement || e.relatedTarget : e.target;
//coming from closing a normalScrollElements modal or moving outside viewport?
if(target == document || !target){
setMouseHijack(true);
if(isUsingScrollOverflow){
options.scrollOverflowHandler.setIscroll(target, true);
}
return;
}
if(type === 'touchend'){
g_canFireMouseEnterNormalScroll = false;
setTimeout(function(){
g_canFireMouseEnterNormalScroll = true;
}, 800);
}
//preventing mouseenter event to do anything when coming from a touchEnd event
//fixing issue #3576
if(type === 'mouseenter' && !g_canFireMouseEnterNormalScroll){
return;
}
var normalSelectors = options.normalScrollElements.split(',');
normalSelectors.forEach(function(normalSelector){
if(!isInsideOneNormalScroll){
var isNormalScrollTarget = matches(target, normalSelector);
//leaving a child inside the normalScoll element is not leaving the normalScroll #3661
var isNormalScrollChildFocused = closest(target, normalSelector);
if(isNormalScrollTarget || isNormalScrollChildFocused){
if(!FP.shared.isNormalScrollElement){
setMouseHijack(false);
if(isUsingScrollOverflow){
options.scrollOverflowHandler.setIscroll(target, false);
}
}
FP.shared.isNormalScrollElement = true;
isInsideOneNormalScroll = true;
}
}
});
//not inside a single normal scroll element anymore?
if(!isInsideOneNormalScroll && FP.shared.isNormalScrollElement){
setMouseHijack(true);
if(isUsingScrollOverflow){
options.scrollOverflowHandler.setIscroll(target, true);
}
FP.shared.isNormalScrollElement = false;
}
}
/**
* Checks the viewport a few times on a define interval of time to
* see if it has changed in any of those. If that's the case, it resizes.
*/
function doubleCheckHeight(){
for(var i = 1; i < 4; i++){
g_doubleCheckHeightId = setTimeout(adjustToNewViewport, 350 * i);
}
}
/**
* Adjusts a section to the viewport if it has changed.
*/
function adjustToNewViewport(){
var newWindowHeight = getWindowHeight();
var newWindowWidth = getWindowWidth();
if(windowsHeight !== newWindowHeight || windowsWidth !== newWindowWidth){
windowsHeight = newWindowHeight;
windowsWidth = newWindowWidth;
reBuild(true);
}
}
/**
* Setting options from DOM elements if they are not provided.
*/
function setOptionsFromDOM(){
//no anchors option? Checking for them in the DOM attributes
if(!options.anchors.length){
var anchorsAttribute = '[data-anchor]';
var anchors = $(options.sectionSelector.split(',').join(anchorsAttribute + ',') + anchorsAttribute, container);
if(anchors.length && anchors.length === $(options.sectionSelector, container).length){
g_initialAnchorsInDom = true;
anchors.forEach(function(item){
options.anchors.push(item.getAttribute('data-anchor').toString());
});
}
}
//no tooltips option? Checking for them in the DOM attributes
if(!options.navigationTooltips.length){
var tooltipsAttribute = '[data-tooltip]';
var tooltips = $(options.sectionSelector.split(',').join(tooltipsAttribute + ',') + tooltipsAttribute, container);
if(tooltips.length){
tooltips.forEach(function(item){
options.navigationTooltips.push(item.getAttribute('data-tooltip').toString());
});
}
}
}
/**
* Works over the DOM structure to set it up for the current fullpage options.
*/
function prepareDom(){
css(container, {
'height': '100%',
'position': 'relative'
});
//adding a class to recognize the container internally in the code
addClass(container, WRAPPER);
addClass($html, ENABLED);
//due to https://github.com/alvarotrigo/fullPage.js/issues/1502
windowsHeight = getWindowHeight();
removeClass(container, DESTROYED); //in case it was destroyed before initializing it again
addInternalSelectors();
var sections = $(SECTION_SEL);
//styling the sections / slides / menu
for(var i = 0; i<sections.length; i++){
var sectionIndex = i;
var section = sections[i];
var slides = $(SLIDE_SEL, section);
var numSlides = slides.length;
//caching the original styles to add them back on destroy('all')
section.setAttribute('data-fp-styles', section.getAttribute('style'));
styleSection(section, sectionIndex);
styleMenu(section, sectionIndex);
// if there's any slide
if (numSlides > 0) {
styleSlides(section, slides, numSlides);
}else{
if(options.verticalCentered){
addTableClass(section);
}
}
}
//fixed elements need to be moved out of the plugin container due to problems with CSS3.
if(options.fixedElements && options.css3){
$(options.fixedElements).forEach(function(item){
$body.appendChild(item);
});
}
//vertical centered of the navigation + active bullet
if(options.navigation){
addVerticalNavigation();
}
enableYoutubeAPI();
if(options.scrollOverflow){
scrollBarHandler = options.scrollOverflowHandler.init(options);
}
}
/**
* Styles the horizontal slides for a section.
*/
function styleSlides(section, slides, numSlides){
var sliderWidth = numSlides * 100;
var slideWidth = 100 / numSlides;
var slidesWrapper = document.createElement('div');
slidesWrapper.className = SLIDES_WRAPPER; //fp-slides
wrapAll(slides, slidesWrapper);
var slidesContainer = document.createElement('div');
slidesContainer.className = SLIDES_CONTAINER; //fp-slidesContainer
wrapAll(slides, slidesContainer);
css($(SLIDES_CONTAINER_SEL, section), {'width': sliderWidth + '%'});
if(numSlides > 1){
if(options.controlArrows){
createSlideArrows(section);
}
if(options.slidesNavigation){
addSlidesNavigation(section, numSlides);
}
}
slides.forEach(function(slide) {
css(slide, {'width': slideWidth + '%'});
if(options.verticalCentered){
addTableClass(slide);
}
});
var startingSlide = $(SLIDE_ACTIVE_SEL, section)[0];
//if the slide won't be an starting point, the default will be the first one
//the active section isn't the first one? Is not the first slide of the first section? Then we load that section/slide by default.
if( startingSlide != null && (index($(SECTION_ACTIVE_SEL), SECTION_SEL) !== 0 || (index($(SECTION_ACTIVE_SEL), SECTION_SEL) === 0 && index(startingSlide) !== 0))){
silentLandscapeScroll(startingSlide, 'internal');
}else{
addClass(slides[0], ACTIVE);
}
}
/**
* Styling vertical sections
*/
function styleSection(section, index){
//if no active section is defined, the 1st one will be the default one
if(!index && $(SECTION_ACTIVE_SEL)[0] == null) {
addClass(section, ACTIVE);
}
startingSection = $(SECTION_ACTIVE_SEL)[0];
css(section, {'height': windowsHeight + 'px'});
if(options.paddingTop){
css(section, {'padding-top': options.paddingTop});
}
if(options.paddingBottom){
css(section, {'padding-bottom': options.paddingBottom});
}
if (typeof options.sectionsColor[index] !== 'undefined') {
css(section, {'background-color': options.sectionsColor[index]});
}
if (typeof options.anchors[index] !== 'undefined') {
section.setAttribute('data-anchor', options.anchors[index]);
}
}
/**
* Sets the data-anchor attributes to the menu elements and activates the current one.
*/
function styleMenu(section, index){
if (typeof options.anchors[index] !== 'undefined') {
//activating the menu / nav element on load
if(hasClass(section, ACTIVE)){
activateMenuAndNav(options.anchors[index], index);
}
}
//moving the menu outside the main container if it is inside (avoid problems with fixed positions when using CSS3 tranforms)
if(options.menu && options.css3 && closest($(options.menu)[0], WRAPPER_SEL) != null){
$(options.menu).forEach(function(menu) {
$body.appendChild(menu);
});
}
}
/**
* Adds internal classes to be able to provide customizable selectors
* keeping the link with the style sheet.
*/
function addInternalSelectors(){
addClass($(options.sectionSelector, container), SECTION);
addClass($(options.slideSelector, container), SLIDE);
}
/**
* Creates the control arrows for the given section
*/
function createSlideArrows(section){
var arrows = [createElementFromHTML('<div class="' + SLIDES_ARROW_PREV + '"></div>'), createElementFromHTML('<div class="' + SLIDES_ARROW_NEXT + '"></div>')];
after($(SLIDES_WRAPPER_SEL, section)[0], arrows);
if(options.controlArrowColor !== '#fff'){
css($(SLIDES_ARROW_NEXT_SEL, section), {'border-color': 'transparent transparent transparent '+options.controlArrowColor});
css($(SLIDES_ARROW_PREV_SEL, section), {'border-color': 'transparent '+ options.controlArrowColor + ' transparent transparent'});
}
if(!options.loopHorizontal){
hide($(SLIDES_ARROW_PREV_SEL, section));
}
}
/**
* Creates a vertical navigation bar.
*/
function addVerticalNavigation(){
var navigation = document.createElement('div');
navigation.setAttribute('id', SECTION_NAV);
var divUl = document.createElement('ul');
navigation.appendChild(divUl);
appendTo(navigation, $body);
var nav = $(SECTION_NAV_SEL)[0];
addClass(nav, 'fp-' + options.navigationPosition);
if(options.showActiveTooltip){
addClass(nav, SHOW_ACTIVE_TOOLTIP);
}
var li = '';
for (var i = 0; i < $(SECTION_SEL).length; i++) {
var link = '';
if (options.anchors.length) {
link = options.anchors[i];
}
li += '<li><a href="#' + link + '"><span class="fp-sr-only">' + getBulletLinkName(i, 'Section') + '</span><span></span></a>';
// Only add tooltip if needed (defined by user)
var tooltip = options.navigationTooltips[i];
if (typeof tooltip !== 'undefined' && tooltip !== '') {
li += '<div class="' + SECTION_NAV_TOOLTIP + ' fp-' + options.navigationPosition + '">' + tooltip + '</div>';
}
li += '</li>';
}
$('ul', nav)[0].innerHTML = li;
//activating the current active section
var bullet = $('li', $(SECTION_NAV_SEL)[0])[index($(SECTION_ACTIVE_SEL)[0], SECTION_SEL)];
addClass($('a', bullet), ACTIVE);
}
/**
* Gets the name for screen readers for a section/slide navigation bullet.
*/
function getBulletLinkName(i, defaultName, item){
var anchor = defaultName === 'Section' ? options.anchors[i] : item.getAttribute('data-anchor');
return options.navigationTooltips[i]
|| anchor
|| defaultName + ' ' + (i+1);
}
/*
* Enables the Youtube videos API so we can control their flow if necessary.
*/
function enableYoutubeAPI(){
$('iframe[src*="youtube.com/embed/"]', container).forEach(function(item){
addURLParam(item, 'enablejsapi=1');
});
}
/**
* Adds a new parameter and its value to the `src` of a given element
*/
function addURLParam(element, newParam){
var originalSrc = element.getAttribute('src');
element.setAttribute('src', originalSrc + getUrlParamSign(originalSrc) + newParam);
}
/*
* Returns the prefix sign to use for a new parameter in an existen URL.
*
* @return {String} ? | &
*/
function getUrlParamSign(url){
return ( !/\?/.test( url ) ) ? '?' : '&';
}
/**
* Actions and callbacks to fire afterRender
*/
function afterRenderActions(){
var section = $(SECTION_ACTIVE_SEL)[0];
addClass(section, COMPLETELY);
lazyLoad(section);
lazyLoadOthers();
playMedia(section);
if(options.scrollOverflow){
options.scrollOverflowHandler.afterLoad();
}
if(isDestinyTheStartingSection() && isFunction(options.afterLoad) ){
fireCallback('afterLoad', {
activeSection: section,
element: section,
direction: null,
//for backwards compatibility callback (to be removed in a future!)
anchorLink: section.getAttribute('data-anchor'),
sectionIndex: index(section, SECTION_SEL)
});
}
if(isFunction(options.afterRender)){
fireCallback('afterRender');
}
}
/**
* Determines if the URL anchor destiny is the starting section (the one using 'active' class before initialization)
*/
function isDestinyTheStartingSection(){
var anchor = getAnchorsURL();
var destinationSection = getSectionByAnchor(anchor.section);
return !anchor.section || !destinationSection || typeof destinationSection !=='undefined' && index(destinationSection) === index(startingSection);
}
var isScrolling = false;
var lastScroll = 0;
//when scrolling...
function scrollHandler(){
var currentSection;
if(isResizing){
return;
}
if(!options.autoScrolling || options.scrollBar){
var currentScroll = getScrollTop();
var scrollDirection = getScrollDirection(currentScroll);
var visibleSectionIndex = 0;
var screen_mid = currentScroll + (getWindowHeight() / 2.0);
var isAtBottom = $body.offsetHeight - getWindowHeight() === currentScroll;
var sections = $(SECTION_SEL);
//when using `auto-height` for a small last section it won't be centered in the viewport
if(isAtBottom){
visibleSectionIndex = sections.length - 1;
}
//is at top? when using `auto-height` for a small first section it won't be centered in the viewport
else if(!currentScroll){
visibleSectionIndex = 0;
}
//taking the section which is showing more content in the viewport
else{
for (var i = 0; i < sections.length; ++i) {
var section = sections[i];
// Pick the the last section which passes the middle line of the screen.
if (section.offsetTop <= screen_mid)
{
visibleSectionIndex = i;
}
}
}
if(isCompletelyInViewPort(scrollDirection)){
if(!hasClass($(SECTION_ACTIVE_SEL)[0], COMPLETELY)){
addClass($(SECTION_ACTIVE_SEL)[0], COMPLETELY);
removeClass(siblings($(SECTION_ACTIVE_SEL)[0]), COMPLETELY);
}
}
//geting the last one, the current one on the screen
currentSection = sections[visibleSectionIndex];
//setting the visible section as active when manually scrolling
//executing only once the first time we reach the section
if(!hasClass(currentSection, ACTIVE)){
isScrolling = true;
var leavingSection = $(SECTION_ACTIVE_SEL)[0];
var leavingSectionIndex = index(leavingSection, SECTION_SEL) + 1;
var yMovement = getYmovement(currentSection);
var anchorLink = currentSection.getAttribute('data-anchor');
var sectionIndex = index(currentSection, SECTION_SEL) + 1;
var activeSlide = $(SLIDE_ACTIVE_SEL, currentSection)[0];
var slideIndex;
var slideAnchorLink;
var callbacksParams = {
activeSection: leavingSection,
sectionIndex: sectionIndex -1,
anchorLink: anchorLink,
element: currentSection,
leavingSection: leavingSectionIndex,
direction: yMovement
};
if(activeSlide){
slideAnchorLink = activeSlide.getAttribute('data-anchor');
slideIndex = index(activeSlide);
}
if(canScroll){
addClass(currentSection, ACTIVE);
removeClass(siblings(currentSection), ACTIVE);
if(isFunction( options.onLeave )){
fireCallback('onLeave', callbacksParams);
}
if(isFunction( options.afterLoad )){
fireCallback('afterLoad', callbacksParams);
}
stopMedia(leavingSection);
lazyLoad(currentSection);
playMedia(currentSection);
activateMenuAndNav(anchorLink, sectionIndex - 1);
if(options.anchors.length){
//needed to enter in hashChange event when using the menu with anchor links
lastScrolledDestiny = anchorLink;
}
setState(slideIndex, slideAnchorLink, anchorLink, sectionIndex);
}
//small timeout in order to avoid entering in hashChange event when scrolling is not finished yet
clearTimeout(scrollId);
scrollId = setTimeout(function(){
isScrolling = false;
}, 100);
}
if(options.fitToSection){
//for the auto adjust of the viewport to fit a whole section