diff --git a/src/events/touch.js b/src/events/touch.js index ff2587f1bc..7ada0a12e6 100644 --- a/src/events/touch.js +++ b/src/events/touch.js @@ -95,7 +95,11 @@ p5.prototype.touches = []; p5.prototype._updateTouchCoords = function(e) { if (this._curElement !== null) { const touches = []; - for (let i = 0; i < e.touches.length; i++) { + // During a touchend event, the lifted finger is removed from e.touches + // (W3C spec) and is only present in e.changedTouches. Fall back to + // e.changedTouches so that touches[] is populated inside touchEnded(). + const touchList = e.touches.length > 0 ? e.touches : e.changedTouches; + for (let i = 0; i < touchList.length; i++) { touches[i] = getTouchInfo( this._curElement.elt, this.width, diff --git a/test/unit/events/touch.js b/test/unit/events/touch.js index 776b1095c6..2f0e27dd5b 100644 --- a/test/unit/events/touch.js +++ b/test/unit/events/touch.js @@ -138,6 +138,24 @@ suite('Touch Events', function() { }); suite('touchEnded', function() { + test('touches[] should contain changedTouches data inside touchEnded', function() { + // During touchend, the W3C spec removes the lifted finger from e.touches + // and places it in e.changedTouches only. Verify that p5 surfaces those + // positions in the touches[] array so user code can read them. + let touchesInsideCallback = null; + myp5.touchEnded = function() { + touchesInsideCallback = [...myp5.touches]; + }; + const endEvent = new TouchEvent('touchend', { + touches: [], // spec: lifted touch is gone from touches + changedTouches: [touchObj1] // spec: lifted touch lives here + }); + window.dispatchEvent(endEvent); + assert.isNotNull(touchesInsideCallback); + assert.strictEqual(touchesInsideCallback.length, 1); + assert.strictEqual(touchesInsideCallback[0].id, 36); + }); + test('touchEnded must run when a touch is registered', function() { let count = 0; myp5.touchEnded = function() {