Implement propper support for pointer events and MSPointer events.

Closes  and .
This commit is contained in:
Jürg Lehni 2014-03-12 13:22:41 +01:00
parent 5f00c02e67
commit 9c552b1739
4 changed files with 132 additions and 74 deletions

View file

@ -46,6 +46,24 @@ var DomElement = new function() {
return res; return res;
} }
// Handles both getting and setting of vendor prefix values
function handlePrefix(el, name, set, value) {
var prefixes = ['webkit', 'moz', 'Moz', 'ms', 'o', ''],
suffix = name[0].toUpperCase() + name.substring(1);
for (var i = 0; i < 6; i++) {
var prefix = prefixes[i],
key = prefix ? prefix + suffix : name;
if (key in el) {
if (set) {
el[key] = value;
} else {
return el[key];
}
break;
}
}
}
return /** @lends DomElement */{ return /** @lends DomElement */{
create: function(nodes, parent) { create: function(nodes, parent) {
var isArray = Array.isArray(nodes), var isArray = Array.isArray(nodes),
@ -203,13 +221,17 @@ var DomElement = new function() {
* Gets the given property from the element, trying out all browser * Gets the given property from the element, trying out all browser
* prefix variants. * prefix variants.
*/ */
getPrefixValue: function(el, name) { getPrefixed: function(el, name) {
var value = el[name], return handlePrefix(el, name);
prefixes = ['webkit', 'moz', 'ms', 'o'], },
suffix = name[0].toUpperCase() + name.substring(1);
for (var i = 0; i < 4 && value == null; i++) setPrefixed: function(el, name, value) {
value = el[prefixes[i] + suffix]; if (typeof name === 'object') {
return value; for (var key in name)
handlePrefix(el, key, true, name[key]);
} else {
handlePrefix(el, name, true, value);
}
} }
}; };
}; };

View file

@ -17,13 +17,21 @@
*/ */
var DomEvent = /** @lends DomEvent */{ var DomEvent = /** @lends DomEvent */{
add: function(el, events) { add: function(el, events) {
for (var type in events) for (var type in events) {
el.addEventListener(type, events[type], false); var func = events[type],
parts = type.split(/[\s,]+/g);
for (var i = 0, l = parts.length; i < l; i++)
el.addEventListener(parts[i], func, false);
}
}, },
remove: function(el, events) { remove: function(el, events) {
for (var type in events) for (var type in events) {
el.removeEventListener(type, events[type], false); var func = events[type],
parts = type.split(/[\s,]+/g);
for (var i = 0, l = parts.length; i < l; i++)
el.removeEventListener(parts[i], func, false);
}
}, },
getPoint: function(event) { getPoint: function(event) {
@ -59,8 +67,7 @@ var DomEvent = /** @lends DomEvent */{
}; };
DomEvent.requestAnimationFrame = new function() { DomEvent.requestAnimationFrame = new function() {
var nativeRequest = DomElement.getPrefixValue(window, var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'),
'requestAnimationFrame'),
requested = false, requested = false,
callbacks = [], callbacks = [],
focused = true, focused = true,

View file

@ -51,7 +51,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
// Hi-DPI Canvas support based on: // Hi-DPI Canvas support based on:
// http://www.html5rocks.com/en/tutorials/canvas/hidpi/ // http://www.html5rocks.com/en/tutorials/canvas/hidpi/
var deviceRatio = window.devicePixelRatio || 1, var deviceRatio = window.devicePixelRatio || 1,
backingStoreRatio = DomElement.getPrefixValue(this._context, backingStoreRatio = DomElement.getPrefixed(this._context,
'backingStorePixelRatio') || 1; 'backingStorePixelRatio') || 1;
this._pixelRatio = deviceRatio / backingStoreRatio; this._pixelRatio = deviceRatio / backingStoreRatio;
} }

View file

@ -38,7 +38,20 @@ var View = Base.extend(Callback, /** @lends View# */{
if (this._id == null) if (this._id == null)
element.setAttribute('id', this._id = 'view-' + View._id++); element.setAttribute('id', this._id = 'view-' + View._id++);
// Install event handlers // Install event handlers
DomEvent.add(element, this._viewHandlers); DomEvent.add(element, this._viewEvents);
// Borrowed from Hammer.js:
var none = 'none';
DomElement.setPrefixed(element.style, {
userSelect: none,
// This makes the element blocking in IE10+
// You could experiment with the value, see this issue:
// https://github.com/EightMedia/hammer.js/issues/241
touchAction: none,
touchCallout: none,
contentZooming: none,
userDrag: none,
tapHighlightColor: 'rgba(0,0,0,0)'
});
// If the element has the resize attribute, resize the it to fill the // If the element has the resize attribute, resize the it to fill the
// window and resize it again whenever the user resizes the window. // window and resize it again whenever the user resizes the window.
if (PaperScope.hasAttribute(element, 'resize')) { if (PaperScope.hasAttribute(element, 'resize')) {
@ -48,7 +61,7 @@ var View = Base.extend(Callback, /** @lends View# */{
that = this; that = this;
size = DomElement.getViewportBounds(element) size = DomElement.getViewportBounds(element)
.getSize().subtract(offset); .getSize().subtract(offset);
this._windowHandlers = { this._windowEvents = {
resize: function() { resize: function() {
// Only update element offset if it's not invisible, as // Only update element offset if it's not invisible, as
// otherwise the offset would be wrong. // otherwise the offset would be wrong.
@ -60,7 +73,7 @@ var View = Base.extend(Callback, /** @lends View# */{
.getSize().subtract(offset)); .getSize().subtract(offset));
} }
}; };
DomEvent.add(window, this._windowHandlers); DomEvent.add(window, this._windowEvents);
} else { } else {
// Try visible size first, since that will help handling previously // Try visible size first, since that will help handling previously
// scaled canvases (e.g. when dealing with pixel-ratio) // scaled canvases (e.g. when dealing with pixel-ratio)
@ -130,8 +143,8 @@ var View = Base.extend(Callback, /** @lends View# */{
this._project.view = null; this._project.view = null;
/*#*/ if (__options.environment == 'browser') { /*#*/ if (__options.environment == 'browser') {
// Uninstall event handlers again for this view. // Uninstall event handlers again for this view.
DomEvent.remove(this._element, this._viewHandlers); DomEvent.remove(this._element, this._viewEvents);
DomEvent.remove(window, this._windowHandlers); DomEvent.remove(window, this._windowEvents);
/*#*/ } // __options.environment == 'browser' /*#*/ } // __options.environment == 'browser'
this._element = this._project = null; this._element = this._project = null;
// Remove all onFrame handlers. // Remove all onFrame handlers.
@ -687,7 +700,68 @@ var View = Base.extend(Callback, /** @lends View# */{
} }
} }
function mousedown(event) { function handleMouseMove(view, point, event) {
view._handleEvent('mousemove', point, event);
var tool = view._scope.tool;
if (tool) {
// If there's no onMouseDrag, fire onMouseMove while dragging.
tool._handleEvent(dragging && tool.responds('mousedrag')
? 'mousedrag' : 'mousemove', point, event);
}
view.update();
return tool;
}
// Touch handling inspired by Hammer.js
var navigator = window.navigator,
mousedown, mousemove, mouseup;
if (navigator.pointerEnabled || navigator.msPointerEnabled) {
// HTML5 / MS pointer events
mousedown = 'pointerdown MSPointerDown';
mousemove = 'pointermove MSPointerMove';
mouseup = 'pointerup pointercancel MSPointerUp MSPointerCancel';
} else {
mousedown = 'touchstart';
mousemove = 'touchmove';
mouseup = 'touchend touchcancel';
// Do not add mouse events on mobile and tablet devices
if (!('ontouchstart' in window && navigator.userAgent.match(
/mobile|tablet|ip(ad|hone|od)|android|silk/i))) {
// For non pointer events browsers and mixed browsers, like chrome
// on Windows8 touch laptop.
mousedown += ' mousedown';
mousemove += ' mousemove';
mouseup += ' mouseup';
}
}
var viewEvents = {
selectstart: function(event) {
// Only stop this even if we're dragging already, since otherwise no
// text whatsoever can be selected on the page.
if (dragging)
event.preventDefault();
}
};
var docEvents = {
mouseout: function(event) {
// When the moues leaves the document, fire one last mousemove
// event, to give items the change to receive a mouseleave, etc.
var view = View._focused,
target = DomEvent.getRelatedTarget(event);
if (view && (!target || target.nodeName === 'HTML'))
handleMouseMove(view, viewToProject(view, event), event);
},
scroll: updateFocus
};
// mousemove and mouseup events need to be installed on document, not the
// view element, since we want to catch the end of drag events even outside
// our view. Only the mousedown events are installed on the view, as defined
// by _viewEvents below.
viewEvents[mousedown] = function(event) {
// Get the view from the event, and store a reference to the view that // Get the view from the event, and store a reference to the view that
// should receive keyboard input. // should receive keyboard input.
var view = View._focused = getView(event), var view = View._focused = getView(event),
@ -701,29 +775,17 @@ var View = Base.extend(Callback, /** @lends View# */{
// In the end we always call update(), which only updates the view if // In the end we always call update(), which only updates the view if
// anything has changed in the above calls. // anything has changed in the above calls.
view.update(); view.update();
} };
function handleMouseMove(view, point, event) { docEvents[mousemove] = function(event) {
view._handleEvent('mousemove', point, event);
var tool = view._scope.tool;
if (tool) {
// If there's no onMouseDrag, fire onMouseMove while dragging.
tool._handleEvent(dragging && tool.responds('mousedrag')
? 'mousedrag' : 'mousemove', point, event);
}
view.update();
return tool;
}
function mousemove(event) {
var view = View._focused; var view = View._focused;
if (!dragging) { if (!dragging) {
// See if we can get the view from the current event target, and // See if we can get the view from the current event target, and
// handle the mouse move over it. // handle the mouse move over it.
var target = getView(event); var target = getView(event);
if (target) { if (target) {
// Temporarily focus this view without making it sticky, so // Temporarily focus this view without making it sticky, so Key
// Key events are handled too during the mouse over // events are handled too during the mouse over.
// If we switch view, fire one last mousemove in the old view, // If we switch view, fire one last mousemove in the old view,
// to give items the change to receive a mouseleave, etc. // to give items the change to receive a mouseleave, etc.
if (view !== target) if (view !== target)
@ -741,18 +803,9 @@ var View = Base.extend(Callback, /** @lends View# */{
if (dragging || view.getBounds().contains(point)) if (dragging || view.getBounds().contains(point))
tool = handleMouseMove(view, point, event); tool = handleMouseMove(view, point, event);
} }
} };
function mouseout(event) { docEvents[mouseup] = function(event) {
// When the moues leaves the document, fire one last mousemove event,
// to give items the change to receive a mouseleave, etc.
var view = View._focused,
target = DomEvent.getRelatedTarget(event);
if (view && (!target || target.nodeName === 'HTML'))
handleMouseMove(view, viewToProject(view, event), event);
}
function mouseup(event) {
var view = View._focused; var view = View._focused;
if (!view || !dragging) if (!view || !dragging)
return; return;
@ -763,40 +816,16 @@ var View = Base.extend(Callback, /** @lends View# */{
if (tool) if (tool)
tool._handleEvent('mouseup', point, event); tool._handleEvent('mouseup', point, event);
view.update(); view.update();
} };
function selectstart(event) { DomEvent.add(document, docEvents);
// Only stop this even if we're dragging already, since otherwise no
// text whatsoever can be selected on the page.
if (dragging)
event.preventDefault();
}
// mousemove and mouseup events need to be installed on document, not the
// view element, since we want to catch the end of drag events even outside
// our view. Only the mousedown events are installed on the view, as handled
// by _createHandlers below.
DomEvent.add(document, {
mousemove: mousemove,
mouseout: mouseout,
mouseup: mouseup,
touchmove: mousemove,
touchend: mouseup,
selectstart: selectstart,
scroll: updateFocus
});
DomEvent.add(window, { DomEvent.add(window, {
load: updateFocus load: updateFocus
}); });
return { return {
_viewHandlers: { _viewEvents: viewEvents,
mousedown: mousedown,
touchstart: mousedown,
selectstart: selectstart
},
// To be defined in subclasses // To be defined in subclasses
_handleEvent: function(/* type, point, event */) {}, _handleEvent: function(/* type, point, event */) {},