Skip to content

Commit

Permalink
Support mouse event in iOS when css transform used.
Browse files Browse the repository at this point in the history
  • Loading branch information
100pah committed May 12, 2019
1 parent c0138ab commit cd20afd
Show file tree
Hide file tree
Showing 4 changed files with 890 additions and 19 deletions.
134 changes: 115 additions & 19 deletions src/core/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,30 @@

import Eventful from '../mixin/Eventful';
import env from './env';
import {buildTransformer} from './fourPointsTransform';

var isDomLevel2 = (typeof window !== 'undefined') && !!window.addEventListener;

var MOUSE_EVENT_REG = /^(?:mouse|pointer|contextmenu|drag|drop)|click/;

function getBoundingClientRect(el) {
// BlackBerry 5, iOS 3 (original iPhone) don't have getBoundingRect
return el.getBoundingClientRect ? el.getBoundingClientRect() : {left: 0, top: 0};
}
var EVENT_SAVED_PROP = '___zrEVENTSAVED';
var _calcOut = [];

/**
* Get the `zrX` and `zrY`, which are relative to the top-left of
* the input `el`.
* CSS transform is supported.
* CSS transform (2D & 3D) is supported.
*
* The strategy to fetch the coords:
* + If `calculate` is not set as `true`, users of this method should
* ensure that `el` is the same or the same size & location as `e.target`.
* Otherwise the result coords are probably not expected. Because we
* firstly try to get coords from e.offsetX/e.offsetY.
* + If `calculate` is set as `true`, the input `el` can be any element
* and we force to calculate the coords based on `el`.
* + The input `el` should be positionable (not position:static).
*
* The force `calculate` can be used in case like:
* When mousemove event triggered on ec tooltip, `e.target` is not `el`(zr painter.dom).
*
* @param {HTMLElement} el DOM element.
* @param {Event} e Mouse event or touch event.
Expand All @@ -35,12 +45,8 @@ export function clientToLocal(el, e, out, calculate) {
// (see http://www.jacklmoore.com/notes/mouse-position/)
// In zr painter.dom, padding edge equals to border edge.

// FIXME
// When mousemove event triggered on ec tooltip, target is not zr painter.dom, and
// offsetX/Y is relative to e.target, where the calculation of zrX/Y via offsetX/Y
// is too complex. So css-transfrom dont support in this case temporarily.
if (calculate || !env.canvasSupported) {
defaultGetZrXY(el, e, out);
calculateZrXY(el, e, out);
}
// Caution: In FireFox, layerX/layerY Mouse position relative to the closest positioned
// ancestor element, so we should make sure el is positioned (e.g., not position:static).
Expand All @@ -60,29 +66,119 @@ export function clientToLocal(el, e, out, calculate) {
}
// For some other device, e.g., IOS safari.
else {
defaultGetZrXY(el, e, out);
calculateZrXY(el, e, out);
}

return out;
}

function defaultGetZrXY(el, e, out) {
// This well-known method below does not support css transform.
var box = getBoundingClientRect(el);
out.zrX = e.clientX - box.left;
out.zrY = e.clientY - box.top;
function calculateZrXY(el, e, out) {
// BlackBerry 5, iOS 3 (original iPhone) don't have getBoundingRect.
if (el.getBoundingClientRect && env.domSupported) {
var ex = e.clientX;
var ey = e.clientY;

if (el.nodeName.toUpperCase() === 'CANVAS') {
// Original approach, which do not support CSS transform.
// marker can not be locationed in a canvas container
// (getBoundingClientRect is always 0). We do not support
// that input a pre-created canvas to zr while using css
// transform in iOS.
var box = el.getBoundingClientRect();
out.zrX = ex - box.left;
out.zrY = ey - box.top;
return;
}
else {
var saved = el[EVENT_SAVED_PROP] || (el[EVENT_SAVED_PROP] = {});
var transformer = preparePointerTransformer(prepareCoordMarkers(el, saved), saved);
if (transformer) {
transformer(_calcOut, ex, ey);
out.zrX = _calcOut[0];
out.zrY = _calcOut[1];
return;
}
}
}
out.zrX = out.zrY = 0;
}

function prepareCoordMarkers(el, saved) {
var markers = saved.markers;
if (markers) {
return markers;
}

markers = saved.markers = [];
var propLR = ['left', 'right'];
var propTB = ['top', 'bottom'];

for (var i = 0; i < 4; i++) {
var marker = document.createElement('div');
var stl = marker.style;
var idxLR = i % 2;
var idxTB = (i >> 1) % 2;
stl.cssText = [
'position:absolute',
'visibility: hidden',
'padding: 0',
'margin: 0',
'border-width: 0',
'width:0',
'height:0',
// 'width: 5px',
// 'height: 5px',
propLR[idxLR] + ':0',
propTB[idxTB] + ':0',
propLR[1 - idxLR] + ':auto',
propTB[1 - idxTB] + ':auto',
''
].join('!important;');
el.appendChild(marker);
markers.push(marker);
}

return markers;
}

function preparePointerTransformer(markers, saved) {
var transformer = saved.transformer;
var oldSrcCoords = saved.srcCoords;
var useOld = true;
var srcCoords = [];
var destCoords = [];

for (var i = 0; i < 4; i++) {
var rect = markers[i].getBoundingClientRect();
var ii = 2 * i;
var x = rect.left;
var y = rect.top;
srcCoords.push(x, y);
useOld &= oldSrcCoords && x === oldSrcCoords[ii] && y === oldSrcCoords[ii + 1];
destCoords.push(markers[i].offsetLeft, markers[i].offsetTop);
}

// Cache to avoid time consuming of `buildTransformer`.
return useOld
? transformer
: (
saved.srcCoords = srcCoords,
saved.transformer = buildTransformer(srcCoords, destCoords)
);
}

/**
* Normalize the coordinates of the input event.
*
* Get the `e.zrX` and `e.zrY`, which are relative to the top-left of
* the input `el`. CSS transform is supported.
* the input `el`.
* Get `e.zrDelta` if using mouse wheel.
* Get `e.which`, see the comment inside this function.
*
* Do not calculate repeatly if `zrX` and `zrY` already exist.
* CSS transform is supported.
*
* Notice: see comments in `clientToLocal`. check the relationship
* between the result coords and the parameters `el` and `calculate`.
*
* @param {HTMLElement} el DOM element.
* @param {Event} [e] Mouse event or touch event. For lagency IE,
Expand Down
99 changes: 99 additions & 0 deletions src/core/fourPointsTransform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* The algoritm is learnt from
* https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/
* And we made some optimization for matrix inversion.
* Other similar approaches:
* "cv::getPerspectiveTransform", "Direct Linear Transformation".
*/

