Skip to main content
Code Review

Return to Revisions

5 of 5
Commonmark migration

Limiting updates

You are correct about mouse event rates, I have seen mouse event 2ms apart. However touch events seem to be in sync with the display 60FPS, I have never seen a touch event fire at rates higher than this (but that is a hardware attribute as far as I know).

But that is besides the point. It is always a good idea to decouple the rendering from random events, be they user input, or from some other source. The display is driven at a fixed rate and to maintain good quality animations and presentation, keeping it tightly synced to that rate is important.

Some style a code points.

  • window is the default object you don't need to use it. window.removeEventListener is the same as removeEventListener and conversely your function throttleRedraw is the same as window.throttleRedraw

  • It is not good to have statement block without delimiting parenthesis as it can easily be missed when making modifications. if (ondraw) ondraw(now); is better as if (ondraw) { ondraw(now) } the ; is optional as the line has ended in a }

  • You don't have to have a separate event handler for each event type. Many of the event properties are repeated and you end up duplicating code. Create a single handler that handles all the events of similar type.

  • The DOM does it all, but the DOM does it badly, adding and removing event handlers is just adding more work to the DOM and can be more efficiently handled by your JavaScript. Set handlers once and ignore events not needed

  • Too many comments. Comment only code that is not self evident; any other comments are just noise and make the code harder to read.

Design

From a design point I would not tie the mouse/touch event handlers so tightly with the rendering functionality. It is likely that there will be additional event like sources that require updated rendering. Completely decoupling the rendering from the pointer like events will make the system more flexible.

Touch and mouse are abstractly the same, try to reduce the individual abstraction and use a common pointer like abstract to name and define behaviors.

The flag absolute is an over complication. Just keep a pointer delta that contains the relative pointer motion and let the handling code pick which to use.

Example

The example decouples the pointer from the rendering.

The pointer is created and listens to all relevant events and sets its properties x, y, down... and so on. The pointer also correct the coords to be relative to the target top left.

The render start up and creates a pointer object setting its target to the canvas The main loop continues to run and will only update if there is a change to the pointer.

The result is that the rendering becomes more complex, but that is offset by greatly reducing the pointer event handling code.

This also means that you could add keyboard events, or have running animations (like selection box dash movement) and not be reliant on the mouse event handler to pass appropriate render calls

Note that it listens to document events and filters out events not triggered by the active element. (THE snippet has modified behavior and does not allow event outside the snippet window. The event handler has the target test commented out to improve it a little, but it is not an accurate representation of how it works on an independent page).

function createPointer(target) {
 var currentTarget;
 var bounds;
 const types = {
 mouse: 1, touch: 2, move: 1, up: 2, end: 2, cancel: 2, start: 0, down: 0,
 names : "touchcancel,touchend,touchmove,touchstart,mousemove,mousedown,mouseup".split(","),
 }
 const pointer = {
 delta: {x : 0, y : 0},
 pos: {x : 0, y : 0, coord(x,y) { this.x = x; this.y = y }},
 last: {x : 0, y : 0, set from(src) { this.x = src.x; this.y = src.y }},
 down : false,
 active : false,
 changed : false,
 set position(coord) {
 pointer.last.from = pointer.pos;
 pointer.pos.coord(coord.clientX- bounds.left + scrollX, coord.clientY - bounds.top + scrollY);
 pointer.delta.x = pointer.pos.x - pointer.last.x;
 pointer.delta.y = pointer.pos.y - pointer.last.y;
 },
 set target(element){
 if(!element){
 types.names.forEach(event => removeEventListener(event, events) );
 currentTarget = undefined;
 pointer.active = false;
 }else {
 if (!currentTarget) { types.names.forEach(event => addEventListener(event, events) ) }
 currentTarget = element;
 bounds = element.getBoundingClientRect()
 pointer.active = true;
 }
 pointer.down = false;
 pointer.changed = true;
 }
 }
 const events = event => {
 // if (event.target === currentTarget) {
 const type = types[event.type.substring(5)];
 pointer.position = types[event.type.substring(0,5)] === types.touch ? event.touches[0] : event;
 if (type === types.down) { pointer.down = true }
 else if (type === types.up) { pointer.down = false }
 pointer.changed = true;
 event.preventDefault();
 // }
 }
 pointer.target = target;
 return pointer;
}
const rendering = (()=> {
 requestAnimationFrame(mainLoop);
 const canvas = document.querySelector('canvas');
 const ctx = canvas.getContext('2d');
 const pointer = createPointer(canvas);
 var update = false;
 
 function as(src) { this.x = src.x; this.y = src.y }
 const drag = {
 active : false,
 start : {x : 0, y : 0, as},
 end : {x : 0, y : 0, as},
 draw() {
 ctx.fillStyle = "red";
 ctx.fillRect(
 Math.min(drag.start.x, drag.end.x), Math.min(drag.start.y, drag.end.y),
 Math.abs(drag.start.x - drag.end.x), Math.abs(drag.start.y - drag.end.y),
 )
 }
 }
 
 function checkPointer() {
 if (pointer.changed) {
 if (pointer.down) {
 if (!drag.active) {
 drag.start.as(pointer.pos);
 drag.active = true;
 }
 drag.end.as(pointer.pos);
 update = true;
 } else if (drag.active) {
 drag.end.as(pointer.pos);
 drag.active = false;
 update = true;
 }
 pointer.changed = false;
 }
 }
 
 function mainLoop(time) {
 checkPointer();
 if (update) {
 ctx.clearRect(0,0,canvas.width,canvas.height);
 drag.draw();
 update = false;
 }
 requestAnimationFrame(mainLoop);
 }
})();
<canvas width="400" height="400" style="border: 2px solid #888"></canvas>

Blindman67
  • 22.8k
  • 2
  • 16
  • 40
default

AltStyle によって変換されたページ (->オリジナル) /