break touch interaction into own module

This commit is contained in:
Hakim El Hattab 2020-03-11 08:13:53 +01:00
parent e32f38740c
commit 5a5a5c9a6c
6 changed files with 279 additions and 236 deletions

4
dist/reveal.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -87,6 +87,7 @@ gulp.task('serve', () => {
connect.server({ connect.server({
root: root, root: root,
port: port, port: port,
host: '0.0.0.0',
livereload: true livereload: true
}) })

View File

@ -1,7 +1,5 @@
import { enterFullscreen } from '../utils/util.js'
/** /**
* Handles all reveal.js keyboard interactions. * Reads and writes the URL based on reveal.js' current state.
*/ */
export default class Location { export default class Location {

View File

@ -1,7 +1,5 @@
import { enterFullscreen } from '../utils/util.js'
/** /**
* Handles all reveal.js keyboard interactions. * Handles the display of reveal.js' optional slide number.
*/ */
export default class SlideNumber { export default class SlideNumber {

255
js/controllers/touch.js Normal file
View File

@ -0,0 +1,255 @@
const SWIPE_THRESHOLD = 40;
/**
* Controls all touch interactions and navigations for
* a presentation.
*/
export default class Touch {
constructor( Reveal ) {
this.Reveal = Reveal;
// Holds information about the currently ongoing touch interaction
this.touchStartX = 0;
this.touchStartY = 0;
this.touchStartCount = 0;
this.touchCaptured = false;
this.onPointerDown = this.onPointerDown.bind( this );
this.onPointerMove = this.onPointerMove.bind( this );
this.onPointerUp = this.onPointerUp.bind( this );
this.onTouchStart = this.onTouchStart.bind( this );
this.onTouchMove = this.onTouchMove.bind( this );
this.onTouchEnd = this.onTouchEnd.bind( this );
}
/**
*
*/
bind() {
var revealElement = this.Reveal.getRevealElement();
if( 'onpointerdown' in window ) {
// Use W3C pointer events
revealElement.addEventListener( 'pointerdown', this.onPointerDown, false );
revealElement.addEventListener( 'pointermove', this.onPointerMove, false );
revealElement.addEventListener( 'pointerup', this.onPointerUp, false );
}
else if( window.navigator.msPointerEnabled ) {
// IE 10 uses prefixed version of pointer events
revealElement.addEventListener( 'MSPointerDown', this.onPointerDown, false );
revealElement.addEventListener( 'MSPointerMove', this.onPointerMove, false );
revealElement.addEventListener( 'MSPointerUp', this.onPointerUp, false );
}
else {
// Fall back to touch events
revealElement.addEventListener( 'touchstart', this.onTouchStart, false );
revealElement.addEventListener( 'touchmove', this.onTouchMove, false );
revealElement.addEventListener( 'touchend', this.onTouchEnd, false );
}
}
/**
*
*/
unbind() {
var revealElement = this.Reveal.getRevealElement();
revealElement.removeEventListener( 'pointerdown', this.onPointerDown, false );
revealElement.removeEventListener( 'pointermove', this.onPointerMove, false );
revealElement.removeEventListener( 'pointerup', this.onPointerUp, false );
revealElement.removeEventListener( 'MSPointerDown', this.onPointerDown, false );
revealElement.removeEventListener( 'MSPointerMove', this.onPointerMove, false );
revealElement.removeEventListener( 'MSPointerUp', this.onPointerUp, false );
revealElement.removeEventListener( 'touchstart', this.onTouchStart, false );
revealElement.removeEventListener( 'touchmove', this.onTouchMove, false );
revealElement.removeEventListener( 'touchend', this.onTouchEnd, false );
}
/**
* Checks if the target element prevents the triggering of
* swipe navigation.
*/
isSwipePrevented( target ) {
while( target && typeof target.hasAttribute === 'function' ) {
if( target.hasAttribute( 'data-prevent-swipe' ) ) return true;
target = target.parentNode;
}
return false;
}
/**
* Handler for the 'touchstart' event, enables support for
* swipe and pinch gestures.
*
* @param {object} event
*/
onTouchStart( event ) {
if( this.isSwipePrevented( event.target ) ) return true;
this.touchStartX = event.touches[0].clientX;
this.touchStartY = event.touches[0].clientY;
this.touchStartCount = event.touches.length;
}
/**
* Handler for the 'touchmove' event.
*
* @param {object} event
*/
onTouchMove( event ) {
if( this.isSwipePrevented( event.target ) ) return true;
let config = this.Reveal.getConfig();
// Each touch should only trigger one action
if( !this.touchCaptured ) {
this.Reveal.onUserInput( event );
let currentX = event.touches[0].clientX;
let currentY = event.touches[0].clientY;
// There was only one touch point, look for a swipe
if( event.touches.length === 1 && this.touchStartCount !== 2 ) {
let deltaX = currentX - this.touchStartX,
deltaY = currentY - this.touchStartY;
if( deltaX > SWIPE_THRESHOLD && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
this.touchCaptured = true;
if( config.navigationMode === 'linear' ) {
if( config.rtl ) {
this.Reveal.next();
}
else {
this.Reveal.prev()();
}
}
else {
this.Reveal.left();
}
}
else if( deltaX < -SWIPE_THRESHOLD && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
this.touchCaptured = true;
if( config.navigationMode === 'linear' ) {
if( config.rtl ) {
this.Reveal.prev()();
}
else {
this.Reveal.next();
}
}
else {
this.Reveal.right();
}
}
else if( deltaY > SWIPE_THRESHOLD ) {
this.touchCaptured = true;
if( config.navigationMode === 'linear' ) {
this.Reveal.prev()();
}
else {
this.Reveal.up();
}
}
else if( deltaY < -SWIPE_THRESHOLD ) {
this.touchCaptured = true;
if( config.navigationMode === 'linear' ) {
this.Reveal.next();
}
else {
this.Reveal.down();
}
}
// If we're embedded, only block touch events if they have
// triggered an action
if( config.embedded ) {
if( this.touchCaptured || this.Reveal.isVerticalSlide( currentSlide ) ) {
event.preventDefault();
}
}
// Not embedded? Block them all to avoid needless tossing
// around of the viewport in iOS
else {
event.preventDefault();
}
}
}
// There's a bug with swiping on some Android devices unless
// the default action is always prevented
else if( isAndroid ) {
event.preventDefault();
}
}
/**
* Handler for the 'touchend' event.
*
* @param {object} event
*/
onTouchEnd( event ) {
this.touchCaptured = false;
}
/**
* Convert pointer down to touch start.
*
* @param {object} event
*/
onPointerDown( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
this.onTouchStart( event );
}
}
/**
* Convert pointer move to touch move.
*
* @param {object} event
*/
onPointerMove( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
this.onTouchMove( event );
}
}
/**
* Convert pointer up to touch end.
*
* @param {object} event
*/
onPointerUp( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
this.onTouchEnd( event );
}
}
}

View File

@ -6,8 +6,11 @@ import Overview from './controllers/overview.js'
import Keyboard from './controllers/keyboard.js' import Keyboard from './controllers/keyboard.js'
import Location from './controllers/location.js' import Location from './controllers/location.js'
import Plugins from './controllers/plugins.js' import Plugins from './controllers/plugins.js'
import Touch from './controllers/touch.js'
import Playback from './components/playback.js' import Playback from './components/playback.js'
import defaultConfig from './config.js' import defaultConfig from './config.js'
import { isMobile, isChrome, isAndroid, supportsZoom } from './utils/device.js'
import { colorToRgb, colorBrightness } from './utils/color.js'
import { import {
SLIDES_SELECTOR, SLIDES_SELECTOR,
HORIZONTAL_SLIDES_SELECTOR, HORIZONTAL_SLIDES_SELECTOR,
@ -26,8 +29,6 @@ import {
enterFullscreen, enterFullscreen,
getQueryHash getQueryHash
} from './utils/util.js' } from './utils/util.js'
import { isMobile, isChrome, isAndroid, supportsZoom } from './utils/device.js'
import { colorToRgb, colorBrightness } from './utils/color.js'
/** /**
* reveal.js * reveal.js
@ -71,16 +72,6 @@ export default function( revealElement, options ) {
// The current scale of the presentation (see width/height config) // The current scale of the presentation (see width/height config)
scale = 1, scale = 1,
// CSS transform that is currently applied to the slides container,
// split into two groups
slidesTransform = { layout: '', overview: '' },
// Cached references to DOM elements
dom = {},
// Controller for plugin loading
plugins = new Plugins(),
// Controls loading and playback of slide content // Controls loading and playback of slide content
slideContent = new SlideContent( Reveal ), slideContent = new SlideContent( Reveal ),
@ -102,6 +93,19 @@ export default function( revealElement, options ) {
// Controls the current location/URL // Controls the current location/URL
location = new Location( Reveal ), location = new Location( Reveal ),
// Controller for plugin loading
plugins = new Plugins(),
// Controls touch/swipe navigation for our deck
touch = new Touch( Reveal ),
// CSS transform that is currently applied to the slides container,
// split into two groups
slidesTransform = { layout: '', overview: '' },
// Cached references to DOM elements
dom = {},
// List of asynchronously loaded reveal.js dependencies // List of asynchronously loaded reveal.js dependencies
asyncDependencies = [], asyncDependencies = [],
@ -124,16 +128,7 @@ export default function( revealElement, options ) {
autoSlidePlayer, autoSlidePlayer,
autoSlideTimeout = 0, autoSlideTimeout = 0,
autoSlideStartTime = -1, autoSlideStartTime = -1,
autoSlidePaused = false, autoSlidePaused = false;
// Holds information about the currently ongoing touch input
touch = {
startX: 0,
startY: 0,
startCount: 0,
captured: false,
threshold: 40
};
/** /**
* Starts up the presentation if the client is capable. * Starts up the presentation if the client is capable.
@ -922,24 +917,7 @@ export default function( revealElement, options ) {
window.addEventListener( 'resize', onWindowResize, false ); window.addEventListener( 'resize', onWindowResize, false );
if( config.touch ) { if( config.touch ) {
if( 'onpointerdown' in window ) { touch.bind();
// Use W3C pointer events
dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
}
else if( window.navigator.msPointerEnabled ) {
// IE 10 uses prefixed version of pointer events
dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
}
else {
// Fall back to touch events
dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
}
} }
if( config.keyboard ) { if( config.keyboard ) {
@ -984,23 +962,12 @@ export default function( revealElement, options ) {
eventsAreBound = false; eventsAreBound = false;
touch.unbind();
keyboard.unbind(); keyboard.unbind();
window.removeEventListener( 'hashchange', onWindowHashChange, false ); window.removeEventListener( 'hashchange', onWindowHashChange, false );
window.removeEventListener( 'resize', onWindowResize, false ); window.removeEventListener( 'resize', onWindowResize, false );
dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
dom.pauseOverlay.removeEventListener( 'click', resume, false ); dom.pauseOverlay.removeEventListener( 'click', resume, false );
if ( config.progress && dom.progress ) { if ( config.progress && dom.progress ) {
@ -3121,21 +3088,6 @@ export default function( revealElement, options ) {
} }
/**
* Checks if the target element prevents the triggering of
* swipe navigation.
*/
function isSwipePrevented( target ) {
while( target && typeof target.hasAttribute === 'function' ) {
if( target.hasAttribute( 'data-prevent-swipe' ) ) return true;
target = target.parentNode;
}
return false;
}
// --------------------------------------------------------------------// // --------------------------------------------------------------------//
// ----------------------------- EVENTS -------------------------------// // ----------------------------- EVENTS -------------------------------//
@ -3171,167 +3123,6 @@ export default function( revealElement, options ) {
} }
/**
* Handler for the 'touchstart' event, enables support for
* swipe and pinch gestures.
*
* @param {object} event
*/
function onTouchStart( event ) {
if( isSwipePrevented( event.target ) ) return true;
touch.startX = event.touches[0].clientX;
touch.startY = event.touches[0].clientY;
touch.startCount = event.touches.length;
}
/**
* Handler for the 'touchmove' event.
*
* @param {object} event
*/
function onTouchMove( event ) {
if( isSwipePrevented( event.target ) ) return true;
// Each touch should only trigger one action
if( !touch.captured ) {
onUserInput( event );
let currentX = event.touches[0].clientX;
let currentY = event.touches[0].clientY;
// There was only one touch point, look for a swipe
if( event.touches.length === 1 && touch.startCount !== 2 ) {
let deltaX = currentX - touch.startX,
deltaY = currentY - touch.startY;
if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
touch.captured = true;
if( config.navigationMode === 'linear' ) {
if( config.rtl ) {
navigateNext();
}
else {
navigatePrev();
}
}
else {
navigateLeft();
}
}
else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
touch.captured = true;
if( config.navigationMode === 'linear' ) {
if( config.rtl ) {
navigatePrev();
}
else {
navigateNext();
}
}
else {
navigateRight();
}
}
else if( deltaY > touch.threshold ) {
touch.captured = true;
if( config.navigationMode === 'linear' ) {
navigatePrev();
}
else {
navigateUp();
}
}
else if( deltaY < -touch.threshold ) {
touch.captured = true;
if( config.navigationMode === 'linear' ) {
navigateNext();
}
else {
navigateDown();
}
}
// If we're embedded, only block touch events if they have
// triggered an action
if( config.embedded ) {
if( touch.captured || isVerticalSlide( currentSlide ) ) {
event.preventDefault();
}
}
// Not embedded? Block them all to avoid needless tossing
// around of the viewport in iOS
else {
event.preventDefault();
}
}
}
// There's a bug with swiping on some Android devices unless
// the default action is always prevented
else if( isAndroid ) {
event.preventDefault();
}
}
/**
* Handler for the 'touchend' event.
*
* @param {object} event
*/
function onTouchEnd( event ) {
touch.captured = false;
}
/**
* Convert pointer down to touch start.
*
* @param {object} event
*/
function onPointerDown( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
onTouchStart( event );
}
}
/**
* Convert pointer move to touch move.
*
* @param {object} event
*/
function onPointerMove( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
onTouchMove( event );
}
}
/**
* Convert pointer up to touch end.
*
* @param {object} event
*/
function onPointerUp( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
onTouchEnd( event );
}
}
/** /**
* Handles mouse wheel scrolling, throttled to avoid skipping * Handles mouse wheel scrolling, throttled to avoid skipping
* multiple slides. * multiple slides.