mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-06-18 07:28:46 +08:00
[fix] ResponderEvent touch identifier normalization
Move the touch identifier normalization into the ResponderEvent creation. This ensures that the identifier is consistent throughout. If application code reads an identifier from a touch object it can be used to find that touch in the `touchBank` array. Fix #1716
This commit is contained in:
@@ -84,9 +84,17 @@ function getTouchIdentifier({ identifier }: Touch): number {
|
||||
if (identifier == null) {
|
||||
console.error('Touch object is missing identifier.');
|
||||
}
|
||||
// Safari produces very large identifiers that would cause the array length
|
||||
// to be so large as to crash the browser, if not normalized like this.
|
||||
return identifier > MAX_TOUCH_BANK ? identifier % 20 : identifier;
|
||||
if (__DEV__) {
|
||||
if (identifier > MAX_TOUCH_BANK) {
|
||||
console.error(
|
||||
'Touch identifier %s is greater than maximum supported %s which causes ' +
|
||||
'performance issues backfilling array locations for all of the indices.',
|
||||
identifier,
|
||||
MAX_TOUCH_BANK
|
||||
);
|
||||
}
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
||||
function recordTouchStart(touch: Touch): void {
|
||||
|
||||
@@ -162,6 +162,33 @@ describe('useResponderEvents', () => {
|
||||
expect(targetCallbacks.onResponderGrant).toBeCalledTimes(4);
|
||||
});
|
||||
|
||||
// NOTE: this is only needed for performance reasons while the
|
||||
// `touchBank` is an array.
|
||||
test('normalizes touch identifiers', () => {
|
||||
const targetRef = createRef();
|
||||
let identifier;
|
||||
const Component = () => {
|
||||
useResponderEvents(targetRef, {
|
||||
onStartShouldSetResponder: () => true,
|
||||
onResponderStart: jest.fn(e => {
|
||||
identifier = e.nativeEvent.identifier;
|
||||
})
|
||||
});
|
||||
return <div ref={targetRef} />;
|
||||
};
|
||||
|
||||
// render
|
||||
act(() => {
|
||||
render(<Component />);
|
||||
});
|
||||
const target = createEventTarget(targetRef.current);
|
||||
// gesture
|
||||
act(() => {
|
||||
target.pointerdown({ pointerId: 123456, pointerType: 'touch' });
|
||||
});
|
||||
expect(identifier <= 20).toBe(true);
|
||||
});
|
||||
|
||||
/**
|
||||
* SET: onStartShouldSetResponderCapture
|
||||
*/
|
||||
|
||||
@@ -57,6 +57,15 @@ const emptyFunction = () => {};
|
||||
const emptyObject = {};
|
||||
const emptyArray = [];
|
||||
|
||||
/**
|
||||
* Safari produces very large identifiers that would cause the `touchBank` array
|
||||
* length to be so large as to crash the browser, if not normalized like this.
|
||||
* In the future the `touchBank` should use an object/map instead.
|
||||
*/
|
||||
function normalizeIdentifier(identifier) {
|
||||
return identifier > 20 ? identifier % 20 : identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a native DOM event to a ResponderEvent.
|
||||
* Mouse events are transformed into fake touch events.
|
||||
@@ -73,7 +82,9 @@ export default function createResponderEvent(domEvent: any): ResponderEvent {
|
||||
const metaKey = domEvent.metaKey === true;
|
||||
const shiftKey = domEvent.shiftKey === true;
|
||||
const force = (domEventChangedTouches && domEventChangedTouches[0].force) || 0;
|
||||
const identifier = (domEventChangedTouches && domEventChangedTouches[0].identifier) || 0;
|
||||
const identifier = normalizeIdentifier(
|
||||
(domEventChangedTouches && domEventChangedTouches[0].identifier) || 0
|
||||
);
|
||||
const clientX = (domEventChangedTouches && domEventChangedTouches[0].clientX) || domEvent.clientX;
|
||||
const clientY = (domEventChangedTouches && domEventChangedTouches[0].clientY) || domEvent.clientY;
|
||||
const pageX = (domEventChangedTouches && domEventChangedTouches[0].pageX) || domEvent.pageX;
|
||||
@@ -86,8 +97,20 @@ export default function createResponderEvent(domEvent: any): ResponderEvent {
|
||||
|
||||
function normalizeTouches(touches) {
|
||||
return Array.prototype.slice.call(touches).map(touch => {
|
||||
touch.timestamp = timestamp;
|
||||
return touch;
|
||||
return {
|
||||
force: touch.force,
|
||||
identifier: normalizeIdentifier(touch.identifier),
|
||||
get locationX() {
|
||||
return locationX(touch.clientX);
|
||||
},
|
||||
get locationY() {
|
||||
return locationY(touch.clientY);
|
||||
},
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY,
|
||||
target: touch.target,
|
||||
timestamp
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,10 +123,10 @@ export default function createResponderEvent(domEvent: any): ResponderEvent {
|
||||
force,
|
||||
identifier,
|
||||
get locationX() {
|
||||
return locationX();
|
||||
return locationX(clientX);
|
||||
},
|
||||
get locationY() {
|
||||
return locationY();
|
||||
return locationY(clientY);
|
||||
},
|
||||
pageX,
|
||||
pageY,
|
||||
@@ -140,10 +163,10 @@ export default function createResponderEvent(domEvent: any): ResponderEvent {
|
||||
force,
|
||||
identifier,
|
||||
get locationX() {
|
||||
return locationX();
|
||||
return locationX(clientX);
|
||||
},
|
||||
get locationY() {
|
||||
return locationY();
|
||||
return locationY(clientY);
|
||||
},
|
||||
pageX,
|
||||
pageY,
|
||||
@@ -165,16 +188,16 @@ export default function createResponderEvent(domEvent: any): ResponderEvent {
|
||||
// Using getters and functions serves two purposes:
|
||||
// 1) The value of `currentTarget` is not initially available.
|
||||
// 2) Measuring the clientRect may cause layout jank and should only be done on-demand.
|
||||
function locationX() {
|
||||
function locationX(x) {
|
||||
rect = rect || getBoundingClientRect(responderEvent.currentTarget);
|
||||
if (rect) {
|
||||
return clientX - rect.left;
|
||||
return x - rect.left;
|
||||
}
|
||||
}
|
||||
function locationY() {
|
||||
function locationY(y) {
|
||||
rect = rect || getBoundingClientRect(responderEvent.currentTarget);
|
||||
if (rect) {
|
||||
return clientY - rect.top;
|
||||
return y - rect.top;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user