/
githubmirror
/
reveal.js
Обзор
Документация
Войти
/
githubmirror
/
reveal.js
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
js/controllers/touch.js
325 строк
8 KB
Hakim El Hattab
fix overlay button size in mobile
21 май 2026, 22:13
21 май 2026, 22:13
363a90b
Код
Авторство
О чём код?
import { isAndroid } from '../utils/device' import { matches } from '../utils/util' const SWIPE_THRESHOLD = 40; const ZOOM_GESTURE_THRESHOLD = 1.03; /** * 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.activePointers = new Map(); this.onPointerDown = this.onPointerDown.bind( this ); this.onPointerMove = this.onPointerMove.bind( this ); this.onPointerUp = this.onPointerUp.bind( this ); this.onPointerCancel = this.onPointerCancel.bind( this ); this.onTouchStart = this.onTouchStart.bind( this ); this.onTouchMove = this.onTouchMove.bind( this ); this.onTouchEnd = this.onTouchEnd.bind( this ); } /** * */ bind() { let revealElement = this.Reveal.getRevealElement(); if( 'onpointerdown' in window ) { revealElement.addEventListener( 'pointerdown', this.onPointerDown, false ); revealElement.addEventListener( 'pointermove', this.onPointerMove, false ); revealElement.addEventListener( 'pointerup', this.onPointerUp, false ); revealElement.addEventListener( 'pointercancel', this.onPointerCancel, false ); } // Fall back to touch events else { revealElement.addEventListener( 'touchstart', this.onTouchStart, false ); revealElement.addEventListener( 'touchmove', this.onTouchMove, false ); revealElement.addEventListener( 'touchend', this.onTouchEnd, false ); } } /** * */ unbind() { let revealElement = this.Reveal.getRevealElement(); revealElement.removeEventListener( 'pointerdown', this.onPointerDown, false ); revealElement.removeEventListener( 'pointermove', this.onPointerMove, false ); revealElement.removeEventListener( 'pointerup', this.onPointerUp, false ); revealElement.removeEventListener( 'pointercancel', this.onPointerCancel, 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 ) { // Prevent accidental swipes when scrubbing timelines if( matches( target, 'video[controls], audio[controls]' ) ) return true; while( target && typeof target.hasAttribute === 'function' ) { if( target.hasAttribute( 'data-prevent-swipe' ) ) return true; target = target.parentNode; } return false; } /** * Returns true when browser-level zoom is active enough that * we should not trigger reveal.js touch gestures. */ isViewportZoomed() { if( !window.visualViewport || typeof window.visualViewport.scale !== 'number' ) { return false; } const currentScale = window.visualViewport.scale; // Fixed-width viewport configurations may start below 1. // Compare against the smallest observed "resting" scale. if( typeof this.visualViewportBaseScale === 'undefined' ) { this.visualViewportBaseScale = currentScale; } else { this.visualViewportBaseScale = Math.min( this.visualViewportBaseScale, currentScale ); } return ( currentScale / this.visualViewportBaseScale ) > ZOOM_GESTURE_THRESHOLD; } /** * Handler for the 'touchstart' event, enables support for * swipe and pinch gestures. * * @param {object} event */ onTouchStart( event ) { this.touchCaptured = false; // if( this.isViewportZoomed() ) return true; 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.isViewportZoomed() ) return true; 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 availableRoutes = this.Reveal.availableRoutes({ includeFragments: true }); 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 && availableRoutes.up ) { this.touchCaptured = true; if( config.navigationMode === 'linear' ) { this.Reveal.prev(); } else { this.Reveal.up(); } } else if( deltaY < -SWIPE_THRESHOLD && availableRoutes.down ) { 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() ) { 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 ) { // Media playback is only allowed as a direct result of a // user interaction. Some mobile devices do not consider a // 'touchmove' to be a direct user action. If this is the // case, we fall back to starting playback here instead. if( this.touchCaptured && !this.Reveal.slideContent.isAllowedToPlayAudio() ) { this.Reveal.startEmbeddedContent( this.Reveal.getCurrentSlide() ); } this.touchCaptured = false; } /** * Returns all active touch pointers in touch-like format. * * @return {Array} */ getActiveTouches() { return Array.from( this.activePointers.values(), pointer => ({ clientX: pointer.clientX, clientY: pointer.clientY }) ); } /** * Convert pointer down to touch start. * * @param {object} event */ onPointerDown( event ) { if( event.pointerType === "touch" ) { this.activePointers.set( event.pointerId, event ); event.touches = this.getActiveTouches(); this.onTouchStart( event ); } } /** * Convert pointer move to touch move. * * @param {object} event */ onPointerMove( event ) { if( event.pointerType === "touch" ) { this.activePointers.set( event.pointerId, event ); event.touches = this.getActiveTouches(); this.onTouchMove( event ); } } /** * Convert pointer up to touch end. * * @param {object} event */ onPointerUp( event ) { if( event.pointerType === "touch" ) { this.activePointers.delete( event.pointerId ); event.touches = this.getActiveTouches(); this.onTouchEnd( event ); } } /** * Convert pointer cancel to touch end. * * @param {object} event */ onPointerCancel( event ) { if( event.pointerType === "touch" ) { this.activePointers.delete( event.pointerId ); event.touches = this.getActiveTouches(); this.onTouchEnd( event ); } } }