@threlte/core
Advanced tools
Comparing version 4.1.1 to 4.1.2
# @threlte/core | ||
## 4.1.2 | ||
### Patch Changes | ||
- cd07968: Validate click events to make sure the last pointerdown event hit the same instance as the click | ||
event did. This heuristic more closely resembles how the DOM works, and prevents accidental clicks | ||
while e.g. using OrbitControls or otherwise dragging on the canvas. | ||
## 4.1.1 | ||
@@ -4,0 +12,0 @@ |
@@ -11,2 +11,3 @@ import type { ThrelteContext, ThrelteRenderContext, ThrelteRootContext } from '../types/types'; | ||
* ``` | ||
* | ||
* @param ctx | ||
@@ -13,0 +14,0 @@ * @param rootCtx |
@@ -31,2 +31,3 @@ import { onDestroy } from 'svelte'; | ||
* ``` | ||
* | ||
* @param ctx | ||
@@ -44,16 +45,33 @@ * @param rootCtx | ||
onDestroy(unsubscribePointer); | ||
// to validate the click event, the click | ||
// event must intersect the same object as | ||
// the preceding pointerdown event | ||
let pointerDownOn; | ||
const onEvent = (e) => { | ||
e.preventDefault(); | ||
const eventType = e.type; | ||
ctx.pointerOverCanvas.set(true); | ||
renderCtx.pointerInvalidated = true; | ||
setPointerFromEvent(ctx, e); | ||
if (rootCtx.interactiveObjects.size === 0 || rootCtx.raycastableObjects.size === 0) | ||
// maybe get an intersection with an object | ||
const intersection = getFirstIntersection(rootCtx, pointer, camera); | ||
if (eventType === 'pointerdown') { | ||
// Remember which object was pressed in order to validate the next click event | ||
pointerDownOn = intersection | ||
? { object: intersection.object, instanceId: intersection.instanceId } | ||
: null; | ||
} | ||
if (eventType === 'click') { | ||
if (!isValidClickEvent(intersection, pointerDownOn)) { | ||
pointerDownOn = null; | ||
return; | ||
} | ||
pointerDownOn = null; | ||
} | ||
if (!intersection) | ||
return; | ||
const intersects = runRaycaster(rootCtx, pointer, camera, Array.from(rootCtx.raycastableObjects)); | ||
if (intersects.length > 0 && rootCtx.interactiveObjects.has(intersects[0].object)) { | ||
getThrelteUserData(intersects[0].object).eventDispatcher?.(e.type, { | ||
...intersects[0], | ||
event: e | ||
}); | ||
} | ||
getThrelteUserData(intersection.object).eventDispatcher?.(eventType, { | ||
...intersection, | ||
event: e | ||
}); | ||
}; | ||
@@ -68,3 +86,23 @@ return { | ||
}; | ||
function getFirstIntersection(rootCtx, pointer, camera) { | ||
if (rootCtx.interactiveObjects.size === 0 || rootCtx.raycastableObjects.size === 0) | ||
return null; | ||
const intersects = runRaycaster(rootCtx, pointer, camera, Array.from(rootCtx.raycastableObjects)); | ||
if (intersects.length === 0 || !rootCtx.interactiveObjects.has(intersects[0].object)) | ||
return null; | ||
return intersects[0]; | ||
} | ||
/** | ||
* Validates a click event to make sure the last pointerdown event | ||
* hit the same instance as the click event did. This heuristic | ||
* resembles how the DOM works and prevents accidental clicks while e.g. | ||
* using OrbitControls. | ||
*/ | ||
function isValidClickEvent(intersection, pointerDownOn) { | ||
if (!intersection || !pointerDownOn) | ||
return false; | ||
return (intersection.object.uuid === pointerDownOn.object.uuid && | ||
intersection.instanceId === pointerDownOn.instanceId); | ||
} | ||
/** | ||
* Some events can't be captured on Mouse- or PointerEvents. | ||
@@ -71,0 +109,0 @@ * Specifically pointerleave and pointerenter are hard to capture |
{ | ||
"name": "@threlte/core", | ||
"version": "4.1.1", | ||
"version": "4.1.2", | ||
"author": "Grischa Erbe <hello@legrisch.com> (https://legrisch.com)", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
202294
2890