Permalink
Browse files

Add swipe support for navigation between steps

Also:
 - Removes the code that allowed navigation by tapping left/right edge of screen.
 - 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
  • Loading branch information...
AndersonDunai authored and henrikingo committed Aug 13, 2016
1 parent bc0092c commit c44fd0f4c102e98a90ee564b91a5cf4f66cec6c3
Showing with 311 additions and 53 deletions.
  1. +1 −0 build.js
  2. +1 −1 index.html
  3. +159 −26 js/impress.js
  4. +86 −6 src/impress.js
  5. +0 −20 src/plugins/navigation/navigation.js
  6. +64 −0 src/plugins/touch/touch.js
View
@@ -14,6 +14,7 @@ buildify()
'src/plugins/rel/rel.js',
'src/plugins/skip/skip.js',
'src/plugins/stop/stop.js',
'src/plugins/touch/touch.js',
'src/plugins/toolbar/toolbar.js'])
.save('js/impress.js')
.uglify()
View
@@ -355,7 +355,7 @@ <h1>impress.js<sup>*</sup></h1>
</div>
<script>
if ("ontouchstart" in document.documentElement) {
document.querySelector(".hint").innerHTML = "<p>Tap on the left or right to navigate</p>";
document.querySelector(".hint").innerHTML = "<p>Swipe left or right to navigate</p>";
}
</script>
View
@@ -174,12 +174,7 @@
// and `classList` and `dataset` APIs
( body.classList ) &&
( body.dataset ) &&
// but some mobile devices need to be blacklisted,
// because their CSS 3D support or hardware is not
// good enough to run impress.js properly, sorry...
( ua.search(/(iphone)|(ipod)|(android)/) === -1 );
( body.dataset );
if (!impressSupported) {
// we can't be sure that `classList` is supported
@@ -627,6 +622,90 @@
return goto(next, undefined, "next");
};
// Swipe for touch devices by @and3rson.
// Below we extend the api to control the animation between the currently
// active step and a presumed next/prev step. See touch plugin for
// an example of using this api.
// Helper function
var interpolate = function(a, b, k) {
return a + (b - a) * k;
};
// Animate a swipe.
//
// pct is a value between -1.0 and +1.0, designating the current length
// of the swipe.
//
// If pct is negative, swipe towards the next() step, if positive,
// towards the prev() step.
//
// Note that pre-stepleave plugins such as goto can mess with what is a
// next() and prev() step, so we need to trigger the pre-stepleave event
// here, even if a swipe doesn't guarantee that the transition will
// actually happen.
//
// Calling swipe(), with any value of pct, won't in itself cause a
// transition to happen, this is just to animate the swipe. Once the
// transition is committed - such as at a touchend event - caller is
// responsible for also calling prev()/next() as appropriate.
var swipe = function(pct){
if( Math.abs(pct) > 1 ) return;
// Prepare & execute the preStepLeave event
var event = { target: activeStep, detail : {} };
// Will be ignored within swipe animation, but just in case a plugin wants to read this, humor them
event.detail.transitionDuration = config.transitionDuration;
if (pct < 0) {
var idx = steps.indexOf(activeStep) + 1;
event.detail.next = idx < steps.length ? steps[idx] : steps[0];
event.detail.reason = "next";
} else if (pct > 0) {
var idx = steps.indexOf(activeStep) - 1;
event.detail.next = idx >= 0 ? steps[idx] : steps[steps.length - 1];
event.detail.reason = "prev";
} else {
// No move
return;
}
execPreStepLeavePlugins(event);
var nextElement = event.detail.next;
var nextStep = stepsData['impress-' + nextElement.id];
var zoomin = nextStep.scale >= currentState.scale;
// if the same step is re-selected, force computing window scaling,
var nextScale = nextStep.scale * windowScale;
var k = Math.abs(pct);
var interpolatedStep = {
translate: {
x: interpolate(currentState.translate.x, -nextStep.translate.x, k),
y: interpolate(currentState.translate.y, -nextStep.translate.y, k),
z: interpolate(currentState.translate.z, -nextStep.translate.z, k)
},
rotate: {
x: interpolate(currentState.rotate.x, -nextStep.rotate.x, k),
y: interpolate(currentState.rotate.y, -nextStep.rotate.y, k),
z: interpolate(currentState.rotate.z, -nextStep.rotate.z, k)
},
scale: interpolate(currentState.scale, nextScale)
};
css(root, {
// to keep the perspective look similar for different scales
// we need to 'scale' the perspective, too
transform: perspective(config.perspective / interpolatedStep.scale) + scale(interpolatedStep.scale),
transitionDuration: "0ms",
transitionDelay: "0ms"
});
css(canvas, {
transform: rotate(interpolatedStep.rotate, true) + translate(interpolatedStep.translate),
transitionDuration: "0ms",
transitionDelay: "0ms"
});
};
// Adding some useful classes to step elements.
//
// All the steps that have not been shown yet are given `future` class.
@@ -699,6 +778,7 @@
goto: goto,
next: next,
prev: prev,
swipe: swipe,
addPreInitPlugin: addPreInitPlugin,
addPreStepLeavePlugin: addPreStepLeavePlugin
});
@@ -1348,26 +1428,6 @@
}
}, false);
// touch handler to detect taps on the left and right side of the screen
// based on awesome work of @hakimel: https://github.com/hakimel/reveal.js
document.addEventListener("touchstart", function ( event ) {
if (event.touches.length === 1) {
var x = event.touches[0].clientX,
width = window.innerWidth * 0.3,
result = null;
if ( x < width ) {
result = api.prev();
} else if ( x > window.innerWidth - width ) {
result = api.next();
}
if (result) {
event.preventDefault();
}
}
}, false);
// TODO: This was originally defined here, when impress.js was a single file.
// It has nothing to do with key navigation events, but it depends on the
// throttle function defined above. Leaving here for now.
@@ -1542,6 +1602,14 @@
* create a boring presentation where each slide shifts for example 1000px down
* from the previous.
*
* In addition to plain numbers, which are pixel values, it is also possible to
* define relative positions as a multiple of screen height and width, using
* a unit of "h" and "w", respectively, appended to the number.
*
* Example:
*
* <div class="step" data-rel-x="1.5w" data-rel-y="1.5h">
*
* This plugin is a *pre-init plugin*. It is called synchronously from impress.js
* core at the beginning of `impress().init()`. This allows it to process its own
* data attributes first, and possibly alter the data-x, data-y and data-z attributes
@@ -1739,6 +1807,71 @@
})(document, window);
/**
* Support for swipe and tap on touch devices
*
* This plugin implements navigation for plugin devices, via swiping left/right,
* or tapping on the left/right edges of the screen.
*
*
*
* Copyright 2015: Andrew Dunai (@and3rson)
* Modified to a plugin, 2016: Henrik Ingo (@henrikingo)
*
* MIT License
*/
(function ( document, window ) {
'use strict';
// Touch handler to detect swiping left and right based on window size.
// If the difference in X change is bigger than 1/20 of the screen width,
// we simply call an appropriate API function to complete the transition.
var startX = 0;
var lastX = 0;
var lastDX = 0;
var threshold = window.innerWidth / 20;
document.addEventListener('touchstart', function (event) {
lastX = startX = event.touches[0].clientX;
});
document.addEventListener('touchmove', function (event) {
var x = event.touches[0].clientX;
var diff = x - startX;
// To be used in touchend
lastDX = lastX - x;
lastX = x;
impress().swipe( diff / window.innerWidth );
});
document.addEventListener('touchend', function (event) {
var totalDiff = lastX - startX;
if (Math.abs(totalDiff) > window.innerWidth / 5 && (totalDiff * lastDX) <= 0) {
if (totalDiff > window.innerWidth / 5 && lastDX <= 0) {
impress().prev();
} else if (totalDiff < -window.innerWidth / 5 && lastDX >= 0) {
impress().next();
}
} else if (Math.abs(lastDX) > threshold) {
if (lastDX < -threshold) {
impress().prev();
} else if (lastDX > threshold) {
impress().next();
}
} else {
// No movement - move (back) to the current slide
impress().goto(document.querySelector("#impress .step.active"));
}
});
document.addEventListener('touchcancel', function (event) {
// move (back) to the current slide
impress().goto(document.querySelector("#impress .step.active"));
});
})(document, window);
/**
* Toolbar plugin
*
View
@@ -174,12 +174,7 @@
// and `classList` and `dataset` APIs
( body.classList ) &&
( body.dataset ) &&
// but some mobile devices need to be blacklisted,
// because their CSS 3D support or hardware is not
// good enough to run impress.js properly, sorry...
( ua.search(/(iphone)|(ipod)|(android)/) === -1 );
( body.dataset );
if (!impressSupported) {
// we can't be sure that `classList` is supported
@@ -627,6 +622,90 @@
return goto(next, undefined, "next");
};
// Swipe for touch devices by @and3rson.
// Below we extend the api to control the animation between the currently
// active step and a presumed next/prev step. See touch plugin for
// an example of using this api.
// Helper function
var interpolate = function(a, b, k) {
return a + (b - a) * k;
};
// Animate a swipe.
//
// pct is a value between -1.0 and +1.0, designating the current length
// of the swipe.
//
// If pct is negative, swipe towards the next() step, if positive,
// towards the prev() step.
//
// Note that pre-stepleave plugins such as goto can mess with what is a
// next() and prev() step, so we need to trigger the pre-stepleave event
// here, even if a swipe doesn't guarantee that the transition will
// actually happen.
//
// Calling swipe(), with any value of pct, won't in itself cause a
// transition to happen, this is just to animate the swipe. Once the
// transition is committed - such as at a touchend event - caller is
// responsible for also calling prev()/next() as appropriate.
var swipe = function(pct){
if( Math.abs(pct) > 1 ) return;
// Prepare & execute the preStepLeave event
var event = { target: activeStep, detail : {} };
// Will be ignored within swipe animation, but just in case a plugin wants to read this, humor them
event.detail.transitionDuration = config.transitionDuration;
if (pct < 0) {
var idx = steps.indexOf(activeStep) + 1;
event.detail.next = idx < steps.length ? steps[idx] : steps[0];
event.detail.reason = "next";
} else if (pct > 0) {
var idx = steps.indexOf(activeStep) - 1;
event.detail.next = idx >= 0 ? steps[idx] : steps[steps.length - 1];
event.detail.reason = "prev";
} else {
// No move
return;
}
execPreStepLeavePlugins(event);
var nextElement = event.detail.next;
var nextStep = stepsData['impress-' + nextElement.id];
var zoomin = nextStep.scale >= currentState.scale;
// if the same step is re-selected, force computing window scaling,
var nextScale = nextStep.scale * windowScale;
var k = Math.abs(pct);
var interpolatedStep = {
translate: {
x: interpolate(currentState.translate.x, -nextStep.translate.x, k),
y: interpolate(currentState.translate.y, -nextStep.translate.y, k),
z: interpolate(currentState.translate.z, -nextStep.translate.z, k)
},
rotate: {
x: interpolate(currentState.rotate.x, -nextStep.rotate.x, k),
y: interpolate(currentState.rotate.y, -nextStep.rotate.y, k),
z: interpolate(currentState.rotate.z, -nextStep.rotate.z, k)
},
scale: interpolate(currentState.scale, nextScale)
};
css(root, {
// to keep the perspective look similar for different scales
// we need to 'scale' the perspective, too
transform: perspective(config.perspective / interpolatedStep.scale) + scale(interpolatedStep.scale),
transitionDuration: "0ms",
transitionDelay: "0ms"
});
css(canvas, {
transform: rotate(interpolatedStep.rotate, true) + translate(interpolatedStep.translate),
transitionDuration: "0ms",
transitionDelay: "0ms"
});
};
// Adding some useful classes to step elements.
//
// All the steps that have not been shown yet are given `future` class.
@@ -699,6 +778,7 @@
goto: goto,
next: next,
prev: prev,
swipe: swipe,
addPreInitPlugin: addPreInitPlugin,
addPreStepLeavePlugin: addPreStepLeavePlugin
});
@@ -134,26 +134,6 @@
}
}, false);
// touch handler to detect taps on the left and right side of the screen
// based on awesome work of @hakimel: https://github.com/hakimel/reveal.js
document.addEventListener("touchstart", function ( event ) {
if (event.touches.length === 1) {
var x = event.touches[0].clientX,
width = window.innerWidth * 0.3,
result = null;
if ( x < width ) {
result = api.prev();
} else if ( x > window.innerWidth - width ) {
result = api.next();
}
if (result) {
event.preventDefault();
}
}
}, false);
// TODO: This was originally defined here, when impress.js was a single file.
// It has nothing to do with key navigation events, but it depends on the
// throttle function defined above. Leaving here for now.
Oops, something went wrong.

0 comments on commit c44fd0f

Please sign in to comment.