From 4e562e2978855b90a584b470c56f14e24edc7d11 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Tue, 24 Jan 2017 15:04:05 +0100 Subject: [PATCH 01/33] Common gitignore file --- .gitignore | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c84756 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# IDE files # +############# +.idea/ + +# Third Party # +############### +node_modules/ + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db \ No newline at end of file From 2e93754ba1f6e36ac76e0e82bf5d442af082f4c3 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Tue, 24 Jan 2017 15:04:15 +0100 Subject: [PATCH 02/33] Adding package.json --- package.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..fdbc19b --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "webslides", + "version": "0.0.1", + "description": "Making HTML presentations easy", + "main": "index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/jlantunez/webslides.git" + }, + "keywords": [ + "webslides", + "presentation", + "css" + ], + "author": "jlantunez", + "license": "MIT", + "bugs": { + "url": "https://github.com/jlantunez/webslides/issues" + }, + "homepage": "https://github.com/jlantunez/webslides#readme" +} From 70bde2f4aedd01466b98e40c9301dfef4f512879 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Wed, 25 Jan 2017 22:48:35 +0100 Subject: [PATCH 03/33] Entry point --- .editorconfig | 20 ++++++++++++++++ package.json | 29 ++++++++++++++++++++++- src/full.js | 3 +++ src/lite.js | 3 +++ src/modules/navigation.js | 48 +++++++++++++++++++++++++++++++++++++++ src/modules/webslides.js | 33 +++++++++++++++++++++++++++ src/utils/dom.js | 12 ++++++++++ webpack.config.babel.js | 27 ++++++++++++++++++++++ 8 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 src/full.js create mode 100644 src/lite.js create mode 100644 src/modules/navigation.js create mode 100644 src/modules/webslides.js create mode 100644 src/utils/dom.js create mode 100644 webpack.config.babel.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7e6c763 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 233 + +[*.json] +indent_style = space +indent_size = 2 + +[*.yml] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/package.json b/package.json index fdbc19b..81d7443 100644 --- a/package.json +++ b/package.json @@ -17,5 +17,32 @@ "bugs": { "url": "https://github.com/jlantunez/webslides/issues" }, - "homepage": "https://github.com/jlantunez/webslides#readme" + "homepage": "https://github.com/jlantunez/webslides#readme", + "devDependencies": { + "babel-cli": "^6.22.2", + "babel-core": "^6.22.1", + "babel-loader": "^6.2.10", + "babel-preset-es2015": "^6.22.0", + "npm-run-all": "^4.0.1", + "rimraf": "^2.5.4", + "webpack": "^2.2.0", + "webpack-dev-server": "^2.2.0" + }, + "scripts": { + "prebuild": "rimraf dist", + "build:": "npm-run-all --parallel build:*", + "build:main": "webpack", + "build:main.min": "webpack --output-filename [name].min.js -p", + "dev": "webpack-dev-server" + }, + "babel": { + "presets": [ + [ + "es2015", + { + "modules": false + } + ] + ] + } } diff --git a/src/full.js b/src/full.js new file mode 100644 index 0000000..ad71d64 --- /dev/null +++ b/src/full.js @@ -0,0 +1,3 @@ +import WebSlides from './modules/webslides'; + +window.WebSlides = WebSlides; diff --git a/src/lite.js b/src/lite.js new file mode 100644 index 0000000..ad71d64 --- /dev/null +++ b/src/lite.js @@ -0,0 +1,3 @@ +import WebSlides from './modules/webslides'; + +window.WebSlides = WebSlides; diff --git a/src/modules/navigation.js b/src/modules/navigation.js new file mode 100644 index 0000000..79e3501 --- /dev/null +++ b/src/modules/navigation.js @@ -0,0 +1,48 @@ +import DOM from '../utils/dom'; + +const ELEMENT_ID = { + NAV: 'navigation', + NEXT: 'next', + PREV: 'previous', + COUNTER: 'counter' +}; + +const LABELS = { + VERTICAL: { + NEXT: '↓', + PREV: '→' + }, + HORIZONTAL: { + NEXT: '↑', + PREV: '←' + } +}; + + +export default class Navigation { + constructor({ isVertical }) { + const arrowLabels = isVertical ? LABELS.VERTICAL : LABELS.HORIZONTAL; + + this.el = DOM.createNode('div', 'navigation'); + this.next = Navigation.createArrow(ELEMENT_ID.NEXT, arrowLabels.NEXT); + this.prev = Navigation.createArrow(ELEMENT_ID.PREV, arrowLabels.PREV); + this.counter = DOM.createNode('span', ELEMENT_ID.COUNTER); + + this.el.appendChild(this.next); + this.el.appendChild(this.prev); + this.el.appendChild(this.counter); + console.log(this); + } + + updateCounter(current, max) { + this.counter.textContent = `${current} / ${max}`; + } + + static createArrow(id, text) { + const arrow = DOM.createNode('a', id, text); + arrow.href = '#'; + arrow.title = 'Arrow Keys'; + + return arrow; + } +} diff --git a/src/modules/webslides.js b/src/modules/webslides.js new file mode 100644 index 0000000..33125d2 --- /dev/null +++ b/src/modules/webslides.js @@ -0,0 +1,33 @@ +import Navigation from './navigation'; + +export default class WebSlides { + constructor() { + this.el = document.getElementById('webslides'); + this.moving = false; + this.currentSlide = 0; + + if (!this.el) { + throw new Error('Couldn\'t find the webslides container!'); + } + + this.createNav_(); + this.navigation.updateCounter(this.currentSlide + 1, this.slides.length); + } + + createNav_() { + this.navigation = new Navigation({ + isVertical: true + }); + this.el.appendChild(this.navigation.el); + } + + grabSlides_() { + this.slides = Array.from(this.el.getElementsByClassName('slide')); + } + + goToSlide(slide) { + if (slide >= 0 && slide < this.slides.length) { + console.log('Foo'); + } + } +} diff --git a/src/utils/dom.js b/src/utils/dom.js new file mode 100644 index 0000000..11cd14e --- /dev/null +++ b/src/utils/dom.js @@ -0,0 +1,12 @@ +export default class DOM { + static createNode(tag, id = '', text = null) { + const node = document.createElement(tag); + node.id = id; + + if (text) { + node.textContent = text; + } + + return node; + } +} diff --git a/webpack.config.babel.js b/webpack.config.babel.js new file mode 100644 index 0000000..922ad66 --- /dev/null +++ b/webpack.config.babel.js @@ -0,0 +1,27 @@ +const path = require('path'); + +const src = path.join(__dirname, 'src'); + +module.exports = { + context: src, + entry: { + webslides: './full.js', + "webslides-lite": './lite.js' + }, + output: { + filename: '[name].js', + path: path.join(__dirname, 'dist') + }, + devServer: { + contentBase: __dirname, + }, + module: { + rules: [ + { + test: /\.js$/, + loader: 'babel-loader', + include: src + } + ] + } +}; From 01fa93d50e0c620e1d23723ab1f9be1c689cd77f Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Fri, 27 Jan 2017 12:28:45 +0100 Subject: [PATCH 04/33] Finishing the core bits --- src/modules/hash.js | 25 +++++++ src/modules/navigation.js | 9 +-- src/modules/slide.js | 46 ++++++++++++ src/modules/webslides.js | 150 ++++++++++++++++++++++++++++++++++++-- src/utils/dom.js | 16 ++++ src/utils/easing.js | 9 +++ src/utils/scroll-to.js | 58 +++++++++++++++ webpack.config.babel.js | 3 +- 8 files changed, 302 insertions(+), 14 deletions(-) create mode 100644 src/modules/hash.js create mode 100644 src/modules/slide.js create mode 100644 src/utils/easing.js create mode 100644 src/utils/scroll-to.js diff --git a/src/modules/hash.js b/src/modules/hash.js new file mode 100644 index 0000000..6aee80d --- /dev/null +++ b/src/modules/hash.js @@ -0,0 +1,25 @@ +const HASH = '#slide'; +const slideRegex = /#slide=(\d+)/; + +export default class Hash { + static getSlideNumber() { + let results = document.location.hash.match(slideRegex); + let slide = 0; + + if (Array.isArray(results)) { + slide = parseInt(results[1], 10); + } + + if (!Number.isInteger(slide) || slide < 0 || !Array.isArray(results)) { + slide = null; + } else { + slide--; // Convert to 0 index + } + + return slide; + } + + static setSlideNumber(number) { + history.pushState(null, `Slide ${number}`, `${HASH}=${number}`); + } +} diff --git a/src/modules/navigation.js b/src/modules/navigation.js index 79e3501..19d04de 100644 --- a/src/modules/navigation.js +++ b/src/modules/navigation.js @@ -9,12 +9,12 @@ const ELEMENT_ID = { const LABELS = { VERTICAL: { - NEXT: '↓', - PREV: '→' + NEXT: '↓', + PREV: '↑' }, HORIZONTAL: { - NEXT: '↑', - PREV: '←' + NEXT: '→', + PREV: '←' } }; @@ -31,7 +31,6 @@ export default class Navigation { this.el.appendChild(this.next); this.el.appendChild(this.prev); this.el.appendChild(this.counter); - console.log(this); } updateCounter(current, max) { diff --git a/src/modules/slide.js b/src/modules/slide.js new file mode 100644 index 0000000..efa8b14 --- /dev/null +++ b/src/modules/slide.js @@ -0,0 +1,46 @@ +import DOM from '../utils/dom'; + +const CLASSES = { + SLIDE: 'slide', + CURRENT: 'current' +}; + +export default class Slide { + constructor(el, i) { + this.el = el; + this.parent = el.parentNode; + this.i = i; + this.el.id = 'section-' + (i + 1); + + this.el.classList.add(CLASSES.SLIDE); + + // Hide slides by default + this.hide(); + } + + hide() { + DOM.hide(this.el); + this.el.classList.remove(CLASSES.CURRENT); + } + + show() { + DOM.show(this.el); + this.el.classList.add(CLASSES.CURRENT); + } + + moveAfterLast() { + const last = this.parent.childNodes[this.parent.childElementCount - 1]; + + this.parent.insertBefore(this.el, last.nextSibling); + } + + moveBeforeFirst() { + const first = this.parent.childNodes[0]; + + this.parent.insertBefore(this.el, first); + } + + static isCandidate(el) { + return el.nodeType === 1 && el.tagName === 'SECTION'; + } +} diff --git a/src/modules/webslides.js b/src/modules/webslides.js index 33125d2..d001481 100644 --- a/src/modules/webslides.js +++ b/src/modules/webslides.js @@ -1,33 +1,167 @@ +import Hash from './hash'; import Navigation from './navigation'; +import Slide from './slide'; +import DOM from '../utils/dom'; +import ScrollHelper from '../utils/scroll-to'; + +const CLASSES = { + VERTICAL: 'vertical' +}; export default class WebSlides { constructor() { this.el = document.getElementById('webslides'); - this.moving = false; - this.currentSlide = 0; + this.isMoving = false; + this.slides = null; + this.navigation = null; + this.currentSlideI_ = -1; + this.currentSlide_ = null; + this.maxSlide_ = 0; + this.isVertical = this.el.classList.contains(CLASSES.VERTICAL); if (!this.el) { throw new Error('Couldn\'t find the webslides container!'); } + this.removeChildren_(); + this.grabSlides_(); this.createNav_(); - this.navigation.updateCounter(this.currentSlide + 1, this.slides.length); + this.initSlides_(); + + window.st = ScrollHelper; + } + + removeChildren_() { + const nodes = this.el.childNodes; + let i = nodes.length; + + while (i--) { + const node = nodes[i]; + + if (!Slide.isCandidate(node)) { + this.el.removeChild(node); + } + } } createNav_() { this.navigation = new Navigation({ - isVertical: true + isVertical: this.isVertical }); + this.el.appendChild(this.navigation.el); } grabSlides_() { - this.slides = Array.from(this.el.getElementsByClassName('slide')); + this.slides = Array.from(this.el.childNodes) + .map((slide, i) => new Slide(slide, i)); + + this.maxSlide_ = this.slides.length; } - goToSlide(slide) { - if (slide >= 0 && slide < this.slides.length) { - console.log('Foo'); + goToSlide(slideI, forward = null) { + if (this.isValidIndexSlide_(slideI) && !this.isMoving) { + this.isMoving = true; + let isMovingForward = false; + + if (forward !== null) { + isMovingForward = forward; + } else { + if (Number.isInteger(this.currentSlideI_)) { + isMovingForward = slideI > this.currentSlideI_; + } + } + + const nextSlide = this.slides[slideI]; + + if (this.currentSlide_ !== null) { + this.animateToSlide_(isMovingForward, nextSlide, this.onSlideChange_); + } else { + this.transitionToSlide_( + isMovingForward, nextSlide, this.onSlideChange_); + nextSlide.moveBeforeFirst(); + } } } + + animateToSlide_(isMovingForward, nextSlide, callback) { + DOM.lockScroll(); + + nextSlide.show(); + + ScrollHelper.scrollTo(nextSlide.el.offsetTop, 500, () => { + this.currentSlide_.hide(); + + if (isMovingForward) { + this.currentSlide_.moveAfterLast(); + } else { + nextSlide.moveBeforeFirst(); + } + + DOM.unlockScroll(); + callback.call(this, nextSlide); + }); + } + + transitionToSlide_(isMovingForward, nextSlide, callback) { + ScrollHelper.scrollTo(0, 0); + + nextSlide.show(); + + if (this.currentSlide_) { + if (isMovingForward) { + this.currentSlide_.moveAfterLast(); + } else { + nextSlide.moveBeforeFirst(); + } + } + + callback.call(this, nextSlide); + } + + onSlideChange_(slide) { + this.currentSlide_ = slide; + this.currentSlideI_ = slide.i; + this.navigation.updateCounter( + this.currentSlideI_ + 1, this.maxSlide_); + this.isMoving = false; + + Hash.setSlideNumber(this.currentSlideI_ + 1); + } + + goNext() { + let nextIndex = this.currentSlideI_ + 1; + + if (nextIndex >= this.maxSlide_) { + nextIndex = 0; + } + + this.goToSlide(nextIndex, true); + } + + goPrev() { + let prevIndex = this.currentSlideI_ - 1; + + if (prevIndex < 0) { + prevIndex = this.maxSlide_ - 1; + } + + this.goToSlide(prevIndex, false); + } + + isValidIndexSlide_(i) { + return i >= 0 && i < this.maxSlide_; + } + + initSlides_() { + let slideNumber = Hash.getSlideNumber(); + + // Not valid + if (slideNumber === null || + slideNumber >= this.maxSlide_) { + slideNumber = 0; + } + + this.goToSlide(slideNumber); + } } diff --git a/src/utils/dom.js b/src/utils/dom.js index 11cd14e..4caa078 100644 --- a/src/utils/dom.js +++ b/src/utils/dom.js @@ -9,4 +9,20 @@ export default class DOM { return node; } + + static hide(el) { + el.style.display = 'none'; + } + + static show(el) { + el.style.display = ''; + } + + static lockScroll() { + document.documentElement.style.overflow = 'hidden'; + } + + static unlockScroll() { + document.documentElement.style.overflow = 'auto'; + } } diff --git a/src/utils/easing.js b/src/utils/easing.js new file mode 100644 index 0000000..68604c6 --- /dev/null +++ b/src/utils/easing.js @@ -0,0 +1,9 @@ +function swing (p) { + return 0.5 - Math.cos(p * Math.PI) / 2; +} + +function linear(p) { + return p; +} + +export default { swing, linear }; diff --git a/src/utils/scroll-to.js b/src/utils/scroll-to.js new file mode 100644 index 0000000..94888e5 --- /dev/null +++ b/src/utils/scroll-to.js @@ -0,0 +1,58 @@ +import Easings from './easing'; + +let SCROLLABLE_CONTAINER; + +/** + * Returns the correct DOM element to be used for scrolling the + * page, due to Firefox not scrolling on document.body. + * @return {Element} Scrollable Element. + */ +function getScrollableContainer() { + if (SCROLLABLE_CONTAINER) { + return SCROLLABLE_CONTAINER; + } + + const documentElement = window.document.documentElement; + let scrollableContainer; + + documentElement.scrollTop = 1; + + if (documentElement.scrollTop === 1) { + documentElement.scrollTop = 0; + scrollableContainer = documentElement; + } else { + scrollableContainer = document.body; + } + + SCROLLABLE_CONTAINER = scrollableContainer; + + return scrollableContainer; +} + +function scrollTo(y, duration = 500, cb = () => {}) { + const scrollableContainer = getScrollableContainer(); + const delta = y - scrollableContainer.scrollTop; + const increment = 20; + + const animateScroll = elapsedTime => { + elapsedTime += increment; + const percent = elapsedTime / duration; + + scrollableContainer.scrollTop = Easings.swing( + percent, + elapsedTime * percent, + y, + delta, + duration) * delta; + + if (elapsedTime < duration) { + setTimeout(() => animateScroll(elapsedTime), increment); + } else { + cb(); + } + }; + + animateScroll(0); +} + +export default { getScrollableContainer, scrollTo }; diff --git a/webpack.config.babel.js b/webpack.config.babel.js index 922ad66..0b4fc02 100644 --- a/webpack.config.babel.js +++ b/webpack.config.babel.js @@ -10,7 +10,8 @@ module.exports = { }, output: { filename: '[name].js', - path: path.join(__dirname, 'dist') + path: path.join(__dirname, 'dist'), + publicPath: '/dist', }, devServer: { contentBase: __dirname, From d4e6662ab6f9229fd1996349eb307704f20ac0bf Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Fri, 27 Jan 2017 15:28:40 +0100 Subject: [PATCH 05/33] Starting to add comments and JSDoc --- src/modules/hash.js | 36 +++++++++++++++++++++++++++++++++++- src/utils/dom.js | 31 ++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/modules/hash.js b/src/modules/hash.js index 6aee80d..426bd6c 100644 --- a/src/modules/hash.js +++ b/src/modules/hash.js @@ -1,7 +1,32 @@ const HASH = '#slide'; const slideRegex = /#slide=(\d+)/; +/** + * Static class with methods to manipulate and extract infro from the hash of + * the URL. + */ export default class Hash { + /** + * Listens to the hashchange event and reacts to it by making the + * WebSlide instance navigate to the needed slide. + * @param {WebSlides} wsInstance + */ + static init(wsInstance) { + window.addEventListener('hashchange', () => { + const newSlideIndex = Hash.getSlideNumber(); + + if (newSlideIndex !== null) { + wsInstance.goToSlide(newSlideIndex); + } + }, false); + } + + /** + * Gets the slide number from the hash by a regex matching `#slide=` and gets + * the number after it. If the number is invalid or less than 0, it will + * return null as an invalid value. + * @return {?number} + */ static getSlideNumber() { let results = document.location.hash.match(slideRegex); let slide = 0; @@ -19,7 +44,16 @@ export default class Hash { return slide; } + /** + * It will update the hash (if it's different) so it reflects the slide + * number being visible. + * @param {number} number The number of the slide we're transitioning to. + */ static setSlideNumber(number) { - history.pushState(null, `Slide ${number}`, `${HASH}=${number}`); + if (Hash.getSlideNumber() !== (number - 1)) { + history.pushState({ + slideI: number - 1 + }, `Slide ${number}`, `${HASH}=${number}`); + } } } diff --git a/src/utils/dom.js b/src/utils/dom.js index 4caa078..9fb5ef4 100644 --- a/src/utils/dom.js +++ b/src/utils/dom.js @@ -1,5 +1,17 @@ +/** + * Static class for DOM helper. + */ export default class DOM { - static createNode(tag, id = '', text = null) { + /** + * Creates a node with optional parameters. + * @param {string} tag The name of the tag of the needed element. + * @param {string} id The desired id for the element. It defaults to an + * empty string. + * @param {string} text The desired text to go inside of the element. It defaults + * to an empty string. + * @return {Element} + */ + static createNode(tag, id = '', text = '') { const node = document.createElement(tag); node.id = id; @@ -10,18 +22,35 @@ export default class DOM { return node; } + /** + * Hides an element setting the display to none. + * @param {Element} el Element to be hidden. + */ static hide(el) { el.style.display = 'none'; } + /** + * Shows an element by removing the display property. This is only intended + * to be used in conjunction with DOM.hide. + * @param {Element} el Element to be shown. + */ static show(el) { el.style.display = ''; } + /** + * Locks the scroll on the document by setting the HTML to have a hidden + * overflow. + */ static lockScroll() { document.documentElement.style.overflow = 'hidden'; } + /** + * Unlocks the scroll on the document by setting the HTML to have an auto + * overflow. + */ static unlockScroll() { document.documentElement.style.overflow = 'auto'; } From 86cc1f494f1d4e98b489e3b4ac117c14ad4b8ae5 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Fri, 27 Jan 2017 15:32:50 +0100 Subject: [PATCH 06/33] More JSDocs for the slides --- src/modules/slide.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/modules/slide.js b/src/modules/slide.js index efa8b14..6203cb9 100644 --- a/src/modules/slide.js +++ b/src/modules/slide.js @@ -5,41 +5,77 @@ const CLASSES = { CURRENT: 'current' }; +/** + * Wrapper for the Slide section. + */ export default class Slide { + /** + * Bootstraps the slide by saving some data, adding a class and hiding it. + * @param {Element} el Section element. + * @param {number} i Zero based index of the slide. + */ constructor(el, i) { + /** + * @type {Element} + */ this.el = el; + /** + * The section's parent. + * @type {Node} + */ this.parent = el.parentNode; + /** + * @type {number} + */ this.i = i; - this.el.id = 'section-' + (i + 1); + this.el.id = 'section-' + (i + 1); this.el.classList.add(CLASSES.SLIDE); // Hide slides by default this.hide(); } + /** + * Hides the node and removes the class that makes it "active". + */ hide() { DOM.hide(this.el); this.el.classList.remove(CLASSES.CURRENT); } + /** + * Shows the node and adds the class that makes it "active". + */ show() { DOM.show(this.el); this.el.classList.add(CLASSES.CURRENT); } + /** + * Moves the section to the bottom of the section's list. + */ moveAfterLast() { const last = this.parent.childNodes[this.parent.childElementCount - 1]; this.parent.insertBefore(this.el, last.nextSibling); } + /** + * Moves the section to the top of the section's list. + */ moveBeforeFirst() { const first = this.parent.childNodes[0]; this.parent.insertBefore(this.el, first); } + /** + * Checks whether an element is a valid candidate to be a slide by ensuring + * it's a "section" element. + * @param {Element} el Element to be checked. + * @return {boolean} Whether is candidate or not. + */ static isCandidate(el) { return el.nodeType === 1 && el.tagName === 'SECTION'; } From 049d0837440b0204db9debfd29bb3fccb1984e37 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Fri, 27 Jan 2017 15:34:34 +0100 Subject: [PATCH 07/33] =?UTF-8?q?Easing=E2=80=99s=20JSDoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/easing.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/utils/easing.js b/src/utils/easing.js index 68604c6..4cbc293 100644 --- a/src/utils/easing.js +++ b/src/utils/easing.js @@ -1,7 +1,17 @@ +/** + * Swing easing function. + * @param {number} p The percentage of time that has passed. + * @return {number} + */ function swing (p) { return 0.5 - Math.cos(p * Math.PI) / 2; } +/** + * Linear easing function. + * @param {number} p The percentage of time that has passed. + * @return {number} + */ function linear(p) { return p; } From 7b495cccf136f01f522d4bcf12122c8cc4a01675 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sat, 28 Jan 2017 15:38:21 +0100 Subject: [PATCH 08/33] Navigation to plugins --- src/modules/navigation.js | 24 +++++++++++++++++--- src/modules/plugins.js | 5 +++++ src/modules/webslides.js | 47 ++++++++++++++++++++++++++------------- 3 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 src/modules/plugins.js diff --git a/src/modules/navigation.js b/src/modules/navigation.js index 19d04de..1ce624d 100644 --- a/src/modules/navigation.js +++ b/src/modules/navigation.js @@ -18,10 +18,15 @@ const LABELS = { } }; - export default class Navigation { - constructor({ isVertical }) { - const arrowLabels = isVertical ? LABELS.VERTICAL : LABELS.HORIZONTAL; + /** + * The Navigation constructor. It'll create all the nodes needed for the + * navigation such as the arrows and the counter. + * @param {WebSlides} wsInstance The WebSlides instance + */ + constructor(wsInstance) { + const arrowLabels = wsInstance.isVertical ? + LABELS.VERTICAL : LABELS.HORIZONTAL; this.el = DOM.createNode('div', 'navigation'); this.next = Navigation.createArrow(ELEMENT_ID.NEXT, arrowLabels.NEXT); @@ -31,12 +36,25 @@ export default class Navigation { this.el.appendChild(this.next); this.el.appendChild(this.prev); this.el.appendChild(this.counter); + + wsInstance.el.appendChild(this.el); } + /** + * + * @param current + * @param max + */ updateCounter(current, max) { this.counter.textContent = `${current} / ${max}`; } + /** + * Creates an arrow to navigate. + * @param {!String} id Desired ID for the arrow. + * @param {!String} text Desired text for the arrow. + * @return {Element} The arrow element. + */ static createArrow(id, text) { const arrow = DOM.createNode('a', id, text); arrow.href = '#'; diff --git a/src/modules/plugins.js b/src/modules/plugins.js new file mode 100644 index 0000000..4b0afd9 --- /dev/null +++ b/src/modules/plugins.js @@ -0,0 +1,5 @@ +import Navigation from './navigation'; + +export default { + Navigation +}; diff --git a/src/modules/webslides.js b/src/modules/webslides.js index d001481..7d7bcf6 100644 --- a/src/modules/webslides.js +++ b/src/modules/webslides.js @@ -1,5 +1,5 @@ import Hash from './hash'; -import Navigation from './navigation'; +import Plugins from './plugins'; import Slide from './slide'; import DOM from '../utils/dom'; import ScrollHelper from '../utils/scroll-to'; @@ -8,16 +8,18 @@ const CLASSES = { VERTICAL: 'vertical' }; +const PLUGINS = ['Navigation']; + export default class WebSlides { constructor() { this.el = document.getElementById('webslides'); this.isMoving = false; this.slides = null; - this.navigation = null; this.currentSlideI_ = -1; this.currentSlide_ = null; this.maxSlide_ = 0; this.isVertical = this.el.classList.contains(CLASSES.VERTICAL); + this.plugins = {}; if (!this.el) { throw new Error('Couldn\'t find the webslides container!'); @@ -25,10 +27,10 @@ export default class WebSlides { this.removeChildren_(); this.grabSlides_(); - this.createNav_(); + this.createPlugins_(); this.initSlides_(); - window.st = ScrollHelper; + Hash.init(this); } removeChildren_() { @@ -44,12 +46,15 @@ export default class WebSlides { } } - createNav_() { - this.navigation = new Navigation({ - isVertical: this.isVertical + createPlugins_() { + PLUGINS.forEach(pluginName => { + if (Plugins[pluginName]) { + const pluginCto = Plugins[pluginName]; + this.plugins[pluginCto] = new pluginCto(this); + } else { + throw new Error(`Tried to initialize plugin ${pluginName} but doesn't exist.`); + } }); - - this.el.appendChild(this.navigation.el); } grabSlides_() { @@ -67,11 +72,10 @@ export default class WebSlides { if (forward !== null) { isMovingForward = forward; } else { - if (Number.isInteger(this.currentSlideI_)) { + if (this.currentSlideI_ >= 0) { isMovingForward = slideI > this.currentSlideI_; } } - const nextSlide = this.slides[slideI]; if (this.currentSlide_ !== null) { @@ -87,15 +91,19 @@ export default class WebSlides { animateToSlide_(isMovingForward, nextSlide, callback) { DOM.lockScroll(); - nextSlide.show(); + if (!isMovingForward) { + nextSlide.moveBeforeFirst(); + nextSlide.show(); + ScrollHelper.scrollTo(this.currentSlide_.el.offsetTop, 0); + } else { + nextSlide.show(); + } ScrollHelper.scrollTo(nextSlide.el.offsetTop, 500, () => { this.currentSlide_.hide(); if (isMovingForward) { this.currentSlide_.moveAfterLast(); - } else { - nextSlide.moveBeforeFirst(); } DOM.unlockScroll(); @@ -122,8 +130,6 @@ export default class WebSlides { onSlideChange_(slide) { this.currentSlide_ = slide; this.currentSlideI_ = slide.i; - this.navigation.updateCounter( - this.currentSlideI_ + 1, this.maxSlide_); this.isMoving = false; Hash.setSlideNumber(this.currentSlideI_ + 1); @@ -162,6 +168,15 @@ export default class WebSlides { slideNumber = 0; } + // Keeping the order + if (slideNumber !== 0) { + let i = 0; + while(i < slideNumber) { + this.slides[i].moveAfterLast(); + i++; + } + } + this.goToSlide(slideNumber); } } From ea164b6d691932984445c72fd863d8fda51a19c4 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sat, 28 Jan 2017 15:38:33 +0100 Subject: [PATCH 09/33] Adding custom event support --- src/utils/custom-event.js | 38 ++++++++++++++++++++++++++++++++++++++ src/utils/dom.js | 15 +++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/utils/custom-event.js diff --git a/src/utils/custom-event.js b/src/utils/custom-event.js new file mode 100644 index 0000000..587af72 --- /dev/null +++ b/src/utils/custom-event.js @@ -0,0 +1,38 @@ +const NativeCustomEvent = window.CustomEvent; + +/** + * Check for the usage of native support for CustomEvents which is lacking + * completely on IE. + * @return {boolean} Whether it can be used or not. + */ +function canIuseNativeCustom () { + try { + const p = new NativeCustomEvent('t', { detail: { a: 'b' } }); + return 't' === p.type && 'b' === p.detail.a; + } catch (e) { + } + return false; +} + +/** + * Lousy polyfill for the Custom Event constructor for IE. + * @param {!string} type The type of the event. + * @param {?Object} params Additional information for the event. + * @return {Event} + * @constructor + */ +const IECustomEvent = function CustomEvent(type, params) { + const e = document.createEvent('CustomEvent'); + + if (params) { + e.initCustomEvent(type, params.bubbles, params.cancelable, params.detail); + } else { + e.initCustomEvent(type, false, false, undefined); + } + + return e; +}; + +const WSCustomEvent = canIuseNativeCustom() ? NativeCustomEvent : IECustomEvent; + +export default WSCustomEvent; diff --git a/src/utils/dom.js b/src/utils/dom.js index 9fb5ef4..7a1c6d9 100644 --- a/src/utils/dom.js +++ b/src/utils/dom.js @@ -1,3 +1,6 @@ +import WSCustomEvent from './custom-event'; + + /** * Static class for DOM helper. */ @@ -54,4 +57,16 @@ export default class DOM { static unlockScroll() { document.documentElement.style.overflow = 'auto'; } + + /** + * Fires a custom event on the given target. + * @param {Element} target The target of the event. + * @param {string} eventType The event type. + * @param {Object} eventInfo Optional parameter to provide additional data + * to the event. + */ + static fireEvent(target, eventType, eventInfo = { detail: '' }) { + const event = new WSCustomEvent(eventType, eventInfo); + target.dispatchEvent(event); + } } From 6e7ce46ebcfbd39c45681c3dbe039ef3ec1e0501 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sat, 28 Jan 2017 15:38:49 +0100 Subject: [PATCH 10/33] Fixing the scroll function and adding docs --- src/utils/scroll-to.js | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/utils/scroll-to.js b/src/utils/scroll-to.js index 94888e5..7a49d04 100644 --- a/src/utils/scroll-to.js +++ b/src/utils/scroll-to.js @@ -29,21 +29,37 @@ function getScrollableContainer() { return scrollableContainer; } +/** + * Smoothly scrolls to a given Y position using Easing.Swing. It'll run a + * callback upon finishing. + * @param {number} y Offset of the page to scroll to. + * @param {number} duration Duration of the animation. 500ms by default. + * @param {function} cb Callback function to call upon completion. + */ function scrollTo(y, duration = 500, cb = () => {}) { const scrollableContainer = getScrollableContainer(); const delta = y - scrollableContainer.scrollTop; - const increment = 20; + const startLocation = scrollableContainer.scrollTop; + const increment = 16; + + if (!duration) { + scrollableContainer.scrollTop = y; + cb(); + return; + } const animateScroll = elapsedTime => { elapsedTime += increment; - const percent = elapsedTime / duration; + const percent = Math.min(1, elapsedTime / duration); + const easingP = Easings.swing( + percent, + elapsedTime * percent, + y, + delta, + duration); - scrollableContainer.scrollTop = Easings.swing( - percent, - elapsedTime * percent, - y, - delta, - duration) * delta; + scrollableContainer.scrollTop = Math.floor(startLocation + + (easingP * delta)); if (elapsedTime < duration) { setTimeout(() => animateScroll(elapsedTime), increment); From ae36e155885b1c721b2a51684be63452f1b23d8e Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sat, 28 Jan 2017 16:26:55 +0100 Subject: [PATCH 11/33] Finishing navigation --- src/modules/navigation.js | 65 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/src/modules/navigation.js b/src/modules/navigation.js index 1ce624d..59178f4 100644 --- a/src/modules/navigation.js +++ b/src/modules/navigation.js @@ -27,23 +27,55 @@ export default class Navigation { constructor(wsInstance) { const arrowLabels = wsInstance.isVertical ? LABELS.VERTICAL : LABELS.HORIZONTAL; - + /** + * Navigation element. + * @type {Element} + */ this.el = DOM.createNode('div', 'navigation'); + /** + * Next button. + * @type {Element} + */ this.next = Navigation.createArrow(ELEMENT_ID.NEXT, arrowLabels.NEXT); + /** + * Prev button. + * @type {Element} + */ this.prev = Navigation.createArrow(ELEMENT_ID.PREV, arrowLabels.PREV); + /** + * Counter Element. + * @type {Element} + */ this.counter = DOM.createNode('span', ELEMENT_ID.COUNTER); + /** + * @type {WebSlides} + * @private + */ + this.ws_ = wsInstance; this.el.appendChild(this.next); this.el.appendChild(this.prev); this.el.appendChild(this.counter); - wsInstance.el.appendChild(this.el); + this.ws_.el.appendChild(this.el); + this.bindEvents_(); } /** - * - * @param current - * @param max + * Bind all events for the navigation. + * @private + */ + bindEvents_() { + this.ws_.el.addEventListener( + 'ws:slide-change', this.onSlideChanged_.bind(this)); + this.next.addEventListener('click', this.onButtonClicked_.bind(this)); + this.prev.addEventListener('click', this.onButtonClicked_.bind(this)); + } + + /** + * Updates the counter inside the navigation. + * @param {string|number} current Current slide number. + * @param {string|number} max Max slide number. */ updateCounter(current, max) { this.counter.textContent = `${current} / ${max}`; @@ -62,4 +94,27 @@ export default class Navigation { return arrow; } + + /** + * Slide Change event handler. Will update the text on the navigation. + * @param {CustomEvent} event + * @private + */ + onSlideChanged_(event) { + this.updateCounter(event.detail.currentSlide, event.detail.slides); + } + + /** + * Handles clicks on the next/prev buttons. + * @param {MouseEvent} event + * @private + */ + onButtonClicked_(event) { + event.preventDefault(); + if (event.target === this.next) { + this.ws_.goNext(); + } else { + this.ws_.goPrev(); + } + } } From 75de8a46ace7e879630e3a70584c52ef9242cc19 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sat, 28 Jan 2017 16:27:15 +0100 Subject: [PATCH 12/33] Reworking the plugins --- src/modules/webslides.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/modules/webslides.js b/src/modules/webslides.js index 7d7bcf6..78a7063 100644 --- a/src/modules/webslides.js +++ b/src/modules/webslides.js @@ -8,7 +8,9 @@ const CLASSES = { VERTICAL: 'vertical' }; -const PLUGINS = ['Navigation']; +const PLUGINS = { + 'nav': Plugins.Navigation +}; export default class WebSlides { constructor() { @@ -31,6 +33,7 @@ export default class WebSlides { this.initSlides_(); Hash.init(this); + this.onInit_(); } removeChildren_() { @@ -47,16 +50,16 @@ export default class WebSlides { } createPlugins_() { - PLUGINS.forEach(pluginName => { - if (Plugins[pluginName]) { - const pluginCto = Plugins[pluginName]; - this.plugins[pluginCto] = new pluginCto(this); - } else { - throw new Error(`Tried to initialize plugin ${pluginName} but doesn't exist.`); - } + Object.keys(PLUGINS).forEach(pluginName => { + const pluginCto = PLUGINS[pluginName]; + this.plugins[pluginCto] = new pluginCto(this); }); } + onInit_() { + DOM.fireEvent(this.el, 'ws:init'); + } + grabSlides_() { this.slides = Array.from(this.el.childNodes) .map((slide, i) => new Slide(slide, i)); @@ -133,6 +136,11 @@ export default class WebSlides { this.isMoving = false; Hash.setSlideNumber(this.currentSlideI_ + 1); + DOM.fireEvent(this.el, 'ws:slide-change', { + slides: this.maxSlide_, + currentSlide0: this.currentSlideI_, + currentSlide: this.currentSlideI_ + 1 + }); } goNext() { From 72d057293ef53d04b26c4b1eb2a80b25af6cd2dc Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sat, 28 Jan 2017 16:27:38 +0100 Subject: [PATCH 13/33] Adding to detail instead of needing to add a full object --- src/utils/dom.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/utils/dom.js b/src/utils/dom.js index 7a1c6d9..3129c2d 100644 --- a/src/utils/dom.js +++ b/src/utils/dom.js @@ -65,8 +65,11 @@ export default class DOM { * @param {Object} eventInfo Optional parameter to provide additional data * to the event. */ - static fireEvent(target, eventType, eventInfo = { detail: '' }) { - const event = new WSCustomEvent(eventType, eventInfo); + static fireEvent(target, eventType, eventInfo = {}) { + const event = new WSCustomEvent(eventType, { + detail: eventInfo + }); + target.dispatchEvent(event); } } From 0e02056ca7e6b05fb0287e5624f20e539809a82e Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sat, 28 Jan 2017 16:40:30 +0100 Subject: [PATCH 14/33] Hash to plugin --- src/modules/hash.js | 34 +++++++++++++++++++++++----------- src/modules/plugins.js | 4 +++- src/modules/webslides.js | 10 ++++------ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/modules/hash.js b/src/modules/hash.js index 426bd6c..f447d37 100644 --- a/src/modules/hash.js +++ b/src/modules/hash.js @@ -2,23 +2,35 @@ const HASH = '#slide'; const slideRegex = /#slide=(\d+)/; /** - * Static class with methods to manipulate and extract infro from the hash of + * Static class with methods to manipulate and extract info from the hash of * the URL. */ export default class Hash { /** - * Listens to the hashchange event and reacts to it by making the - * WebSlide instance navigate to the needed slide. - * @param {WebSlides} wsInstance + * Listens to the slide change event and the hash change events. + * @param wsInstance */ - static init(wsInstance) { - window.addEventListener('hashchange', () => { - const newSlideIndex = Hash.getSlideNumber(); + constructor(wsInstance) { + this.ws_ = wsInstance; - if (newSlideIndex !== null) { - wsInstance.goToSlide(newSlideIndex); - } - }, false); + wsInstance.el.addEventListener('ws:slide-change', Hash.onSlideChange_); + window.addEventListener('hashchange', this.onHashChange_.bind(this), false); + } + + /** + * hashchange event handler, makes the WebSlide instance navigate to the + * needed slide. + */ + onHashChange_() { + const newSlideIndex = Hash.getSlideNumber(); + + if (newSlideIndex !== null) { + this.ws_.goToSlide(newSlideIndex); + } + } + + static onSlideChange_(event) { + Hash.setSlideNumber(event.detail.currentSlide); } /** diff --git a/src/modules/plugins.js b/src/modules/plugins.js index 4b0afd9..29ce5ef 100644 --- a/src/modules/plugins.js +++ b/src/modules/plugins.js @@ -1,5 +1,7 @@ import Navigation from './navigation'; +import Hash from './hash'; export default { - Navigation + Navigation, + Hash }; diff --git a/src/modules/webslides.js b/src/modules/webslides.js index 78a7063..028b946 100644 --- a/src/modules/webslides.js +++ b/src/modules/webslides.js @@ -1,4 +1,3 @@ -import Hash from './hash'; import Plugins from './plugins'; import Slide from './slide'; import DOM from '../utils/dom'; @@ -9,7 +8,8 @@ const CLASSES = { }; const PLUGINS = { - 'nav': Plugins.Navigation + 'nav': Plugins.Navigation, + 'hash': Plugins.Hash }; export default class WebSlides { @@ -32,7 +32,6 @@ export default class WebSlides { this.createPlugins_(); this.initSlides_(); - Hash.init(this); this.onInit_(); } @@ -52,7 +51,7 @@ export default class WebSlides { createPlugins_() { Object.keys(PLUGINS).forEach(pluginName => { const pluginCto = PLUGINS[pluginName]; - this.plugins[pluginCto] = new pluginCto(this); + this.plugins[pluginName] = new pluginCto(this); }); } @@ -135,7 +134,6 @@ export default class WebSlides { this.currentSlideI_ = slide.i; this.isMoving = false; - Hash.setSlideNumber(this.currentSlideI_ + 1); DOM.fireEvent(this.el, 'ws:slide-change', { slides: this.maxSlide_, currentSlide0: this.currentSlideI_, @@ -168,7 +166,7 @@ export default class WebSlides { } initSlides_() { - let slideNumber = Hash.getSlideNumber(); + let slideNumber = this.plugins.hash.constructor.getSlideNumber(); // Not valid if (slideNumber === null || From b99fdc8c47b73e6c464b7a64074eaa2cf727e7cf Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sat, 28 Jan 2017 17:17:10 +0100 Subject: [PATCH 15/33] Moving plugins, adding Keyboard integration --- src/modules/webslides.js | 5 +-- src/{modules => plugins}/hash.js | 0 src/plugins/keyboard.js | 42 ++++++++++++++++++++++++++ src/{modules => plugins}/navigation.js | 0 src/{modules => plugins}/plugins.js | 4 ++- src/utils/keys.js | 10 ++++++ 6 files changed, 58 insertions(+), 3 deletions(-) rename src/{modules => plugins}/hash.js (100%) create mode 100644 src/plugins/keyboard.js rename src/{modules => plugins}/navigation.js (100%) rename src/{modules => plugins}/plugins.js (65%) create mode 100644 src/utils/keys.js diff --git a/src/modules/webslides.js b/src/modules/webslides.js index 028b946..4e31f55 100644 --- a/src/modules/webslides.js +++ b/src/modules/webslides.js @@ -1,4 +1,4 @@ -import Plugins from './plugins'; +import Plugins from '../plugins/plugins'; import Slide from './slide'; import DOM from '../utils/dom'; import ScrollHelper from '../utils/scroll-to'; @@ -9,7 +9,8 @@ const CLASSES = { const PLUGINS = { 'nav': Plugins.Navigation, - 'hash': Plugins.Hash + 'hash': Plugins.Hash, + 'keyboard': Plugins.Keyboard }; export default class WebSlides { diff --git a/src/modules/hash.js b/src/plugins/hash.js similarity index 100% rename from src/modules/hash.js rename to src/plugins/hash.js diff --git a/src/plugins/keyboard.js b/src/plugins/keyboard.js new file mode 100644 index 0000000..d231e33 --- /dev/null +++ b/src/plugins/keyboard.js @@ -0,0 +1,42 @@ +import Keys from '../utils/keys'; + +export default class Keyboard { + /** + * @param {WebSlides} wsInstance The WebSlides instance + */ + constructor(wsInstance) { + /** + * @type {WebSlides} + * @private + */ + this.ws_ = wsInstance; + + document.addEventListener("keydown", this.onKeyPress_.bind(this), false); + } + + onKeyPress_(event) { + let method; + + if (event.which === Keys.SPACE) { + method = this.ws_.goNext; + } else { + if (this.ws_.isVertical) { + if (event.which === Keys.DOWN) { + method = this.ws_.goNext; + } else if (event.which === Keys.UP) { + method = this.ws_.goPrev; + } + } else { + if (event.which === Keys.RIGHT) { + method = this.ws_.goNext; + } else if (event.which === Keys.LEFT) { + method = this.ws_.goPrev; + } + } + } + + if (method) { + method.call(this.ws_); + } + } +} diff --git a/src/modules/navigation.js b/src/plugins/navigation.js similarity index 100% rename from src/modules/navigation.js rename to src/plugins/navigation.js diff --git a/src/modules/plugins.js b/src/plugins/plugins.js similarity index 65% rename from src/modules/plugins.js rename to src/plugins/plugins.js index 29ce5ef..3bdb403 100644 --- a/src/modules/plugins.js +++ b/src/plugins/plugins.js @@ -1,7 +1,9 @@ import Navigation from './navigation'; import Hash from './hash'; +import Keyboard from './keyboard'; export default { Navigation, - Hash + Hash, + Keyboard }; diff --git a/src/utils/keys.js b/src/utils/keys.js new file mode 100644 index 0000000..02c5a4a --- /dev/null +++ b/src/utils/keys.js @@ -0,0 +1,10 @@ +const Keys = { + ENTER: 13, + SPACE: 32, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40 +}; + +export default Keys; From c1777f593f140476a5218205d5cf7e290259bb26 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sat, 28 Jan 2017 18:06:13 +0100 Subject: [PATCH 16/33] Finishing docs --- src/full.js | 3 + src/modules/webslides.js | 133 +++++++++++++++++++++++++++++++++++++-- src/plugins/keyboard.js | 9 ++- src/plugins/scroll.js | 0 4 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 src/plugins/scroll.js diff --git a/src/full.js b/src/full.js index ad71d64..20009bf 100644 --- a/src/full.js +++ b/src/full.js @@ -1,3 +1,6 @@ import WebSlides from './modules/webslides'; +import Scroll from './plugins/scroll'; + +WebSlides.registerPlugin('Scroll', Scroll); window.WebSlides = WebSlides; diff --git a/src/modules/webslides.js b/src/modules/webslides.js index 4e31f55..44d87f6 100644 --- a/src/modules/webslides.js +++ b/src/modules/webslides.js @@ -7,6 +7,7 @@ const CLASSES = { VERTICAL: 'vertical' }; +// Default plugins const PLUGINS = { 'nav': Plugins.Navigation, 'hash': Plugins.Hash, @@ -15,27 +16,68 @@ const PLUGINS = { export default class WebSlides { constructor() { + /** + * WebSlide element. + * @type {Element} + */ this.el = document.getElementById('webslides'); + /** + * Moving flag. + * @type {boolean} + */ this.isMoving = false; + /** + * Slide's array. + * @type {?Array} + */ this.slides = null; + /** + * Current slide's index. + * @type {number} + * @private + */ this.currentSlideI_ = -1; + /** + * Current slide reference. + * @type {?Slide} + * @private + */ this.currentSlide_ = null; + /** + * Max slide index. + * @type {number} + * @private + */ this.maxSlide_ = 0; + /** + * Whether the layout is going to be vertical or horizontal. + * @type {boolean} + */ this.isVertical = this.el.classList.contains(CLASSES.VERTICAL); + /** + * Plugin's dictionary. + * @type {Object} + */ this.plugins = {}; if (!this.el) { throw new Error('Couldn\'t find the webslides container!'); } + // Bootstrapping this.removeChildren_(); this.grabSlides_(); this.createPlugins_(); this.initSlides_(); - + // Finished this.onInit_(); } + /** + * Removes all children elements inside of the main container that are not + * eligible to be a Slide Element. + * @private + */ removeChildren_() { const nodes = this.el.childNodes; let i = nodes.length; @@ -49,6 +91,11 @@ export default class WebSlides { } } + /** + * Creates all the registered plugins and store the instances inside of the + * the webslide instance. + * @private + */ createPlugins_() { Object.keys(PLUGINS).forEach(pluginName => { const pluginCto = PLUGINS[pluginName]; @@ -56,10 +103,19 @@ export default class WebSlides { }); } + /** + * Called once the WebSlide instance has finished initialising. + * @private + * @fires WebSlide#ws:init + */ onInit_() { DOM.fireEvent(this.el, 'ws:init'); } + /** + * Grabs the slides from the DOM and creates all the Slides modules. + * @private + */ grabSlides_() { this.slides = Array.from(this.el.childNodes) .map((slide, i) => new Slide(slide, i)); @@ -67,6 +123,13 @@ export default class WebSlides { this.maxSlide_ = this.slides.length; } + /** + * Goes to a given slide. + * @param {!number} slideI The slide index. + * @param {?boolean} forward Whether we're forcing moving forward/backwards. + * This parameter is used only from the goNext, goPrev functions to adjust the + * scroll animations. + */ goToSlide(slideI, forward = null) { if (this.isValidIndexSlide_(slideI) && !this.isMoving) { this.isMoving = true; @@ -81,17 +144,28 @@ export default class WebSlides { } const nextSlide = this.slides[slideI]; - if (this.currentSlide_ !== null) { - this.animateToSlide_(isMovingForward, nextSlide, this.onSlideChange_); + if (this.currentSlide_ !== null && !this.isVertical) { + this.scrollTransitionToSlide_( + isMovingForward, nextSlide, this.onSlideChange_); } else { this.transitionToSlide_( isMovingForward, nextSlide, this.onSlideChange_); - nextSlide.moveBeforeFirst(); } } } - animateToSlide_(isMovingForward, nextSlide, callback) { + /** + * Transitions to a slide, doing the scroll animation. + * @param {boolean} isMovingForward Whether we're going forward or backwards. + * @param {Slide} nextSlide Next slide. + * @param {Function} callback Callback to be called upon finishing. This is an + * async function so it'll happen once the scroll animation finishes. + * @private + * @see DOM.lockScroll + * @see DOM.unlockScroll + * @see ScrollHelper.scrollTo + */ + scrollTransitionToSlide_(isMovingForward, nextSlide, callback) { DOM.lockScroll(); if (!isMovingForward) { @@ -114,6 +188,14 @@ export default class WebSlides { }); } + /** + * Transitions to a slide, without doing the scroll animation. + * @param {boolean} isMovingForward Whether we're going forward or backwards. + * @param {Slide} nextSlide Next slide. + * @param {Function} callback Callback to be called upon finishing. This is a + * sync function so it'll happen on run time. + * @private + */ transitionToSlide_(isMovingForward, nextSlide, callback) { ScrollHelper.scrollTo(0, 0); @@ -130,6 +212,14 @@ export default class WebSlides { callback.call(this, nextSlide); } + /** + * Whenever a slide is changed, this function gets called. It updates the + * references to the current slide, disables the moving flag and fires + * a custom event. + * @param {Slide} slide The slide we're transitioning to. + * @fires WebSlide#ws:slide-change + * @private + */ onSlideChange_(slide) { this.currentSlide_ = slide; this.currentSlideI_ = slide.i; @@ -142,6 +232,9 @@ export default class WebSlides { }); } + /** + * Goes to the next slide. + */ goNext() { let nextIndex = this.currentSlideI_ + 1; @@ -152,6 +245,9 @@ export default class WebSlides { this.goToSlide(nextIndex, true); } + /** + * Goes to the previous slide. + */ goPrev() { let prevIndex = this.currentSlideI_ - 1; @@ -162,10 +258,22 @@ export default class WebSlides { this.goToSlide(prevIndex, false); } + /** + * Check if the given number is a valid index to go to. + * @param {number} i The index to check. + * @return {boolean} Whether you can move to that slide or not. + * @private + */ isValidIndexSlide_(i) { return i >= 0 && i < this.maxSlide_; } + /** + * Init the shown slide on load. It'll fetch it from the Hash if present + * and, otherwise, it'll default to the first one. + * @private + * @see Hash.getSlideNumber + */ initSlides_() { let slideNumber = this.plugins.hash.constructor.getSlideNumber(); @@ -186,4 +294,19 @@ export default class WebSlides { this.goToSlide(slideNumber); } + + /** + * Registers a plugin to be loaded when the instance is created. It allows + * (on purpose) to replace default plugins. + * Those being: + * - Navigation + * - Hash + * - Keyboard + * @param {!string} key They key under which it'll be stored inside of the + * instance, inside the plugins dict. + * @param {!Function} cto Plugin constructor. + */ + static registerPlugin(key, cto) { + PLUGINS[key] = cto; + } } diff --git a/src/plugins/keyboard.js b/src/plugins/keyboard.js index d231e33..20f473d 100644 --- a/src/plugins/keyboard.js +++ b/src/plugins/keyboard.js @@ -2,6 +2,7 @@ import Keys from '../utils/keys'; export default class Keyboard { /** + * Keyboard interaction plugin. * @param {WebSlides} wsInstance The WebSlides instance */ constructor(wsInstance) { @@ -11,9 +12,15 @@ export default class Keyboard { */ this.ws_ = wsInstance; - document.addEventListener("keydown", this.onKeyPress_.bind(this), false); + document.addEventListener('keydown', this.onKeyPress_.bind(this), false); } + /** + * Reacts to the keydown event. It reacts to the arrows and space key + * depending on the layout of the page. + * @param {KeyboardEvent} event The key event. + * @private + */ onKeyPress_(event) { let method; diff --git a/src/plugins/scroll.js b/src/plugins/scroll.js new file mode 100644 index 0000000..e69de29 From 2a46012a58fbf661c1044be5a6ea8e5405ddfd0d Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sun, 29 Jan 2017 11:05:30 +0100 Subject: [PATCH 17/33] Fix build script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 81d7443..029b444 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ }, "scripts": { "prebuild": "rimraf dist", - "build:": "npm-run-all --parallel build:*", + "build": "npm-run-all --parallel build:*", "build:main": "webpack", "build:main.min": "webpack --output-filename [name].min.js -p", "dev": "webpack-dev-server" From 8e47ffead4e8e0acb15a8206a12e3f0f01bc90e2 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sun, 29 Jan 2017 11:06:04 +0100 Subject: [PATCH 18/33] Fixing issue with normal transition --- src/modules/webslides.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/modules/webslides.js b/src/modules/webslides.js index 44d87f6..f2e5a03 100644 --- a/src/modules/webslides.js +++ b/src/modules/webslides.js @@ -144,7 +144,8 @@ export default class WebSlides { } const nextSlide = this.slides[slideI]; - if (this.currentSlide_ !== null && !this.isVertical) { + if (this.currentSlide_ !== null && this.isVertical && + (!this.plugins.Touch || !this.plugins.Touch.isEnabled)) { this.scrollTransitionToSlide_( isMovingForward, nextSlide, this.onSlideChange_); } else { @@ -199,16 +200,19 @@ export default class WebSlides { transitionToSlide_(isMovingForward, nextSlide, callback) { ScrollHelper.scrollTo(0, 0); - nextSlide.show(); + if (!isMovingForward) { + nextSlide.moveBeforeFirst(); + } if (this.currentSlide_) { if (isMovingForward) { this.currentSlide_.moveAfterLast(); - } else { - nextSlide.moveBeforeFirst(); } + + this.currentSlide_.hide(); } + nextSlide.show(); callback.call(this, nextSlide); } From ff3c2d066a48676b38aa6f21b1baf9c59ce14894 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Sun, 29 Jan 2017 11:06:22 +0100 Subject: [PATCH 19/33] Adding touch plugin --- src/full.js | 2 + src/plugins/touch.js | 151 +++++++++++++++++++++++++++++++++++ src/utils/mobile-detector.js | 64 +++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 src/plugins/touch.js create mode 100644 src/utils/mobile-detector.js diff --git a/src/full.js b/src/full.js index 20009bf..593d7c0 100644 --- a/src/full.js +++ b/src/full.js @@ -1,6 +1,8 @@ import WebSlides from './modules/webslides'; import Scroll from './plugins/scroll'; +import Touch from './plugins/touch'; WebSlides.registerPlugin('Scroll', Scroll); +WebSlides.registerPlugin('Touch', Touch); window.WebSlides = WebSlides; diff --git a/src/plugins/touch.js b/src/plugins/touch.js new file mode 100644 index 0000000..eeaea10 --- /dev/null +++ b/src/plugins/touch.js @@ -0,0 +1,151 @@ +import MobileDetector from '../utils/mobile-detector'; + +const EVENTS = { + touch: { + START: 'touchstart', + MOVE: 'touchmove', + END: 'touchend' + }, + pointer: { + START: 'pointerdown', + MOVE: 'pointermove', + END: 'pointerup' + } +}; + +const SLIDE_OFFSET = 50; + +export default class Touch { + /** + * @param {WebSlides} wsInstance The WebSlides instance + */ + constructor(wsInstance) { + /** + * @type {WebSlides} + * @private + */ + this.ws_ = wsInstance; + + /** + * Start position for the X coord. + * @type {number} + * @private + */ + this.startX_ = 0; + + /** + * Start position for the Y coord. + * @type {number} + * @private + */ + this.startY_ = 0; + + /** + * Start position for the X coord. + * @type {number} + * @private + */ + this.endX_ = 0; + + /** + * Start position for the Y coord. + * @type {number} + * @private + */ + this.endY_ = 0; + + /** + * Whether is enabled or not. Only enabled for touch devices. + * @type {boolean} + * @private + */ + this.isEnabled = false; + + let events; + + if (MobileDetector.isAny()) { + // Likely IE + if (window.PointerEvent && ( + MobileDetector.isWindows() || MobileDetector.isWindowsPhone())) { + events = EVENTS.pointer; + } else { + events = EVENTS.touch; + } + + this.isEnabled = true; + document.addEventListener(events.START, this.onStart_.bind(this), false); + document.addEventListener(events.MOVE, this.onMove_.bind(this), false); + document.addEventListener(events.MOVE, this.onMove_.bind(this), false); + document.addEventListener(events.END, this.onStop_.bind(this), false); + } + } + + /** + * Start touch handler. Saves starting points. + * @param event + * @private + */ + onStart_(event) { + const info = Touch.normalizeEventInfo(event); + + this.startX_ = info.x; + this.startY_ = info.y; + this.endX_ = info.x; + this.endY_ = info.y; + } + + /** + * Move touch handler. Saves end points. + * @param event + * @private + */ + onMove_(event) { + const info = Touch.normalizeEventInfo(event); + + this.endX_ = info.x; + this.endY_ = info.y; + } + + /** + * Stop touch handler. Checks if it needs to make any actions. + * @private + */ + onStop_() { + const diffX = this.startX_ - this.endX_; + const diffY = this.startY_ - this.endY_; + + // It's an horizontal drag + if (Math.abs(diffX) > Math.abs(diffY)) { + if(diffX < -SLIDE_OFFSET) { + this.ws_.goPrev(); + } else if(diffX > SLIDE_OFFSET) { + this.ws_.goNext(); + } + } + } + + /** + * Normalizes an event to deal with differences between PointerEvent and + * TouchEvent. + * @param event + * @return {*} + */ + static normalizeEventInfo(event) { + let x; + let y; + let touchEvent = { pageX : 0, pageY : 0}; + + if (typeof event.changedTouches !== 'undefined'){ + touchEvent = event.changedTouches[0]; + } + else if (typeof event.originalEvent !== 'undefined' && + typeof event.originalEvent.changedTouches !== 'undefined'){ + touchEvent = event.originalEvent.changedTouches[0]; + } + + x = event.offsetX || event.layerX || touchEvent.pageX; + y = event.offsetY || event.layerY || touchEvent.pageY; + + return { x, y }; + } +}; diff --git a/src/utils/mobile-detector.js b/src/utils/mobile-detector.js new file mode 100644 index 0000000..83d403f --- /dev/null +++ b/src/utils/mobile-detector.js @@ -0,0 +1,64 @@ +const UA = window.navigator.userAgent; + +export default class MobileDetector { + /** + * Whether the device is Android or not. + * @return {Boolean} + */ + static isAndroid() { + return !!UA.match(/Android/i); + } + + /** + * Whether the device is BlackBerry or not. + * @return {Boolean} + */ + static isBlackBerry() { + return !!UA.match(/BlackBerry/i); + } + + /** + * Whether the device is iOS or not. + * @return {Boolean} + */ + static isiOS() { + return !!UA.match(/iPhone/i); + } + + /** + * Whether the device is Opera or not. + * @return {Boolean} + */ + static isOpera() { + return !!UA.match(/Opera Mini/i); + } + + /** + * Whether the device is Windows or not. + * @return {Boolean} + */ + static isWindows() { + return !!UA.match(/IEMobile/i); + } + + /** + * Whether the device is Windows Phone or not. + * @return {Boolean} + */ + static isWindowsPhone() { + return !!UA.match(/Windows Phone/i); + } + + /** + * Whether the device is any mobile device or not. + * @return {Boolean} + */ + static isAny() { + return MobileDetector.isAndroid() || + MobileDetector.isBlackBerry() || + MobileDetector.isiOS() || + MobileDetector.isOpera() || + MobileDetector.isWindows() || + MobileDetector.isWindowsPhone(); + } +} From c5133158adf4de7deb52c45280421d7bda79cb1e Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 30 Jan 2017 15:47:17 +0100 Subject: [PATCH 20/33] Adding scroll to plugin --- src/modules/webslides.js | 2 +- src/plugins/scroll.js | 50 ++++++++++++++++++++++++++++++++++++++++ src/utils/scroll-to.js | 2 +- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/modules/webslides.js b/src/modules/webslides.js index f2e5a03..4c00aef 100644 --- a/src/modules/webslides.js +++ b/src/modules/webslides.js @@ -185,7 +185,7 @@ export default class WebSlides { } DOM.unlockScroll(); - callback.call(this, nextSlide); + setTimeout(() => { callback.call(this, nextSlide); }, 150); }); } diff --git a/src/plugins/scroll.js b/src/plugins/scroll.js index e69de29..caa6ee2 100644 --- a/src/plugins/scroll.js +++ b/src/plugins/scroll.js @@ -0,0 +1,50 @@ +import ScrollHelper from '../utils/scroll-to'; + +const MIN_WHEEL_DELTA = 40; + +export default class Scroll { + /** + * Scroll handler for the WebSlides. + * @param {WebSlides} wsInstance The WebSlides instance + */ + constructor(wsInstance) { + /** + * @type {WebSlides} + * @private + */ + this.ws_ = wsInstance; + + this.scrollContainer_ = ScrollHelper.getScrollableContainer(); + this.isGoingUp_ = false; + + if (this.ws_.isVertical) { + this.scrollContainer_.addEventListener( + 'wheel', this.onMouseWheel_.bind(this)); + } + } + + /** + * Reacts to the wheel event. Detects whether is going up or down and decides + * if it needs to move the slide based on the amount of delta. + * @param {WheelEvent} event The Wheel Event. + * @private + */ + onMouseWheel_(event) { + if (this.ws_.isMoving) { + return; + } + + const { deltaY: wheelDelta } = event; + this.isGoingUp_ = wheelDelta < 0; + + if (Math.abs(wheelDelta) >= MIN_WHEEL_DELTA) { + if (this.isGoingUp_) { + this.ws_.goPrev(); + } else { + this.ws_.goNext(); + } + + event.preventDefault(); + } + } +}; diff --git a/src/utils/scroll-to.js b/src/utils/scroll-to.js index 7a49d04..edec499 100644 --- a/src/utils/scroll-to.js +++ b/src/utils/scroll-to.js @@ -1,6 +1,6 @@ import Easings from './easing'; -let SCROLLABLE_CONTAINER; +let SCROLLABLE_CONTAINER = getScrollableContainer(); /** * Returns the correct DOM element to be used for scrolling the From 0ce1d02d8ba892e046033774897aaeaf52768a09 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 30 Jan 2017 15:55:30 +0100 Subject: [PATCH 21/33] Baseline to plugin --- src/dev.js | 10 +++++ src/plugins/grid.js | 44 ++++++++++++++++++ static/css/base.css | 98 +++++++++++++++++++---------------------- webpack.config.babel.js | 3 +- 4 files changed, 102 insertions(+), 53 deletions(-) create mode 100644 src/dev.js create mode 100644 src/plugins/grid.js diff --git a/src/dev.js b/src/dev.js new file mode 100644 index 0000000..567cbed --- /dev/null +++ b/src/dev.js @@ -0,0 +1,10 @@ +import WebSlides from './modules/webslides'; +import Scroll from './plugins/scroll'; +import Touch from './plugins/touch'; +import Grid from './plugins/grid'; + +WebSlides.registerPlugin('Scroll', Scroll); +WebSlides.registerPlugin('Touch', Touch); +WebSlides.registerPlugin('Grid', Grid); + +window.WebSlides = WebSlides; diff --git a/src/plugins/grid.js b/src/plugins/grid.js new file mode 100644 index 0000000..c6e5989 --- /dev/null +++ b/src/plugins/grid.js @@ -0,0 +1,44 @@ +import Keys from '../utils/keys'; + +export default class Keyboard { + /** + * Grid plugin that shows a grid on top of the WebSlides for easy prototyping. + * @param {WebSlides} wsInstance The WebSlides instance + */ + constructor(wsInstance) { + /** + * @type {WebSlides} + * @private + */ + this.ws_ = wsInstance; + + const CSS = `body.baseline { + background: url(../images/baseline.png) left top .8rem/.8rem; + }`; + const head = document.head || document.getElementsByTagName('head')[0]; + const style = document.createElement('style'); + + style.type = 'text/css'; + + if (style.styleSheet){ + style.styleSheet.cssText = CSS; + } else { + style.appendChild(document.createTextNode(CSS)); + } + + head.appendChild(style); + + document.addEventListener('keydown', this.onKeyPress_.bind(this), false); + } + + /** + * Reacts to the keydown event. It reacts to ENTER key to toggle the class. + * @param {KeyboardEvent} event The key event. + * @private + */ + onKeyPress_(event) { + if (event.which === Keys.ENTER) { + document.body.toggleClass('baseline'); + } + } +} diff --git a/static/css/base.css b/static/css/base.css index e929c82..bf55974 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -3,7 +3,7 @@ App: WebSlides Version: 0.1 Date: 2017-01-08 - Description: A simple and versatile framework for building HTML presentations, landings, and portfolios. + Description: A simple and versatile framework for building HTML presentations, landings, and portfolios. Author: José Luis Antúnez Author URI: http://twitter.com/jlantunez License: The MIT License (MIT) @@ -23,12 +23,12 @@ 2.3 San Francisco Font (Apple) 3. Header & Footer 3.1 Logo - 4. Navigation + 4. Navigation 4.1 Navbars 4.2 Tabs 5. SLIDES (vertically and horizontally centered) - 5.1 Mini container & Alignment - 5.2 Counter / Navigation Slides + 5.1 Mini container & Alignment + 5.2 Counter / Navigation Slides 5.3 Background Images/Video 6. Magic blocks = .flexblock (Flexible blocks with auto-fill and equal height). 6.1 .flexblock.features @@ -57,11 +57,11 @@ 0. CSS Reset & Normalize =========================================== */ -html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, bbbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { border: 0; font-size: 100%; font: inherit; vertical-align: baseline; margin: 0; padding: 0 } +html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, bbbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { border: 0; font-size: 100%; font: inherit; vertical-align: baseline; margin: 0; padding: 0 } -article, aside, details, figcaption, figure, footer, header, main, menu, nav, section, summary { +article, aside, details, figcaption, figure, footer, header, main, menu, nav, section, summary { display: block; -} +} body { line-height: 1; } @@ -216,7 +216,7 @@ ins { text-decoration: none; padding: 0 4px; text-shadow: none; - + } ::-moz-selection { @@ -292,7 +292,7 @@ dd { /*=== Clearing === */ -.clear:before, .clear:after, header:before, header:after, main:before, main:after, .wrap:before, .wrap:after, group:before, group:after, section:before, section:after, aside:before, aside:after,footer:before, footer:after{ content: ""; display: table; } +.clear:before, .clear:after, header:before, header:after, main:before, main:after, .wrap:before, .wrap:after, group:before, group:after, section:before, section:after, aside:before, aside:after,footer:before, footer:after{ content: ""; display: table; } .clear:after, header:after, main:after, .wrap:after, group:after, section:after, aside:after, footer:after { clear: both; } /*========================================= @@ -303,12 +303,6 @@ body { overflow-x: hidden; } -/* == Prototype faster - Vertical rhythm -To show the grid/baseline.png, press ENTER key on keyboard == */ - -body.baseline { - background: url(../images/baseline.png) left top .8rem/.8rem; -} /* #webslides.vertical {cursor: s-resize; } */ @@ -474,8 +468,8 @@ pre code { padding: 0; } -/*=== 1.2 Animations ================ -Just 3 basic animations: +/*=== 1.2 Animations ================ +Just 3 basic animations: .fadeIn, .fadeInUp, .zoomIn. https://github.com/daneden/animate.css*/ @@ -720,7 +714,7 @@ Auto-fill & Equal height === */ -webkit-flex-direction: column; flex-direction: column; -webkit-transition: .3s; - transition: .3s; + transition: .3s; padding: 2.4rem; } @@ -913,7 +907,7 @@ h1+img,h2+img,h3+img { font-size: 2.4rem; line-height: 4rem; } -/*========================================= +/*========================================= 2.1. Headings with background =========================================== */ @@ -927,8 +921,8 @@ h1 [class*="bg-"],h2 [class*="bg-"],h3 [class*="bg-"] { padding: .4rem .8rem; } -/*========================================= -2.2. Typography Classes = .text- +/*========================================= +2.2. Typography Classes = .text- =========================================== */ .text-intro,[class*="content-"] p { @@ -952,7 +946,7 @@ padding: .4rem .8rem; letter-spacing: 1.6rem; } } -/* -- Subtitle (Before h1, h2) -- +/* -- Subtitle (Before h1, h2) -- p.subtitle + h1/h2 */ p.text-subtitle { @@ -1086,13 +1080,13 @@ padding-top: 1.4rem; margin-top: .8rem; } -@media (min-width: 1024px) { +@media (min-width: 1024px) { [class*="text-pull"] { margin-right: -4rem; margin-left: -4rem; } } -@media (min-width: 568px) { +@media (min-width: 568px) { [class*="text-pull-"] { width: 32rem; } @@ -1113,8 +1107,8 @@ margin-right: 2.4rem; } -/*========================================= -2.1. San Francisco Font (Apple's new font) +/*========================================= +2.1. San Francisco Font (Apple's new font) =========================================== */ .text-apple,.bg-apple { @@ -1252,7 +1246,7 @@ nav ul { display: flex; -webkit-flex-wrap: wrap; flex-wrap: wrap; - /*====align left====*/ + /*====align left====*/ justify-content: flex-start; /* ==== align center ====*/ /*justify-content: center; */ @@ -1387,13 +1381,13 @@ ul.tabs li.current { /*============================================ 5. SLIDES (Full Screen) -Vertically and horizontally centered +Vertically and horizontally centered ============================================== */ -/* Fade transition to all slides. +/* Fade transition to all slides. * = All HTML elements will have those styles.*/ -section * { +section * { -webkit-animation: fadeIn 0.3s ease-in-out; animation: fadeIn 0.3s ease-in-out; } @@ -1430,8 +1424,8 @@ section, .slide { /*slide no padding (full card, .embed> youtube video...) */ .fullscreen { padding: 0; -/* Fixed/Visible header? -padding:8.2rem 0 0 0; +/* Fixed/Visible header? +padding:8.2rem 0 0 0; */ } @@ -1447,7 +1441,7 @@ padding:8.2rem 0 0 0; } -/*== 5.1. Mini container width:50% (600px) +/*== 5.1. Mini container width:50% (600px) .wrap:1200px; / Aligned items [class*="content-"]=== */ [class*="content-"] { @@ -1482,7 +1476,7 @@ padding:8.2rem 0 0 0; float: right; } [class*="content-"] + [class*="content-"] { - padding-left:2.4rem; + padding-left:2.4rem; margin-bottom: 4.8rem; } [class*="content-"] + [class*="size-"] { @@ -1494,12 +1488,12 @@ clear:both; [class*="content-"]:after { content: ""; display: table; -} +} [class*="content-"]:after { clear: both; } -} +} /* === 5.2 Counter / Navigation Slides === */ #navigation { @@ -1567,7 +1561,7 @@ background: url('../images/swipe.svg') no-repeat center top; background-size: 4.8rem; -webkit-animation: fadeIn 6s; animation: fadeIn 6s; -} +} #navigation a, #counter {display: none; } } @@ -1597,7 +1591,7 @@ background-position: bottom; background-size: cover; } -/*fullscreen video +/*fullscreen video