var LN2 = Math.log(2);

function determinant(rows, rank, rowStart, rowMask, colMask, detCache) {
var cacheKey = rowMask + '-' + colMask;
var fullRank = rows.length;

if (detCache.hasOwnProperty(cacheKey)) {
return detCache[cacheKey];
}

if (rank === 1) {
// In this case the colMask must be like: `11101111`. We can find the place of `0`.
var colStart = Math.round(Math.log(((1 << fullRank) - 1) & ~colMask) / LN2);
return rows[rowStart][colStart];
}

var subRowMask = rowMask | (1 << rowStart);
var subRowStart = rowStart + 1;
while (rowMask & (1 << subRowStart)) {
subRowStart++;
}

var sum = 0;
for (var j = 0, colLocalIdx = 0; j < fullRank; j++) {
var colTag = 1 << j;
if (!(colTag & colMask)) {
sum += (colLocalIdx % 2 ? -1 : 1) * rows[rowStart][j]
// det(subMatrix(0, j))
* determinant(rows, rank - 1, subRowStart, subRowMask, colMask | colTag, detCache);
colLocalIdx++;
}
}

detCache[cacheKey] = sum;

return sum;
}

/**
* Usage:
* ```js
* var transformer = buildTransformer(
* [10, 44, 100, 44, 100, 300, 10, 300],
* [50, 54, 130, 14, 140, 330, 14, 220]
* );
* var out = [];
* transformer && transformer([11, 33], out);
* ```
*
* Notice: `buildTransformer` may take more than 10ms in some Android device.
*
* @param {Array.<number>} src source four points, [x0, y0, x1, y1, x2, y2, x3, y3]
* @param {Array.<number>} dest destination four points, [x0, y0, x1, y1, x2, y2, x3, y3]
* @return {Function} transformer If fail, return null/undefined.
*/
export function buildTransformer(src, dest) {
var mA = [
[src[0], src[1], 1, 0, 0, 0, -dest[0] * src[0], -dest[0] * src[1]],
[0, 0, 0, src[0], src[1], 1, -dest[1] * src[0], -dest[1] * src[1]],
[src[2], src[3], 1, 0, 0, 0, -dest[2] * src[2], -dest[2] * src[3]],
[0, 0, 0, src[2], src[3], 1, -dest[3] * src[2], -dest[3] * src[3]],
[src[4], src[5], 1, 0, 0, 0, -dest[4] * src[4], -dest[4] * src[5]],
[0, 0, 0, src[4], src[5], 1, -dest[5] * src[4], -dest[5] * src[5]],
[src[6], src[7], 1, 0, 0, 0, -dest[6] * src[6], -dest[6] * src[7]],
[0, 0, 0, src[6], src[7], 1, -dest[7] * src[6], -dest[7] * src[7]]
];

var detCache = {};
var det = determinant(mA, 8, 0, 0, 0, detCache);
if (det === 0) {
return;
}

// `invert(mA) * dest`, that is, `adj(mA) / det * dest`.
var vh = [];
for (var i = 0; i < 8; i++) {
for (var j = 0; j < 8; j++) {
vh[j] == null && (vh[j] = 0);
vh[j] += ((i + j) % 2 ? -1 : 1)
// det(subMatrix(i, j))
* determinant(mA, 7, i === 0 ? 1 : 0, 1 << i, 1 << j, detCache)
/ det * dest[i];
}
}

return function (out, srcPointX, srcPointY) {
var pk = srcPointX * vh[6] + srcPointY * vh[7] + 1;
out[0] = (srcPointX * vh[0] + srcPointY * vh[1] + vh[2]) / pk;
out[1] = (srcPointX * vh[3] + srcPointY * vh[4] + vh[5]) / pk;
};
}
Loading

0 comments on commit cd20afd

Please sign in to comment.