-
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add PointerMoveTracker * fix: update tests for PointerMoveTracker
- Loading branch information
Showing
9 changed files
with
351 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import on from './on'; | ||
import isEventSupported from './utils/isEventSupported'; | ||
|
||
interface PointerMoveTrackerOptions { | ||
useTouchEvent: boolean; | ||
onMove: (x: number, y: number, event: MouseEvent | TouchEvent) => void; | ||
onMoveEnd: (event: MouseEvent | TouchEvent) => void; | ||
} | ||
|
||
/** | ||
* Track mouse/touch events for a given element. | ||
*/ | ||
export default class PointerMoveTracker { | ||
isDragStatus = false; | ||
useTouchEvent = true; | ||
animationFrameID = null; | ||
domNode: Element; | ||
onMove = null; | ||
onMoveEnd = null; | ||
eventMoveToken = null; | ||
eventUpToken = null; | ||
moveEvent = null; | ||
deltaX = 0; | ||
deltaY = 0; | ||
x = 0; | ||
y = 0; | ||
|
||
/** | ||
* onMove is the callback that will be called on every mouse move. | ||
* onMoveEnd is called on mouse up when movement has ended. | ||
*/ | ||
constructor( | ||
domNode: Element, | ||
{ onMove, onMoveEnd, useTouchEvent = true }: PointerMoveTrackerOptions | ||
) { | ||
this.domNode = domNode; | ||
this.onMove = onMove; | ||
this.onMoveEnd = onMoveEnd; | ||
this.useTouchEvent = useTouchEvent; | ||
} | ||
|
||
isSupportTouchEvent() { | ||
return this.useTouchEvent && isEventSupported('touchstart'); | ||
} | ||
|
||
getClientX(event: TouchEvent | MouseEvent) { | ||
return this.isSupportTouchEvent() | ||
? (event as TouchEvent).touches?.[0].clientX | ||
: (event as MouseEvent).clientX; | ||
} | ||
|
||
getClientY(event: TouchEvent | MouseEvent) { | ||
return this.isSupportTouchEvent() | ||
? (event as TouchEvent).touches?.[0].clientY | ||
: (event as MouseEvent).clientY; | ||
} | ||
|
||
/** | ||
* This is to set up the listeners for listening to mouse move | ||
* and mouse up signaling the movement has ended. Please note that these | ||
* listeners are added at the document.body level. It takes in an event | ||
* in order to grab inital state. | ||
*/ | ||
captureMoves(event) { | ||
if (!this.eventMoveToken && !this.eventUpToken) { | ||
this.eventMoveToken = on(this.domNode, 'mousemove', this.onDragMove); | ||
this.eventUpToken = on(this.domNode, 'mouseup', this.onDragUp); | ||
|
||
if (this.isSupportTouchEvent()) { | ||
this.eventMoveToken = on(this.domNode, 'touchmove', this.onDragMove, { passive: false }); | ||
this.eventUpToken = on(this.domNode, 'touchend', this.onDragUp, { passive: false }); | ||
on(this.domNode, 'touchcancel', this.releaseMoves); | ||
} | ||
} | ||
|
||
if (!this.isDragStatus) { | ||
this.deltaX = 0; | ||
this.deltaY = 0; | ||
this.isDragStatus = true; | ||
this.x = this.getClientX(event); | ||
this.y = this.getClientY(event); | ||
} | ||
|
||
event.preventDefault(); | ||
} | ||
|
||
/** | ||
* These releases all of the listeners on document.body. | ||
*/ | ||
releaseMoves() { | ||
if (this.eventMoveToken) { | ||
this.eventMoveToken.off(); | ||
this.eventMoveToken = null; | ||
} | ||
|
||
if (this.eventUpToken) { | ||
this.eventUpToken.off(); | ||
this.eventUpToken = null; | ||
} | ||
|
||
if (this.animationFrameID !== null) { | ||
cancelAnimationFrame(this.animationFrameID); | ||
this.animationFrameID = null; | ||
} | ||
|
||
if (this.isDragStatus) { | ||
this.isDragStatus = false; | ||
this.x = 0; | ||
this.y = 0; | ||
} | ||
} | ||
|
||
/** | ||
* Returns whether or not if the mouse movement is being tracked. | ||
*/ | ||
isDragging = () => this.isDragStatus; | ||
|
||
/** | ||
* Calls onMove passed into constructor and updates internal state. | ||
*/ | ||
onDragMove = (event: MouseEvent | TouchEvent) => { | ||
const x = this.getClientX(event); | ||
const y = this.getClientY(event); | ||
|
||
this.deltaX += x - this.x; | ||
this.deltaY += x - this.y; | ||
|
||
if (this.animationFrameID === null) { | ||
// The mouse may move faster then the animation frame does. | ||
// Use `requestAnimationFrame` to avoid over-updating. | ||
this.animationFrameID = requestAnimationFrame(this.didDragMove); | ||
} | ||
|
||
this.x = x; | ||
this.y = y; | ||
|
||
this.moveEvent = event; | ||
event.preventDefault(); | ||
}; | ||
|
||
didDragMove = () => { | ||
this.animationFrameID = null; | ||
this.onMove(this.deltaX, this.deltaY, this.moveEvent); | ||
|
||
this.deltaX = 0; | ||
this.deltaY = 0; | ||
}; | ||
/** | ||
* Calls onMoveEnd passed into constructor and updates internal state. | ||
*/ | ||
onDragUp = event => { | ||
if (this.animationFrameID) { | ||
this.didDragMove(); | ||
} | ||
this.onMoveEnd?.(event); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import * as lib from '../src'; | ||
import simulant from 'simulant'; | ||
|
||
describe('PointerMoveTracker', () => { | ||
beforeEach(() => { | ||
document.body.innerHTML = window.__html__['test/html/PointerMoveTracker.html']; | ||
}); | ||
|
||
it('Should track for mouse events', done => { | ||
const target = document.getElementById('drag-target'); | ||
let tracker = null; | ||
|
||
const handleDragMove = (x, y, e) => { | ||
if (e instanceof MouseEvent) { | ||
if (x && y) { | ||
expect(x).to.equal(100); | ||
expect(y).to.equal(100); | ||
} | ||
} | ||
}; | ||
|
||
const handleDragEnd = () => { | ||
tracker.releaseMoves(); | ||
tracker = null; | ||
done(); | ||
}; | ||
|
||
function handleStart(e) { | ||
if (!tracker) { | ||
tracker = new lib.PointerMoveTracker(document.body, { | ||
onMove: handleDragMove, | ||
onMoveEnd: handleDragEnd | ||
}); | ||
|
||
tracker.captureMoves(e); | ||
} | ||
} | ||
|
||
target.addEventListener('mousedown', handleStart); | ||
|
||
simulant.fire(target, 'mousedown'); | ||
simulant.fire(document.body, 'mousemove', { clientX: 100, clientY: 100 }); | ||
simulant.fire(document.body, 'mouseup'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ describe('WheelHandler', () => { | |
true, | ||
true | ||
); | ||
|
||
wheelHandler.onWheel(mockEvent); | ||
}); | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>PointerMoveTracker</title> | ||
</head> | ||
<body> | ||
<div style="height: 1000px"> | ||
<button id="btn">drag me</button> | ||
<hr /> | ||
<div id="drag-target"> | ||
<p>drag me (fail)</p> | ||
</div> | ||
|
||
<div id="touch-target"> | ||
<p>touch me</p> | ||
</div> | ||
</div> | ||
|
||
<script type="module"> | ||
|
||
import PointerMoveTracker from '../../lib/esm/PointerMoveTracker.js'; | ||
const handleDragMove = (x, y, e) => { | ||
console.log(e instanceof TouchEvent ? 'TouchEvent:' : 'MouseEvent:', x, y); | ||
}; | ||
let tracker = null; | ||
const handleDragEnd = e => { | ||
console.log('end'); | ||
tracker.releaseMoves(); | ||
tracker = null; | ||
}; | ||
|
||
function handleStart(e) { | ||
console.log('start'); | ||
if (!tracker) { | ||
tracker = new PointerMoveTracker(document.body, { | ||
onMove: handleDragMove, | ||
onMoveEnd: handleDragEnd | ||
}); | ||
tracker.captureMoves(e); | ||
} | ||
} | ||
|
||
document.getElementById('btn').addEventListener('touchstart', handleStart); | ||
document.getElementById('btn').addEventListener('mousedown', handleStart); | ||
</script> | ||
</body> | ||
</html> |
Oops, something went wrong.