Today I added the ability to use the mouse to drag the 2D display around within its canvas. I had to update the various pan functions to allow specification of an arbitrary distance increment, though there are a number of ways I could have done that. I may change things up going forward. In the meantime, the code sets up events for mousedown, mousemove, and mouseup events and, when the dragFlag
is set to true, goes ahead and moves the 2D display by the requested increments in both the x and y directions.
I was a little bit stunned when it worked perfectly the first time I tried it, and it also works smoothly when the simulation is running full blast. Fun!
Here’s the new code for handling the mouse events:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
///////////////////mouse drag events var dragFlag = false; var startDragX; var startDragY; var dragBaseX; var dragBaseY; var dragLastX; var dragLastY; //start drag canvas.addEventListener("mousedown", function(event) { startDrag(event); }); function startDrag(e) { startDragX = globalBaseX; startDragY = globalBaseY; dragBaseX = e.clientX; dragBaseY = e.clientY; dragLastX = dragBaseX; dragLastY = dragBaseY; dragFlag = true; } //end drag canvas.addEventListener("mouseup", function(event) { endDrag(event); }); function endDrag(e) { dragFlag = false; } //move while dragging canvas.addEventListener("mousemove", function(event) { doDrag(event); }); function doDrag(e) { if (dragFlag) { var currentX = e.clientX; var currentY = e.clientY; var incrementX = currentX - dragLastX; if (incrementX > 0) { panLeft(incrementX) } else if (incrementX < 0) { panRight(-incrementX) } var incrementY = currentY - dragLastY; if (incrementY > 0) { panUp(incrementY); } else if (incrementY < 0) { panDown(-incrementY); } dragLastX = currentX; dragLastY = currentY; //eat the event if (e.stopPropagation) { e.stopPropagation() } else { e.cancelBubble = true; } } } //mouse leaves canvas canvas.addEventListener("mouseleave", function(event) { leaveDrag(event); }); function leaveDrag(e) { if (dragFlag) { globalBaseX = startDragX; globalBaseY = startDragY; if (!running) { drawModel(); } dragFlag = false; } } |
And here’s the updated code for the pan functions. I left the limits unchanged for now.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
var xPanMin = -200; //globalBaseX var xPanMax = 200; var xPanInc = 2; var yPanMin = -200; //globalBaseY var yPanMax = 200; var yPanInc = 2; function panDown(increment) { if (typeof increment === "unknown") {increment = yPanInc;} globalBaseY += increment; if (globalBaseY > yPanMax) { globalBaseY = yPanMax; } if (!running) { drawModel(); } } function panUp(increment) { if (typeof increment === "unknown") {increment = yPanInc;} globalBaseY -= increment; if (globalBaseY < yPanMin) { globalBaseY = yPanMin; } if (!running) { drawModel(); } } function panRight(increment) { if (typeof increment === "unknown") {increment = xPanInc;} globalBaseX += increment; if (globalBaseX > xPanMax) { globalBaseX = xPanMax; } if (!running) { drawModel(); } } function panLeft(increment) { if (typeof increment === "unknown") {increment = xPanInc;} globalBaseX -= increment; if (globalBaseX < xPanMin) { globalBaseX = xPanMin; } if (!running) { drawModel(); } } function resetPan() { globalBaseX = 0; globalBaseY = 0; if (!running) { drawModel(); } } document.onkeydown = checkKey; function checkKey(e) { e = e || window.event; //up 38 down 40 if (e.keyCode == '37') { //left arrow panLeft(); } else if (e.keyCode == '39') { //right arrow panRight(); } else if (e.keyCode == '38') { //up arrow panUp(); } else if (e.keyCode == '40') { //down arrow panDown(); } else if (e.keyCode == '66') { //b key } else if (e.keyCode == '70') { //f key } else if (e.keyCode == '36') { //home key resetPan(); } //eat the event if (e.stopPropagation) { e.stopPropagation() } else { e.cancelBubble = true; } } |
I found it necessary to handle the mouseleave event because it was possible to mouse out of the canvas, release the mouse, return to the canvas, and still be dragging the 2D display. In order to get out of that the user would have to do a new mousedown and mouseup to properly clear the state. Perhaps it would be better to only snap back to the drag origin when the user releases the mouse when outside the canvas, when a drag sequence is in progress.
The drag mouse event code to eat that event, or prevent it from bubbling up through further elements, does run when expected, but I can’t be sure it’s doing anything. It runs when using the keystrokes as well, but the entire window will still scroll up or down when the 2D display reaches its upper or lower limits. It’s bad enough when running this page standalone but when its embedded on a WordPress page with ten other posts it wants to scroll the whole window down rather than ever scrolling the 2D display back up. This means either that the event that scrolls the larger window up and down is being processed at a lower level that the canvas and is being allowed to bubble up to the canvas, or that it isn’t actually being stopped from propagating.
I need to get a much, much better feel for how the whole event mechanism works in JavaScript, HTML, and the DOM. It’s amazing how far you can get without going into depth in some really important areas. The explanations I’ve read seem simple enough, and of course I’ve been using event-driven frameworks forever, but my work didn’t usually require me to dig into the details. I suspect this is going to keep me hopping.
In the longer run there’s a lot more to add here. For example, the canvas should only allow the user to drag when the entire image is not in view, and even then perhaps in certain modes, which themselves must be controllable. Anyway, one piece at a time, especially when I’m busy with other things.
Last but not least I ended up learning a lot more about how WebGL runs — or fails to run — on different browsers at different times. I’ll relate that saga tomorrow.