feat(features/touch): add touch gesture support
Allow to navigate i.e. scroll/zoom on the diagram via touch gestures. Closes #46
This commit is contained in:
parent
fcb35c366d
commit
3c7033f92e
|
@ -10,11 +10,12 @@ var Importer = require('./import/Importer'),
|
|||
|
||||
|
||||
function getSvgContents(diagram) {
|
||||
var paper = diagram.get('canvas').getPaper();
|
||||
var outerNode = paper.node.parentNode;
|
||||
var outerNode = diagram.get('canvas').getContainer();
|
||||
|
||||
var svg = outerNode.innerHTML;
|
||||
return svg.replace(/^<svg[^>]*>|<\/svg>$/g, '');
|
||||
return svg.replace(/^<svg[^>]*>|<\/svg>$/g, '')
|
||||
.replace('<desc>Created with Snap</desc>', '')
|
||||
.replace(/<g class="viewport"( transform="[^"]*")?/, '<g');
|
||||
}
|
||||
|
||||
function initListeners(diagram, listeners) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var $ = require('jquery');
|
||||
|
||||
var _ = require('lodash');
|
||||
|
||||
function MoveCanvas(events, canvas) {
|
||||
|
||||
|
@ -9,7 +9,7 @@ function MoveCanvas(events, canvas) {
|
|||
|
||||
function init(element) {
|
||||
|
||||
var context;
|
||||
var context = {};
|
||||
|
||||
function getPosition(e) {
|
||||
return { x: e.clientX, y: e.clientY };
|
||||
|
@ -40,30 +40,33 @@ function MoveCanvas(events, canvas) {
|
|||
|
||||
var position = getPosition(event);
|
||||
|
||||
var delta = getDelta(context.start, position);
|
||||
|
||||
if (!context.dragging && isThresholdReached(delta)) {
|
||||
if (!context.dragging && isThresholdReached(getDelta(context.start, position))) {
|
||||
context.dragging = true;
|
||||
|
||||
context.cursor = cursor('move');
|
||||
}
|
||||
|
||||
|
||||
if (context.dragging) {
|
||||
|
||||
var box = context.viewbox;
|
||||
var lastPos = context.last || context.start;
|
||||
|
||||
canvas.viewbox({
|
||||
x: box.x + delta.x / box.scale,
|
||||
y: box.y + delta.y / box.scale,
|
||||
width: box.width,
|
||||
height: box.height
|
||||
var delta = getDelta(position, lastPos);
|
||||
|
||||
canvas.scroll({
|
||||
dx: delta.x,
|
||||
dy: delta.y
|
||||
});
|
||||
|
||||
context.last = position;
|
||||
}
|
||||
|
||||
// prevent select
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
|
||||
function handleEnd(event) {
|
||||
$(element).off('mousemove', handleMove);
|
||||
$(document).off('mouseup', handleEnd);
|
||||
|
@ -81,7 +84,6 @@ function MoveCanvas(events, canvas) {
|
|||
var position = getPosition(event);
|
||||
|
||||
context = {
|
||||
viewbox: canvas.viewbox(),
|
||||
start: position
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
'use strict';
|
||||
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
|
||||
function TouchInteraction(eventBus, canvas) {
|
||||
|
||||
var RANGE = { min: 0.2, max: 4 };
|
||||
|
||||
function cap(scale) {
|
||||
return Math.max(RANGE.min, Math.min(RANGE.max, scale));
|
||||
}
|
||||
|
||||
function zoom(pinchRatio, position) {
|
||||
canvas.zoom(cap(pinchRatio), position);
|
||||
}
|
||||
|
||||
function euclideanDistance(touch1, touch2) {
|
||||
var dist =
|
||||
Math.sqrt(
|
||||
(touch1.clientX - touch2.clientX) *
|
||||
(touch1.clientX - touch2.clientX) +
|
||||
(touch1.clientY - touch2.clientY) *
|
||||
(touch1.clientY - touch2.clientY)
|
||||
);
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
function init(element) {
|
||||
|
||||
function contextInit() {
|
||||
context = {
|
||||
start: {}
|
||||
};
|
||||
}
|
||||
|
||||
var context;
|
||||
contextInit();
|
||||
|
||||
function handleTouchMove(event) {
|
||||
var orgEvent = event.originalEvent;
|
||||
var cX = 0;
|
||||
var cY = 0;
|
||||
var mX = 0;
|
||||
var mY = 0;
|
||||
|
||||
if (context.move && !context.scale) {
|
||||
|
||||
_.forEach(orgEvent.changedTouches, function (touch) {
|
||||
if (touch.identifier === context.moveId) {
|
||||
cX = touch.clientX;
|
||||
cY = touch.clientY;
|
||||
}
|
||||
});
|
||||
|
||||
if (!context.lasttouch.cX) {
|
||||
context.lasttouch.cX = cX;
|
||||
}
|
||||
|
||||
if (!context.lasttouch.cY) {
|
||||
context.lasttouch.cY = cY;
|
||||
}
|
||||
|
||||
mX = cX - context.lasttouch.cX;
|
||||
mY = cY - context.lasttouch.cY;
|
||||
|
||||
canvas.scroll({
|
||||
dx: mX,
|
||||
dy: mY
|
||||
});
|
||||
|
||||
context.lasttouch.cX = cX;
|
||||
context.lasttouch.cY = cY;
|
||||
}
|
||||
|
||||
if (context.scale) {
|
||||
var dist = euclideanDistance(orgEvent.touches[0], orgEvent.touches[1]);
|
||||
var distRatio = dist / context.start.dist;
|
||||
|
||||
zoom(distRatio * context.start.zoom, context.start.mid);
|
||||
context.lastDistance = dist;
|
||||
}
|
||||
|
||||
// prevent select
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function handleTouchEnd(event) {
|
||||
|
||||
var orgEvent = event.originalEvent;
|
||||
|
||||
if (orgEvent.touches.length < 1) {
|
||||
contextInit();
|
||||
}
|
||||
if (orgEvent.touches.length === 1) {
|
||||
initMove(orgEvent);
|
||||
}
|
||||
if (orgEvent.touches.length === 2) {
|
||||
initScale(orgEvent);
|
||||
}
|
||||
|
||||
_.forEach(orgEvent.changedTouches, function (touch) {
|
||||
delete context[touch.identifier];
|
||||
});
|
||||
|
||||
// prevent select
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function initMove(orgEvent) {
|
||||
context.move = true;
|
||||
context.moveId = orgEvent.touches[0].identifier;
|
||||
context.scale = false;
|
||||
if (!context.lasttouch) {
|
||||
context.lasttouch = {
|
||||
cX: undefined,
|
||||
cY: undefined,
|
||||
mX: 0,
|
||||
mY: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function initScale(orgEvent) {
|
||||
context.scale = true;
|
||||
context.start.dist = euclideanDistance(orgEvent.touches[0], orgEvent.touches[1]);
|
||||
context.lastDistance = context.start.dist;
|
||||
context.start.zoom = canvas.zoom();
|
||||
context.start.mid = {
|
||||
x: (orgEvent.touches[0].clientX + orgEvent.touches[1].clientX) / 2,
|
||||
y: (orgEvent.touches[0].clientY + orgEvent.touches[1].clientY) / 2
|
||||
};
|
||||
}
|
||||
|
||||
function handleTouchStart(event) {
|
||||
|
||||
var orgEvent = event.originalEvent;
|
||||
|
||||
_.forEach(orgEvent.changedTouches, function (touch) {
|
||||
context.start[touch.identifier] = touch;
|
||||
});
|
||||
|
||||
if (orgEvent.touches.length === 1) {
|
||||
initMove(orgEvent);
|
||||
}
|
||||
|
||||
if (orgEvent.touches.length === 2) {
|
||||
initScale(orgEvent);
|
||||
}
|
||||
|
||||
//Stop all interaction for more than 2 touch sources
|
||||
if (orgEvent.touches.length > 2) {
|
||||
contextInit();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
$(element).on('touchstart', handleTouchStart);
|
||||
$(element).on('touchmove', handleTouchMove);
|
||||
$(document).on('touchend', handleTouchEnd);
|
||||
}
|
||||
|
||||
eventBus.on('canvas.init', function(e) {
|
||||
init(e.paper.node);
|
||||
});
|
||||
}
|
||||
|
||||
TouchInteraction.$inject = ['eventBus', 'canvas'];
|
||||
|
||||
module.exports = TouchInteraction;
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
__init__: [ 'touchInteraction' ],
|
||||
touchInteraction: [ 'type', require('./TouchInteraction') ]
|
||||
};
|
|
@ -51,6 +51,7 @@ function ZoomScroll(events, canvas) {
|
|||
|
||||
canvas.scroll(delta);
|
||||
} else {
|
||||
// zoom in relative to diagram {x,y} coordinates
|
||||
zoom(y, { x: event.offsetX, y: event.offsetY });
|
||||
}
|
||||
|
||||
|
|
|
@ -127,6 +127,7 @@ describe('viewer', function() {
|
|||
|
||||
// expect header to be written only once
|
||||
expect(svg.indexOf('<svg width="100%" height="100%">')).toBe(-1);
|
||||
expect(svg.indexOf('<g class="viewport"')).toBe(-1);
|
||||
|
||||
done();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
'use strict';
|
||||
|
||||
var Matchers = require('../../../Matchers'),
|
||||
TestHelper = require('../../../TestHelper');
|
||||
|
||||
/* global bootstrapBpmnJS, inject */
|
||||
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
var $ = require('jquery');
|
||||
|
||||
|
||||
var touchModule = require('../../../../../lib/features/touch'),
|
||||
bpmnModule = require('../../../../../lib/core');
|
||||
|
||||
|
||||
describe('features - touch', function() {
|
||||
|
||||
beforeEach(Matchers.add);
|
||||
|
||||
|
||||
var diagramXML = fs.readFileSync('test/fixtures/bpmn/complex.bpmn', 'utf-8');
|
||||
|
||||
var testModules = [ touchModule, bpmnModule ];
|
||||
|
||||
beforeEach(bootstrapBpmnJS(diagramXML, { modules: testModules }));
|
||||
|
||||
|
||||
describe('bootstrap', function() {
|
||||
|
||||
it('should bootstrap', inject(function(touchInteraction) {
|
||||
expect(touchInteraction).not.toBe(null);
|
||||
}));
|
||||
|
||||
});
|
||||
});
|
|
@ -29,7 +29,7 @@ describe('features - zoomscroll', function() {
|
|||
|
||||
describe('bootstrap', function() {
|
||||
|
||||
iit('should bootstrap', inject(function(zoomScroll) {
|
||||
it('should bootstrap', inject(function(zoomScroll) {
|
||||
expect(zoomScroll).not.toBe(null);
|
||||
}));
|
||||
|
||||
|
|
Loading…
Reference in New Issue