Skip to content

Commit b10f710

Browse files
AndersonDunaihenrikingo
authored andcommitted
Add swipe support for navigation between steps
Also: - Removes the code that allowed navigation by tapping left/right edge of screen. - Actually, this was already removed in this branch... - Removes the code that disabled impress.js on mobile devices - Adds new API call impress().swipe() Refactored for the plugin api from this pull request by @and3rson: impress#496 Manually "cherry picked" from henrikingo@c44fd0f
1 parent 10632c2 commit b10f710

5 files changed

Lines changed: 356 additions & 20 deletions

File tree

‎build.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ buildify()
1010
'src/plugins/navigation/navigation.js',
1111
'src/plugins/rel/rel.js',
1212
'src/plugins/resize/resize.js',
13-
'src/plugins/stop/stop.js'])
13+
'src/plugins/stop/stop.js',
14+
'src/plugins/touch/touch.js'])
1415
.save('js/impress.js');
1516
/*
1617
* Disabled until uglify supports ES6: https://github.com/mishoo/UglifyJS2/issues/448

‎index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ <h1>impress.js<sup>*</sup></h1>
340340
</div>
341341
<script>
342342
if ("ontouchstart" in document.documentElement) {
343-
document.querySelector(".hint").innerHTML = "<p>Tap on the left or right to navigate</p>";
343+
document.querySelector(".hint").innerHTML = "<p>Swipe left or right to navigate</p>";
344344
}
345345
</script>
346346

‎js/impress.js‎

Lines changed: 177 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,21 +111,14 @@
111111

112112
// CHECK SUPPORT
113113
var body = document.body;
114-
115-
var ua = navigator.userAgent.toLowerCase();
116114
var impressSupported =
117115

118116
// Browser should support CSS 3D transtorms
119117
( pfx( "perspective" ) !== null ) &&
120118

121-
// Browser should support `classList` and `dataset` APIs
119+
// And `classList` and `dataset` APIs
122120
( body.classList ) &&
123-
( body.dataset ) &&
124-
125-
// But some mobile devices need to be blacklisted,
126-
// because their CSS 3D support or hardware is not
127-
// good enough to run impress.js properly, sorry...
128-
( ua.search( /(iphone)|(ipod)|(android)/ ) === -1 );
121+
( body.dataset );
129122

130123
if ( !impressSupported ) {
131124

@@ -175,6 +168,7 @@
175168
goto: empty,
176169
prev: empty,
177170
next: empty,
171+
swipe: empty,
178172
tear: empty,
179173
lib: {}
180174
};
@@ -583,6 +577,110 @@
583577
return goto( next, undefined, "next", origEvent );
584578
};
585579

580+
// Swipe for touch devices by @and3rson.
581+
// Below we extend the api to control the animation between the currently
582+
// active step and a presumed next/prev step. See touch plugin for
583+
// an example of using this api.
584+
585+
// Helper function
586+
var interpolate = function( a, b, k ) {
587+
return a + ( b - a ) * k;
588+
};
589+
590+
// Animate a swipe.
591+
//
592+
// Pct is a value between -1.0 and +1.0, designating the current length
593+
// of the swipe.
594+
//
595+
// If pct is negative, swipe towards the next() step, if positive,
596+
// towards the prev() step.
597+
//
598+
// Note that pre-stepleave plugins such as goto can mess with what is a
599+
// next() and prev() step, so we need to trigger the pre-stepleave event
600+
// here, even if a swipe doesn't guarantee that the transition will
601+
// actually happen.
602+
//
603+
// Calling swipe(), with any value of pct, won't in itself cause a
604+
// transition to happen, this is just to animate the swipe. Once the
605+
// transition is committed - such as at a touchend event - caller is
606+
// responsible for also calling prev()/next() as appropriate.
607+
var swipe = function( pct ) {
608+
if ( Math.abs( pct ) > 1 ) {
609+
return;
610+
}
611+
612+
// Prepare & execute the preStepLeave event
613+
var event = { target: activeStep, detail: {} };
614+
event.detail.swipe = pct;
615+
616+
// Will be ignored within swipe animation, but just in case a plugin wants to read this,
617+
// humor them
618+
event.detail.transitionDuration = config.transitionDuration;
619+
var idx; // Needed by jshint
620+
if ( pct < 0 ) {
621+
idx = steps.indexOf( activeStep ) + 1;
622+
event.detail.next = idx < steps.length ? steps[ idx ] : steps[ 0 ];
623+
event.detail.reason = "next";
624+
} else if ( pct > 0 ) {
625+
idx = steps.indexOf( activeStep ) - 1;
626+
event.detail.next = idx >= 0 ? steps[ idx ] : steps[ steps.length - 1 ];
627+
event.detail.reason = "prev";
628+
} else {
629+
630+
// No move
631+
return;
632+
}
633+
if ( execPreStepLeavePlugins( event ) === false ) {
634+
635+
// If a preStepLeave plugin wants to abort the transition, don't animate a swipe
636+
// For stop, this is probably ok. For substep, the plugin it self might want to do
637+
// some animation, but that's not the current implementation.
638+
return false;
639+
}
640+
var nextElement = event.detail.next;
641+
642+
var nextStep = stepsData[ "impress-" + nextElement.id ];
643+
644+
// If the same step is re-selected, force computing window scaling,
645+
var nextScale = nextStep.scale * windowScale;
646+
var k = Math.abs( pct );
647+
648+
var interpolatedStep = {
649+
translate: {
650+
x: interpolate( currentState.translate.x, -nextStep.translate.x, k ),
651+
y: interpolate( currentState.translate.y, -nextStep.translate.y, k ),
652+
z: interpolate( currentState.translate.z, -nextStep.translate.z, k )
653+
},
654+
rotate: {
655+
x: interpolate( currentState.rotate.x, -nextStep.rotate.x, k ),
656+
y: interpolate( currentState.rotate.y, -nextStep.rotate.y, k ),
657+
z: interpolate( currentState.rotate.z, -nextStep.rotate.z, k ),
658+
659+
// Unfortunately there's a discontinuity if rotation order changes. Nothing I
660+
// can do about it?
661+
order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order
662+
},
663+
scale: interpolate( currentState.scale, nextScale, k )
664+
};
665+
666+
css( root, {
667+
668+
// To keep the perspective look similar for different scales
669+
// we need to 'scale' the perspective, too
670+
perspective: config.perspective / interpolatedStep.scale + "px",
671+
transform: scale( interpolatedStep.scale ),
672+
transitionDuration: "0ms",
673+
transitionDelay: "0ms"
674+
} );
675+
676+
css( canvas, {
677+
transform: rotate( interpolatedStep.rotate, true ) +
678+
translate( interpolatedStep.translate ),
679+
transitionDuration: "0ms",
680+
transitionDelay: "0ms"
681+
} );
682+
};
683+
586684
// Teardown impress
587685
// Resets the DOM to the state it was before impress().init() was called.
588686
// (If you called impress(rootId).init() for multiple different rootId's, then you must
@@ -666,6 +764,7 @@
666764
goto: goto,
667765
next: next,
668766
prev: prev,
767+
swipe: swipe,
669768
tear: tear,
670769
lib: lib
671770
} );
@@ -1701,3 +1800,72 @@
17011800

17021801
} )( document, window );
17031802

1803+
1804+
/**
1805+
* Support for swipe and tap on touch devices
1806+
*
1807+
* This plugin implements navigation for plugin devices, via swiping left/right,
1808+
* or tapping on the left/right edges of the screen.
1809+
*
1810+
*
1811+
*
1812+
* Copyright 2015: Andrew Dunai (@and3rson)
1813+
* Modified to a plugin, 2016: Henrik Ingo (@henrikingo)
1814+
*
1815+
* MIT License
1816+
*/
1817+
/* global document, window */
1818+
( function( document, window ) {
1819+
"use strict";
1820+
1821+
// Touch handler to detect swiping left and right based on window size.
1822+
// If the difference in X change is bigger than 1/20 of the screen width,
1823+
// we simply call an appropriate API function to complete the transition.
1824+
var startX = 0;
1825+
var lastX = 0;
1826+
var lastDX = 0;
1827+
var threshold = window.innerWidth / 20;
1828+
1829+
document.addEventListener( "touchstart", function( event ) {
1830+
lastX = startX = event.touches[ 0 ].clientX;
1831+
} );
1832+
1833+
document.addEventListener( "touchmove", function( event ) {
1834+
var x = event.touches[ 0 ].clientX;
1835+
var diff = x - startX;
1836+
1837+
// To be used in touchend
1838+
lastDX = lastX - x;
1839+
lastX = x;
1840+
1841+
window.impress().swipe( diff / window.innerWidth );
1842+
} );
1843+
1844+
document.addEventListener( "touchend", function() {
1845+
var totalDiff = lastX - startX;
1846+
if ( Math.abs( totalDiff ) > window.innerWidth / 5 && ( totalDiff * lastDX ) <= 0 ) {
1847+
if ( totalDiff > window.innerWidth / 5 && lastDX <= 0 ) {
1848+
window.impress().prev();
1849+
} else if ( totalDiff < -window.innerWidth / 5 && lastDX >= 0 ) {
1850+
window.impress().next();
1851+
}
1852+
} else if ( Math.abs( lastDX ) > threshold ) {
1853+
if ( lastDX < -threshold ) {
1854+
window.impress().prev();
1855+
} else if ( lastDX > threshold ) {
1856+
window.impress().next();
1857+
}
1858+
} else {
1859+
1860+
// No movement - move (back) to the current slide
1861+
window.impress().goto( document.querySelector( "#impress .step.active" ) );
1862+
}
1863+
} );
1864+
1865+
document.addEventListener( "touchcancel", function() {
1866+
1867+
// Move (back) to the current slide
1868+
window.impress().goto( document.querySelector( "#impress .step.active" ) );
1869+
} );
1870+
1871+
} )( document, window );

