mirror of
https://github.com/webslides/WebSlides.git
synced 2025-09-09 20:50:39 +02:00
Slides index (aka zoom) merged into dev #73
This commit is contained in:
@@ -5,7 +5,8 @@ import scrollTo from '../utils/scroll-to';
|
||||
|
||||
const CLASSES = {
|
||||
VERTICAL: 'vertical',
|
||||
READY: 'ws-ready'
|
||||
READY: 'ws-ready',
|
||||
DISABLED: 'disabled'
|
||||
};
|
||||
|
||||
// Default plugins
|
||||
@@ -19,7 +20,8 @@ const PLUGINS = {
|
||||
'scroll': Plugins.Scroll,
|
||||
'touch': Plugins.Touch,
|
||||
'video': Plugins.Video,
|
||||
'youtube': Plugins.YouTube
|
||||
'youtube': Plugins.YouTube,
|
||||
'zoom': Plugins.Zoom
|
||||
};
|
||||
|
||||
|
||||
@@ -386,6 +388,35 @@ export default class WebSlides {
|
||||
this.goToSlide(slideNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles zoom
|
||||
*/
|
||||
toggleZoom() {
|
||||
this.plugins.zoom.toggleZoom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the webslides element adding a class "disabled"
|
||||
*/
|
||||
disable() {
|
||||
this.el.classList.add(CLASSES.DISABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the webslides element removing a class "disabled"
|
||||
*/
|
||||
enable() {
|
||||
this.el.classList.remove(CLASSES.DISABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if it is disabled
|
||||
* @return {boolean}
|
||||
*/
|
||||
isDisabled() {
|
||||
return this.el.classList.contains(CLASSES.DISABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a plugin to be loaded when the instance is created. It allows
|
||||
* (on purpose) to replace default plugins.
|
||||
|
@@ -29,7 +29,7 @@ export default class Keyboard {
|
||||
let method;
|
||||
let argument;
|
||||
|
||||
if (DOM.isFocusableElement()) {
|
||||
if (DOM.isFocusableElement() || this.ws_.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -48,7 +48,7 @@ export default class Navigation {
|
||||
* Counter Element.
|
||||
* @type {Element}
|
||||
*/
|
||||
this.counter = DOM.createNode('span', ELEMENT_ID.COUNTER);
|
||||
this.counter = Navigation.createCounter(ELEMENT_ID.COUNTER);
|
||||
/**
|
||||
* @type {WebSlides}
|
||||
* @private
|
||||
@@ -72,6 +72,7 @@ export default class Navigation {
|
||||
'ws:slide-change', this.onSlideChanged_.bind(this));
|
||||
this.next.addEventListener('click', this.onButtonClicked_.bind(this));
|
||||
this.prev.addEventListener('click', this.onButtonClicked_.bind(this));
|
||||
this.counter.addEventListener('click', this.onButtonClicked_.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +81,7 @@ export default class Navigation {
|
||||
* @param {string|number} max Max slide number.
|
||||
*/
|
||||
updateCounter(current, max) {
|
||||
this.counter.textContent = `${current} / ${max}`;
|
||||
this.counter.childNodes[0].textContent = `${current} / ${max}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,6 +98,21 @@ export default class Navigation {
|
||||
return arrow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the navigation counter.
|
||||
* @param {!String} id Desired ID for the counter.
|
||||
* @return {Element} The arrow element.
|
||||
*/
|
||||
static createCounter(id) {
|
||||
const counter = DOM.createNode('span', id);
|
||||
const link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.title = 'View all slides';
|
||||
counter.appendChild(link);
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide Change event handler. Will update the text on the navigation.
|
||||
* @param {CustomEvent} event
|
||||
@@ -115,8 +131,10 @@ export default class Navigation {
|
||||
event.preventDefault();
|
||||
if (event.target === this.next) {
|
||||
this.ws_.goNext();
|
||||
} else {
|
||||
} else if (event.target === this.prev) {
|
||||
this.ws_.goPrev();
|
||||
} else {
|
||||
this.ws_.toggleZoom();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import Scroll from './scroll';
|
||||
import Touch from './touch';
|
||||
import Video from './video';
|
||||
import YouTube from './youtube';
|
||||
import Zoom from './zoom';
|
||||
|
||||
export default {
|
||||
AutoSlide,
|
||||
@@ -19,5 +20,6 @@ export default {
|
||||
Scroll,
|
||||
Touch,
|
||||
Video,
|
||||
YouTube
|
||||
YouTube,
|
||||
Zoom
|
||||
};
|
||||
|
@@ -76,6 +76,10 @@ export default class Scroll {
|
||||
* @private
|
||||
*/
|
||||
onMouseWheel_(event) {
|
||||
if (this.ws_.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.ws_.isMoving || this.timeout_) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
|
@@ -63,6 +63,27 @@ export default class Touch {
|
||||
*/
|
||||
this.isEnabled = false;
|
||||
|
||||
/**
|
||||
* Whether is a gesture or not.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isGesture = false;
|
||||
|
||||
/**
|
||||
* Stores start touch event (x, y).
|
||||
* @type {array}
|
||||
* @private
|
||||
*/
|
||||
this.startTouches = [];
|
||||
|
||||
/**
|
||||
* Stores end touch event (x, y).
|
||||
* @type {array}
|
||||
* @private
|
||||
*/
|
||||
this.endTouches = [];
|
||||
|
||||
let events;
|
||||
|
||||
if (MobileDetector.isAny()) {
|
||||
@@ -87,12 +108,22 @@ export default class Touch {
|
||||
* @private
|
||||
*/
|
||||
onStart_(event) {
|
||||
if (this.ws_.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const info = Touch.normalizeEventInfo(event);
|
||||
|
||||
this.startX_ = info.x;
|
||||
this.startY_ = info.y;
|
||||
this.endX_ = info.x;
|
||||
this.endY_ = info.y;
|
||||
if (event.touches.length == 1) {
|
||||
this.startX_ = info.x;
|
||||
this.startY_ = info.y;
|
||||
this.endX_ = info.x;
|
||||
this.endY_ = info.y;
|
||||
} else if (event.touches.length > 1) {
|
||||
this.startTouches = this.getTouchCoorinates(event);
|
||||
this.endTouches = this.startTouches;
|
||||
this.isGesture = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,10 +132,18 @@ export default class Touch {
|
||||
* @private
|
||||
*/
|
||||
onMove_(event) {
|
||||
if (this.ws_.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const info = Touch.normalizeEventInfo(event);
|
||||
|
||||
this.endX_ = info.x;
|
||||
this.endY_ = info.y;
|
||||
if (this.isGesture) {
|
||||
this.endTouches = this.getTouchCoorinates(event);
|
||||
} else {
|
||||
this.endX_ = info.x;
|
||||
this.endY_ = info.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,19 +151,49 @@ export default class Touch {
|
||||
* @private
|
||||
*/
|
||||
onStop_() {
|
||||
const diffX = this.startX_ - this.endX_;
|
||||
const diffY = this.startY_ - this.endY_;
|
||||
if (this.ws_.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It's an horizontal drag
|
||||
if (Math.abs(diffX) > Math.abs(diffY)) {
|
||||
if (diffX < -this.ws_.options.slideOffset) {
|
||||
this.ws_.goPrev();
|
||||
} else if(diffX > this.ws_.options.slideOffset) {
|
||||
this.ws_.goNext();
|
||||
if (this.isGesture) {
|
||||
const startDistance = Math.sqrt(
|
||||
Math.pow(this.startTouches[0].x - this.startTouches[1].x, 2) +
|
||||
Math.pow(this.startTouches[0].y - this.startTouches[1].y, 2)
|
||||
);
|
||||
const endDistance = Math.sqrt(
|
||||
Math.pow(this.endTouches[0].x - this.endTouches[1].x, 2) +
|
||||
Math.pow(this.endTouches[0].y - this.endTouches[1].y, 2)
|
||||
);
|
||||
if (startDistance > endDistance) {
|
||||
// Pinch gesture
|
||||
this.ws_.toggleZoom();
|
||||
}
|
||||
this.isGesture = false;
|
||||
} else {
|
||||
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 < -this.ws_.options.slideOffset) {
|
||||
this.ws_.goPrev();
|
||||
} else if(diffX > this.ws_.options.slideOffset) {
|
||||
this.ws_.goNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get X,Y coordinates from touchs pointers
|
||||
* @param {Event} event
|
||||
* @return {array}
|
||||
*/
|
||||
getTouchCoorinates(event) {
|
||||
return [{x: event.touches[0].clientX, y: event.touches[0].clientY},
|
||||
{x: event.touches[1].clientX, y: event.touches[1].clientY}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an event to deal with differences between PointerEvent and
|
||||
* TouchEvent.
|
||||
|
205
src/js/plugins/zoom.js
Normal file
205
src/js/plugins/zoom.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import DOM from '../utils/dom';
|
||||
import Keys from '../utils/keys';
|
||||
import Slide from '../modules/slide';
|
||||
|
||||
|
||||
const CLASSES = {
|
||||
ZOOM: 'grid',
|
||||
DIV: 'column',
|
||||
WRAP: 'wrap-zoom'
|
||||
};
|
||||
|
||||
const ID = 'webslides-zoomed';
|
||||
|
||||
/**
|
||||
* Zoom plugin.
|
||||
*/
|
||||
export default class Zoom {
|
||||
/**
|
||||
* @param {WebSlides} wsInstance The WebSlides instance
|
||||
* @constructor
|
||||
*/
|
||||
constructor(wsInstance) {
|
||||
/**
|
||||
* @type {WebSlides}
|
||||
* @private
|
||||
*/
|
||||
this.ws_ = wsInstance;
|
||||
|
||||
/**
|
||||
* @type {WebSlides}
|
||||
* @private
|
||||
*/
|
||||
this.zws_ = {};
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isZoomed_ = false;
|
||||
|
||||
this.preBuildZoom_();
|
||||
document.body.addEventListener('keydown', this.onKeyDown.bind(this));
|
||||
window.addEventListener('resize', this.onWindowResize.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* On key down handler. Will decide if Zoom in or out
|
||||
* @param {Event} event Key down event
|
||||
*/
|
||||
onKeyDown(event) {
|
||||
if ( !this.isZoomed_ && Keys.MINUS.includes( event.which ) ) {
|
||||
this.zoomIn();
|
||||
} else if ( this.isZoomed_ &&
|
||||
(Keys.PLUS.includes( event.which ) || event.which == Keys.ESCAPE ) ) {
|
||||
this.zoomOut();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare zoom structure, scales the slides and uses a grid layout
|
||||
* to show them
|
||||
*/
|
||||
preBuildZoom_() {
|
||||
// Clone #webslides element
|
||||
this.zws_.el = this.ws_.el.cloneNode();
|
||||
this.zws_.el.id = ID;
|
||||
this.zws_.el.className = CLASSES.ZOOM;
|
||||
|
||||
this.zws_.el.addEventListener('click', () => this.toggleZoom());
|
||||
|
||||
// Clone the slides
|
||||
this.zws_.slides = [].map.call(this.ws_.slides,
|
||||
(slide, i) => {
|
||||
const s_ = slide.el.cloneNode(true);
|
||||
this.zws_.el.appendChild(s_);
|
||||
return new Slide(s_, i);
|
||||
});
|
||||
DOM.hide(this.zws_.el);
|
||||
DOM.after(this.zws_.el, this.ws_.el);
|
||||
|
||||
// Creates the container for each slide
|
||||
this.zws_.slides.forEach( elem => this.createSlideBlock_(elem));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a block structure around the slide
|
||||
* @param {Element} elem slide element
|
||||
*/
|
||||
createSlideBlock_(elem) {
|
||||
// Wraps the slide around a container
|
||||
const wrap = DOM.wrap(elem.el, 'div');
|
||||
wrap.className = CLASSES.WRAP;
|
||||
// Slide container, need due to flexbox styles
|
||||
const div = DOM.wrap(wrap, 'div');
|
||||
div.className = CLASSES.DIV;
|
||||
// Adding some layer for controling click events
|
||||
const divLayer = document.createElement('div');
|
||||
divLayer.className = 'zoom-layer';
|
||||
divLayer.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
this.zoomOut();
|
||||
this.ws_.goToSlide(elem.i);
|
||||
});
|
||||
wrap.appendChild(divLayer);
|
||||
// Slide number
|
||||
const slideNumber = document.createElement('span');
|
||||
slideNumber.className = 'slide-number';
|
||||
slideNumber.textContent = `${elem.i+1}`;
|
||||
div.appendChild(slideNumber);
|
||||
|
||||
this.setSizes_(div, wrap, elem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets layers size
|
||||
* @param {Element} div flexbox element
|
||||
* @param {Element} wrap wrapping element
|
||||
* @param {Element} elem slide element
|
||||
*/
|
||||
setSizes_(div, wrap, elem) {
|
||||
// Calculates the margins in relation to window width
|
||||
const divCSS = window.getComputedStyle(div);
|
||||
const marginW = DOM.parseSize(divCSS.paddingLeft)
|
||||
+ DOM.parseSize(divCSS.paddingRight);
|
||||
const marginH = DOM.parseSize(divCSS.paddingTop)
|
||||
+ DOM.parseSize(divCSS.paddingBottom);
|
||||
|
||||
// Sets element size: window size - relative margins
|
||||
const scale = divCSS.width.includes('%') ?
|
||||
100 / DOM.parseSize(divCSS.width) :
|
||||
window.innerWidth / DOM.parseSize(divCSS.width);
|
||||
if (scale == 1) {
|
||||
// If the scale is 100% means it is mobile
|
||||
const wsW = this.ws_.el.clientWidth;
|
||||
elem.el.style.width = `${(wsW - marginW) * 2}px`;
|
||||
elem.el.style.height = `${(wsW - marginH) * 1.5}px`;
|
||||
elem.el.style.minHeight = scale == 1? 'auto' : '';
|
||||
// Because of flexbox, wrap height is required
|
||||
wrap.style.height = `${(wsW - marginH) * 1.5 / 2}px`;
|
||||
} else {
|
||||
elem.el.style.width = `${window.innerWidth - marginW * scale}px`;
|
||||
elem.el.style.height = `${window.innerHeight - marginH * scale}px`;
|
||||
// Because of flexbox, wrap height is required
|
||||
wrap.style.height = `${window.innerHeight / scale}px`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles zoom
|
||||
*/
|
||||
toggleZoom() {
|
||||
if (this.isZoomed_) {
|
||||
this.zoomOut();
|
||||
} else {
|
||||
this.zoomIn();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom In the slider, scales the slides and uses a grid layout to show them
|
||||
*/
|
||||
zoomIn() {
|
||||
this.ws_.el.classList.add('zooming', 'in');
|
||||
this.zws_.el.classList.add('zooming', 'in');
|
||||
DOM.show(this.zws_.el);
|
||||
setTimeout(() => {
|
||||
this.ws_.el.classList.remove('zooming', 'in');
|
||||
this.zws_.el.classList.remove('zooming', 'in');
|
||||
this.ws_.disable();
|
||||
}, 400);
|
||||
this.isZoomed_ = true;
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom Out the slider, remove scale from the slides
|
||||
*/
|
||||
zoomOut() {
|
||||
this.ws_.el.classList.add('zooming', 'out');
|
||||
this.zws_.el.classList.add('zooming', 'out');
|
||||
setTimeout(() => {
|
||||
this.ws_.el.classList.remove('zooming', 'out');
|
||||
this.zws_.el.classList.remove('zooming', 'out');
|
||||
DOM.hide(this.zws_.el);
|
||||
this.ws_.enable();
|
||||
}, 400);
|
||||
this.isZoomed_ = false;
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* When windows resize it is necessary to recalculate layers sizes
|
||||
* @param {Event} ev
|
||||
*/
|
||||
onWindowResize(ev) {
|
||||
if (this.isZoomed_) this.zoomOut();
|
||||
|
||||
this.zws_.slides.forEach( elem => {
|
||||
const wrap = elem.el.parentElement;
|
||||
const div = wrap.parentElement;
|
||||
this.setSizes_(div, wrap, elem);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -127,6 +127,16 @@ export default class DOM {
|
||||
el.style.display = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the element is visible.This is only intended
|
||||
* to be used in conjunction with DOM.hide and DOM.show
|
||||
* @param {Element} el Element to check.
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isVisible(el) {
|
||||
return el.style.display == '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a custom event on the given target.
|
||||
* @param {Element} target The target of the event.
|
||||
@@ -171,4 +181,42 @@ export default class DOM {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the integer value of a style property
|
||||
* @param {string} prop CSS property value
|
||||
* @return {integer} The property without the units
|
||||
*/
|
||||
static parseSize(prop) {
|
||||
return Number( prop.replace( /[^\d\.]/g, '' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a HTML structure arrond a element
|
||||
* @param {Element} elem the element to be wrapped
|
||||
* @param {string} tag the new element tag
|
||||
* @return {Element} the new element
|
||||
*/
|
||||
static wrap(elem, tag) {
|
||||
const wrap = document.createElement(tag);
|
||||
elem.parentElement.insertBefore(wrap, elem);
|
||||
wrap.appendChild(elem);
|
||||
|
||||
return wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts and element after another element
|
||||
* @param {Element} elem the element to be inserted
|
||||
* @param {Element} target the element to be inserted after
|
||||
*/
|
||||
static after(elem, target) {
|
||||
const parent = target.parentNode;
|
||||
|
||||
if (parent.lastChild == target) {
|
||||
parent.appendChild(elem);
|
||||
} else {
|
||||
parent.insertBefore(elem, target.nextSibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,10 @@ const Keys = {
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40
|
||||
DOWN: 40,
|
||||
PLUS: [107, 171],
|
||||
MINUS: [109, 173],
|
||||
ESCAPE: 27
|
||||
};
|
||||
|
||||
export default Keys;
|
||||
|
Reference in New Issue
Block a user