diff --git a/demo_life/index.html b/demo_life/index.html
index cfd1be1..df4532d 100644
--- a/demo_life/index.html
+++ b/demo_life/index.html
@@ -60,13 +60,10 @@
const customDot_ = localStorage.getItem('customDot')
let customDot = customDot_ == null ? null : parseInt(customDot_)
let customDotValue = localStorage.getItem('customDotValue')
-let inputs = {
- /** @type 'down' | 'up' | 'firstDown' */
- pointerState: 'up',
- pointer: {x: -Infinity, y: -Infinity}, // btw, on page load, there's no way to render a first cursor state =(
- clicked: false,
- tooltipValue: customDotValue,
-}
+/** @type 'down' | 'up' | 'firstDown' */
+let pointerState = 'up'
+let pointer = {x: -Infinity, y: -Infinity} // btw, on page load, there's no way to render a first cursor state =(,
+let events = {click: null, input: null, mouseup: null, mousemove: null, pointerdown: null}
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
@@ -80,32 +77,11 @@
// === events
// pointermove doesn't work on android, pointerdown isn't fired on Safari on the first left click after dismissing context menus, mousedown doesn't trigger properly on mobile, pointerup isn't triggered when pointer panned (at least on iOS), don't forget contextmenu event. Tldr there's no pointer event that works cross-browser that can replace mouse & touch events.
window.addEventListener('resize', () => render())
-window.addEventListener('click', (e) => {
- inputs.clicked = true
- // needed to update coords even when we already track mousemove. E.g. in Chrome, right click context menu, move elsewhere, then click to dismiss. BAM, mousemove triggers with stale/wrong (??) coordinates... Click again without moving, and now you're clicking on the wrong thing
- inputs.pointer.x = e.pageX - window.scrollX; inputs.pointer.y = e.pageY - window.scrollY
- render()
-})
-tooltip.addEventListener('input', (e) => {
- inputs.tooltipValue = e.target.value
- render()
-})
-window.addEventListener("mouseup", (e) => {
- inputs.pointerState = 'up'
- render()
-})
-window.addEventListener('mousemove', (e) => {
- // when scrolling (which might schedule a render), a container's mousemove doesn't trigger, so the pointer's local coordinates are stale
- // this means we should only use pointer's global coordinates, which is always right (thus the subtraction of scroll)
- inputs.pointer.x = e.pageX -/*toGlobal*/window.scrollX; inputs.pointer.y = e.pageY -/*toGlobal*/window.scrollY
- // btw, pointer can exceed document bounds, e.g. dragging reports back out-of-bound, legal negative values
- render()
-})
-window.addEventListener('pointerdown', (e) => {
- inputs.pointerState = 'firstDown'
- inputs.pointer.x = e.pageX - window.scrollX; inputs.pointer.y = e.pageY - window.scrollY
- render()
-})
+window.addEventListener('click', (e) => {events.click = e; render()})
+tooltip.addEventListener('input', (e) => {events.input = e; render()})
+window.addEventListener("mouseup", (e) => {events.mouseup = e; render()})
+window.addEventListener('mousemove', (e) => {events.mousemove = e; render()})
+window.addEventListener('pointerdown', (e) => {events.pointerdown = e; render()})
// === hit testing logic. Boxes' hit area should be static and not follow their current animated state usually (but we can do either). Use the dynamic area here for once
function hitTest(blockSize, countX, countY, gridLeft, gridTop, gridFinalSizeX, gridFinalSizeY, pointer) {
@@ -116,13 +92,37 @@
}
function render() {
+ // === step 0: process events
+ // click
+ let clicked = false
+ if (events.click != null) {
+ clicked = true
+ // needed to update coords even when we already track mousemove. E.g. in Chrome, right click context menu, move elsewhere, then click to dismiss. BAM, mousemove triggers with stale/wrong (??) coordinates... Click again without moving, and now you're clicking on the wrong thing
+ pointer.x = events.click.pageX - window.scrollX; pointer.y = events.click.pageY - window.scrollY
+ }
+ // input
+ const newCustomDotValue = events.input == null ? null : events.input.target.value
+ // mouseup
+ if (events.mouseup != null) pointerState = 'up'
+ // mousemove
+ if (events.mousemove != null) {
+ // when scrolling (which might schedule a render), a container's mousemove doesn't trigger, so the pointer's local coordinates are stale
+ // this means we should only use pointer's global coordinates, which is always right (thus the subtraction of scroll)
+ pointer.x = events.mousemove.pageX -/*toGlobal*/window.scrollX; pointer.y = events.mousemove.pageY -/*toGlobal*/window.scrollY
+ // btw, pointer can exceed document bounds, e.g. dragging reports back out-of-bound, legal negative values
+ }
+ // pointerdown
+ if (events.pointerdown != null) {
+ pointerState = 'firstDown'
+ pointer.x = events.pointerdown.pageX - window.scrollX; pointer.y = events.pointerdown.pageY - window.scrollY
+ }
+
// === step 1: batched DOM reads (to avoid accidental DOM read & write interleaving)
const devicePixelRatio = window.devicePixelRatio
const windowSizeX = document.documentElement.clientWidth
const windowSizeY = window.innerHeight
const gridSizeX = windowSizeX - gridGap
const gridSizeY = windowSizeY - gridGap
- let {pointer} = inputs
const blockSize_ = (life * Math.sqrt((4*life*gridSizeX*gridSizeY + (7*gridSizeY)**2) / (life**2)) + 7 * gridSizeY) / 2 / life
const countX = Math.floor((gridSizeX / blockSize_) / 7) * 7
@@ -136,8 +136,8 @@
// === step 2: handle inputs-related state change
const hover = hitTest(blockSize, countX, countY, gridLeft, gridTop, gridFinalSizeX, gridFinalSizeY, pointer)
const newCustomDot =
- inputs.clicked && hover === customDot ? null
- : inputs.clicked ? hover
+ clicked && hover === customDot ? null
+ : clicked ? hover
: customDot
const cursor =
hover == null ? 'auto'
@@ -224,21 +224,20 @@
} else {
tooltip.style.display = hover != null && hover === newCustomDot ? 'block' : 'none'
tooltip.disabled = false
- tooltip.value = inputs.tooltipValue
+ tooltip.value = newCustomDotValue == null ? customDotValue : newCustomDotValue
}
tooltip.focus()
// === step 6: update state & prepare for next frame
- inputs.clicked = false
- // inputs.tooltipValue = null
if (customDot !== newCustomDot) {
localStorage.setItem('customDot', newCustomDot) // TODO: validate (e.g. out of range)
customDot = newCustomDot
}
- if (customDotValue !== inputs.tooltipValue) {
- localStorage.setItem('customDotValue', inputs.tooltipValue) // TODO: validate (e.g. out of range)
- customDotValue = inputs.tooltipValue
+ if (newCustomDotValue != null && customDotValue !== newCustomDotValue) {
+ localStorage.setItem('customDotValue', newCustomDotValue) // TODO: validate (e.g. out of range)
+ customDotValue = newCustomDotValue
}
+ events.click = events.input = events.mouseup = events.mousemove = events.pointerdown = null
}
render()