‎src/impress.js‎

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,21 +111,14 @@
111111

112112
// CHECK SUPPORT
113113
var body = document.body;
114-
115-
var ua = navigator.userAgent.toLowerCase();
116114
var impressSupported =
117115

118116
// Browser should support CSS 3D transtorms
119117
( pfx( "perspective" ) !== null ) &&
120118

121-
// Browser should support `classList` and `dataset` APIs
119+
// And `classList` and `dataset` APIs
122120
( body.classList ) &&
123-
( body.dataset ) &&
124-
125-
// But some mobile devices need to be blacklisted,
126-
// because their CSS 3D support or hardware is not
127-
// good enough to run impress.js properly, sorry...
128-
( ua.search( /(iphone)|(ipod)|(android)/ ) === -1 );
121+
( body.dataset );
129122

130123
if ( !impressSupported ) {
131124

@@ -175,6 +168,7 @@
175168
goto: empty,
176169
prev: empty,
177170
next: empty,
171+
swipe: empty,
178172
tear: empty,
179173
lib: {}
180174
};
@@ -583,6 +577,110 @@
583577
return goto( next, undefined, "next", origEvent );
584578
};
585579

580+
// Swipe for touch devices by @and3rson.
581+
// Below we extend the api to control the animation between the currently
582+
// active step and a presumed next/prev step. See touch plugin for
583+
// an example of using this api.
584+
585+
// Helper function
586+
var interpolate = function( a, b, k ) {
587+
return a + ( b - a ) * k;
588+
};
589+
590+
// Animate a swipe.
591+
//
592+
// Pct is a value between -1.0 and +1.0, designating the current length
593+
// of the swipe.
594+
//
595+
// If pct is negative, swipe towards the next() step, if positive,
596+
// towards the prev() step.
597+
//
598+
// Note that pre-stepleave plugins such as goto can mess with what is a
599+
// next() and prev() step, so we need to trigger the pre-stepleave event
600+
// here, even if a swipe doesn't guarantee that the transition will
601+
// actually happen.
602+
//
603+
// Calling swipe(), with any value of pct, won't in itself cause a
604+
// transition to happen, this is just to animate the swipe. Once the
605+
// transition is committed - such as at a touchend event - caller is
606+
// responsible for also calling prev()/next() as appropriate.
607+
var swipe = function( pct ) {
608+
if ( Math.abs( pct ) > 1 ) {
609+
return;
610+
}
611+
612+
// Prepare & execute the preStepLeave event
613+
var event = { target: activeStep, detail: {} };
614+
event.detail.swipe = pct;
615+
616+
// Will be ignored within swipe animation, but just in case a plugin wants to read this,
617+
// humor them
618+
event.detail.transitionDuration = config.transitionDuration;
619+
var idx; // Needed by jshint
620+
if ( pct < 0 ) {
621+
idx = steps.indexOf( activeStep ) + 1;
622+
event.detail.next = idx < steps.length ? steps[ idx ] : steps[ 0 ];
623+
event.detail.reason = "next";
624+
} else if ( pct > 0 ) {
625+
idx = steps.indexOf( activeStep ) - 1;
626+
event.detail.next = idx >= 0 ? steps[ idx ] : steps[ steps.length - 1 ];
627+
event.detail.reason = "prev";
628+
} else {
629+
630+
// No move
631+
return;
632+
}
633+
if ( execPreStepLeavePlugins( event ) === false ) {
634+
635+
// If a preStepLeave plugin wants to abort the transition, don't animate a swipe
636+
// For stop, this is probably ok. For substep, the plugin it self might want to do
637+
// some animation, but that's not the current implementation.
638+
return false;
639+
}
640+
var nextElement = event.detail.next;
641+
642+
var nextStep = stepsData[ "impress-" + nextElement.id ];
643+
644+
// If the same step is re-selected, force computing window scaling,
645+
var nextScale = nextStep.scale * windowScale;
646+
var k = Math.abs( pct );
647+
648+
var interpolatedStep = {
649+
translate: {
650+
x: interpolate( currentState.translate.x, -nextStep.translate.x, k ),
651+
y: interpolate( currentState.translate.y, -nextStep.translate.y, k ),
652+
z: interpolate( currentState.translate.z, -nextStep.translate.z, k )
653+
},
654+
rotate: {
655+
x: interpolate( currentState.rotate.x, -nextStep.rotate.x, k ),
656+
y: interpolate( currentState.rotate.y, -nextStep.rotate.y, k ),
657+
z: interpolate( currentState.rotate.z, -nextStep.rotate.z, k ),
658+
659+
// Unfortunately there's a discontinuity if rotation order changes. Nothing I
660+
// can do about it?
661+
order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order
662+
},
663+
scale: interpolate( currentState.scale, nextScale, k )
664+
};
665+
666+
css( root, {
667+
668+
// To keep the perspective look similar for different scales
669+
// we need to 'scale' the perspective, too
670+
perspective: config.perspective / interpolatedStep.scale + "px",
671+
transform: scale( interpolatedStep.scale ),
672+
transitionDuration: "0ms",
673+
transitionDelay: "0ms"
674+
} );
675+
676+
css( canvas, {
677+
transform: rotate( interpolatedStep.rotate, true ) +
678+
translate( interpolatedStep.translate ),
679+
transitionDuration: "0ms",
680+
transitionDelay: "0ms"
681+
} );
682+
};
683+
586684
// Teardown impress
587685
// Resets the DOM to the state it was before impress().init() was called.
588686
// (If you called impress(rootId).init() for multiple different rootId's, then you must
@@ -666,6 +764,7 @@
666764
goto: goto,
667765
next: next,
668766
prev: prev,
767+
swipe: swipe,
669768
tear: tear,
670769
lib: lib
671770
} );

0 commit comments

Comments
 (0)