From df3401ed9d7164df3f93de154d1d70284c06e97e Mon Sep 17 00:00:00 2001 From: Oleg Roschupkin Date: Wed, 10 Aug 2011 01:47:32 +0900 Subject: [PATCH] Add inner navigation feature. Closes #4. --- en.htm | 6 +- scripts/script.js | 208 +++++++++++++++++++++++++++++----------------- 2 files changed, 137 insertions(+), 77 deletions(-) diff --git a/en.htm b/en.htm index 3b92a3a..873a4fb 100755 --- a/en.htm +++ b/en.htm @@ -172,7 +172,7 @@ After activation of the last child pressing next button will turn next slide. - + -->
@@ -185,7 +185,7 @@
  • Erase your hard drive.
  • -
    --> +
    @@ -208,4 +208,4 @@ - \ No newline at end of file + diff --git a/scripts/script.js b/scripts/script.js index 56b418b..183d67e 100755 --- a/scripts/script.js +++ b/scripts/script.js @@ -4,11 +4,13 @@ slides = document.querySelectorAll('div.slide'), progress = document.querySelector('div.progress div'), slideList = [], - l = slides.length, - i; + l = slides.length, i; for (i = 0; i < l; i++) { - slideList.push(slides[i].id); + slideList.push({ + id: slides[i].id, + hasInnerNavigation: null !== slides[i].querySelector('.inner') + }); } function getTransform() { @@ -28,62 +30,73 @@ body.style.transform = transform; } - function enterSingleSlideMode() { + function enterSlideMode() { body.className = 'full'; applyTransform(getTransform()); } - function enterSlideListMode() { + function enterListMode() { body.className = 'list'; applyTransform('none'); } function getCurrentSlideNumber() { - return slideList.indexOf(url.hash.substr(1)); + var i, l = slideList.length, + currentSlideId = url.hash.substr(1); + + for (i = 0; i < l; ++i) { + if (currentSlideId === slideList[i].id) { + return i; + } + } + + return -1; } function scrollToCurrentSlide() { - var current_slide = document.getElementById(slideList[getCurrentSlideNumber()]); + var currentSlide = document.getElementById( + slideList[getCurrentSlideNumber()].id + ); - if (null != current_slide) { - window.scrollTo(0, current_slide.offsetTop); + if (null != currentSlide) { + window.scrollTo(0, currentSlide.offsetTop); } } - function isSlideListMode() { + function isListMode() { return 'full' !== url.search.substr(1); } - function normalizeSlideNumber(slide_number) { - if (0 > slide_number) { + function normalizeSlideNumber(slideNumber) { + if (0 > slideNumber) { return slideList.length - 1; - } else if (slideList.length <= slide_number) { + } else if (slideList.length <= slideNumber) { return 0; } else { - return slide_number; + return slideNumber; } } - function updateProgress(slide_number) { - if (!progress) return; - progress.style.width = (100 / (slideList.length - 1) * normalizeSlideNumber(slide_number)).toFixed(2) + '%'; + function updateProgress(slideNumber) { + if (null === progress) { return; } + progress.style.width = (100 / (slideList.length - 1) * normalizeSlideNumber(slideNumber)).toFixed(2) + '%'; } - function getSlideHashByNumber(slide_number) { - return '#' + slideList[normalizeSlideNumber(slide_number)]; + function getSlideHash(slideNumber) { + return '#' + slideList[normalizeSlideNumber(slideNumber)].id; } - function goToSlide(slide_number) { - url.hash = getSlideHashByNumber(slide_number); + function goToSlide(slideNumber) { + url.hash = getSlideHash(slideNumber); - if (!isSlideListMode()) { - updateProgress(slide_number); + if (!isListMode()) { + updateProgress(slideNumber); } } function getContainingSlideId(el) { var node = el; - while ('BODY' !== node.nodeName) { + while ('BODY' !== node.nodeName && 'HTML' !== node.nodeName) { if (-1 !== node.className.indexOf('slide')) { return node.id; } else { @@ -93,82 +106,115 @@ return ''; } - + function dispatchSingleSlideMode(e) { var slideId = getContainingSlideId(e.target); - if ('' !== slideId && isSlideListMode()) { + if ('' !== slideId && isListMode()) { e.preventDefault(); - + // NOTE: we should update hash to get things work properly url.hash = '#' + slideId; history.replaceState(null, null, url.pathname + '?full#' + slideId); - enterSingleSlideMode(); - + enterSlideMode(); + updateProgress(getCurrentSlideNumber()); } } + // Increases inner navigation by adding 'active' class to next inactive + // inner navigation item. + function increaseInnerNavigation(slideNumber) { + // Shortcut for slides without inner navigation + if (true !== slideList[slideNumber].hasInnerNavigation) { return -1; } + + var activeNodes = document.querySelectorAll(getSlideHash(slideNumber) + ' .active'), + // NOTE: we assume there is no other elements in inner navigation + node = activeNodes[activeNodes.length - 1].nextElementSibling; + + if (null !== node) { + node.classList.add('active'); + // NOTE: 'auto-added' class is used in resetInnerNavigation - to + // overcome problems with manually added 'active' class. + node.classList.add('auto-added'); + + return activeNodes.length + 1; + } else { + return -1; + } + } + + // Resets inner navigation by removing 'active' class from all previously + // activated elements. + function resetInnerNavigation(slideNumber) { + // Shortcut for slides without inner navigation + if (true !== slideList[slideNumber].hasInnerNavigation) { return; } + + var activeNodes = document.querySelectorAll( + getSlideHash(slideNumber) + ' .active.auto-added' + ), + i = 0, l; + + for (l = activeNodes.length; i < l; i++) { + activeNodes[i].classList.remove('active'); + activeNodes[i].classList.remove('auto-added'); + } + } + + // Event handlers + window.addEventListener('DOMContentLoaded', function () { - if (!isSlideListMode()) { - // "?full" is present without slide hash so we should display first + if (!isListMode()) { + // "?full" is present without slide hash, so we should display first // slide - if ( -1 === getCurrentSlideNumber() ) { - history.replaceState(null, null, url.pathname + '?full' + getSlideHashByNumber( 0 ) ); + if (-1 === getCurrentSlideNumber()) { + history.replaceState(null, null, url.pathname + '?full' + getSlideHash(0)); } - enterSingleSlideMode(); + enterSlideMode(); updateProgress(getCurrentSlideNumber()); } }, false); window.addEventListener('popstate', function (e) { - if (isSlideListMode()) { - enterSlideListMode(); + if (isListMode()) { + enterListMode(); scrollToCurrentSlide(); } else { - enterSingleSlideMode(); + enterSlideMode(); } }, false); window.addEventListener('resize', function (e) { - if (!isSlideListMode()) { + if (!isListMode()) { applyTransform(getTransform()); } }, false); document.addEventListener('keydown', function (e) { - if (e.altKey || e.ctrlKey || e.metaKey) return; - - var current_slide_number = getCurrentSlideNumber(); + // Shortcut for alt, shift and meta keys + if (e.altKey || e.ctrlKey || e.metaKey) { return; } + + var currentSlideNumber = getCurrentSlideNumber(); switch (e.which) { - case 9: // Tab = +1; Shift + Tab = -1 - if (isSlideListMode()) { - e.preventDefault(); - - current_slide_number += e.shiftKey ? -1 : 1; - url.hash = getSlideHashByNumber(current_slide_number); - } - break; - case 13: // Enter - if (isSlideListMode()) { + if (isListMode()) { e.preventDefault(); - history.pushState(null, null, url.pathname + '?full' + getSlideHashByNumber(current_slide_number)); - enterSingleSlideMode(); + history.pushState(null, null, url.pathname + '?full' + getSlideHash(currentSlideNumber)); + enterSlideMode(); - updateProgress(current_slide_number); + updateProgress(currentSlideNumber); } break; case 27: // Esc - if (!isSlideListMode()) { + if (!isListMode()) { e.preventDefault(); - history.pushState(null, null, url.pathname + getSlideHashByNumber(current_slide_number)); - enterSlideListMode(); + history.pushState(null, null, url.pathname + getSlideHash(currentSlideNumber)); + enterListMode(); scrollToCurrentSlide(); } break; @@ -180,8 +226,9 @@ case 75: // k e.preventDefault(); - current_slide_number--; - goToSlide(current_slide_number); + resetInnerNavigation(currentSlideNumber); + currentSlideNumber--; + goToSlide(currentSlideNumber); break; case 34: // PgDown @@ -191,29 +238,41 @@ case 74: // j e.preventDefault(); - current_slide_number++; - goToSlide(current_slide_number); + // Only go to next slide if current slide have no inner + // navigation or inner navigation is fully shown + if ( + !slideList[currentSlideNumber].hasInnerNavigation || + -1 === increaseInnerNavigation(currentSlideNumber) + ) { + resetInnerNavigation(currentSlideNumber); + currentSlideNumber++; + goToSlide(currentSlideNumber); + } break; case 36: // Home e.preventDefault(); - current_slide_number = 0; - goToSlide(current_slide_number); + resetInnerNavigation(currentSlideNumber); + currentSlideNumber = 0; + goToSlide(currentSlideNumber); break; case 35: // End e.preventDefault(); - current_slide_number = slideList.length - 1; - goToSlide(current_slide_number); + resetInnerNavigation(currentSlideNumber); + currentSlideNumber = slideList.length - 1; + goToSlide(currentSlideNumber); break; + case 9: // Tab = +1; Shift + Tab = -1 case 32: // Space = +1; Shift + Space = -1 e.preventDefault(); - current_slide_number += e.shiftKey ? -1 : 1; - goToSlide(current_slide_number); + resetInnerNavigation(currentSlideNumber); + currentSlideNumber += e.shiftKey ? -1 : 1; + goToSlide(currentSlideNumber); break; default: @@ -225,20 +284,21 @@ document.addEventListener('touchend', dispatchSingleSlideMode, false); document.addEventListener('touchstart', function (e) { - if(!isSlideListMode()) { - var current_slide_number = getCurrentSlideNumber(), + if (!isListMode()) { + var currentSlideNumber = getCurrentSlideNumber(), x = e.touches[0].pageX; - if (x > window.innerWidth/2) { - current_slide_number++; + if (x > window.innerWidth / 2) { + currentSlideNumber++; } else { - current_slide_number--; + currentSlideNumber--; } - goToSlide(current_slide_number); + + goToSlide(currentSlideNumber); } }, false); document.addEventListener('touchmove', function (e) { - if(!isSlideListMode()) { + if (!isListMode()) { e.preventDefault(); } }, false);