mirror of
https://github.com/webslides/WebSlides.git
synced 2025-08-20 03:41:38 +02:00
Finishing the core bits
This commit is contained in:
25
src/modules/hash.js
Normal file
25
src/modules/hash.js
Normal file
@@ -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}`);
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
|
46
src/modules/slide.js
Normal file
46
src/modules/slide.js
Normal file
@@ -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';
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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';
|
||||
}
|
||||
}
|
||||
|
9
src/utils/easing.js
Normal file
9
src/utils/easing.js
Normal file
@@ -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 };
|
58
src/utils/scroll-to.js
Normal file
58
src/utils/scroll-to.js
Normal file
@@ -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 };
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user