Direct link for mobile devices.
Now that I have the chance to return to the Discrete-Event Simulation project the next item to work on is touch events. I had already implemented the ability to scroll the 2D display horizontally and vertically using the keyboard and mouse, and now I wanted to add the ability to do this on a phone.
One thing I was worried about was how laptop touchscreens would handle having both mouse and touch events doing the same thing. My Windows laptop has a touchscreen and I found that touch events appear to activate the functions written for the corresponding mouse events. That is, the code from February 7th would allow me to scroll the 2D image by touch on my laptop even though only mouse events were handled.
I started looking at the way jQuery handles touch events but eventually found the direct documentation here. I prefer knowing how to do things directly and from first principles rather than relying on a framework so I was pleased to find such a clear guide.
I copied the section of code that implemented the mouse action handlers and made minor adjustments to some of the functions to reference touch events and data values in place of mouse events and data values. The code for the mouse and touch handlers is shown below. I use the same global state variables and the same handler functions renamed by appending a “T” to them, for “touch.”
I loaded the whole thing to my server, tried it out, and what do you know? It worked perfectly on the first try. I’m kind of stunned.
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
///////////////////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; } } ///////////////////touch drag events, for now only supports first touch point //var dragFlag = false; //var startDragX; //var startDragY; //var dragBaseX; //var dragBaseY; //var dragLastX; //var dragLastY; //start drag canvas.addEventListener("touchstart", function(event) { startDragT(event); }); function startDragT(e) { startDragX = globalBaseX; startDragY = globalBaseY; e.preventDefault(); var touches = e.changedTouches; dragBaseX = touches[0].pageX; dragBaseY = touches[0].pageY; dragLastX = dragBaseX; dragLastY = dragBaseY; dragFlag = true; } //end drag canvas.addEventListener("touchend", function(event) { endDragT(event); }); function endDragT(e) { e.preventDefault(); dragFlag = false; } //move while dragging canvas.addEventListener("touchmove", function(event) { doDragT(event); }); function doDragT(e) { if (dragFlag) { e.preventDefault(); var touches = e.changedTouches; var currentX = touches[0].pageX; var currentY = touches[0].pageY; 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; } */ } } //touch leaves appropriate area (control works beyond canvas) canvas.addEventListener("touchcancel", function(event) { leaveDragT(event); }); function leaveDragT(e) { e.preventDefault(); if (dragFlag) { globalBaseX = startDragX; globalBaseY = startDragY; if (!running) { drawModel(); } dragFlag = false; } } |
Reusing as much of the original logic as possible is what made this work. That said, there are a few things we need to understand.
I explicitly do not try to read touch points beyond the first. That’s why I refer directly to touches[0]
all the time. If the user wants to do something more complex it’ll have to wait.
I relied on that fact that the coordinate system for mouse events operated on the same orientation and scale as the mouse events. I don’t see any reason why this wouldn’t be the case, but you never know for sure until you verify it for yourself. I also base everything on relative moves, so as long as the different systems use the same scale and orientation everything should work as expected, regardless of the absolute coordinate values reported by the device.
This functionality works even if the device is rotated, which is nice. I didn’t have to do anything special to reinterpret the coordinates, the OS does it automatically.
This functionality works whether the page is being viewed standalone or embedded as an iframe, which also makes things easy.
The touchcancel
function doesn’t seem to ever get invoked. The touch drag event has to be initiated within the proper element (the canvas) but can continue over the entire touchscreen of the device. If the finger goes off the edge of the touchable area it seems just to involve the touchend
function. I’ll have to learn more about this.
I still need to learn more about how events are propagated and consumed but that should come with continuing work. In the meantime I’m happy something was easy for once!