Events revisited
Now that we've introduced events and DOM manipulations, we can revisit events and flesh out the examples some more.
Keyboard events
The keyboard events are keyup
, keydown
and keypress
, all which send the same event data. To find out which key is pressed, use key
or keyCode
. It's easiest to experiment pressing the keys to find out what data each one yields.
Also useful are a set of booleans which track whether modifier keys are pressed, such as the shift or control keys. These properties are: ctrlKey
, shiftKey
, altKey
and metaKey
.
Try this live demo to explore different key events.
Note that it depends on what element has 'focus' as to whether it will trigger keyboard events or not. Elements like a text INPUT
will gobble up keyboard events.
Below is an example that uses shiftKey
and key
properties of the event data to move an element around according to the cursor keys.
HTML
<div id="result">
Try your cursor keys
</div>
<div id="box"></div>
CSS
#box {
position: fixed;
width: 1rem;
height: 1rem;
top: 0px;
left: 0px;
background-color: DeepPink;
}
JS
document.addEventListener('keydown', (e) => {
let speed = 1;
if (e.shiftKey) speed = 10;
switch (e.key) {
case "ArrowLeft":
update("Left");
move(-1*speed, 0);
break;
case "ArrowRight":
update("Right");
move(1*speed, 0);
break;
case "ArrowUp":
update("Up");
move(0, -1*speed);
break;
case "ArrowDown":
update("Down");
move(0, 1*speed);
break;
default:
update("!?");
}
});
function update(m) {
document.getElementById('result').innerHTML = m;
}
function move(x, y) {
const el = document.getElementById('box');
el.style.left = (el.offsetLeft + x) + 'px';
el.style.top = (el.offsetTop + y) + 'px';
}
Pointer events
Pointer events allow for any kind of interaction with a mouse, touch or stylus to be handled uniformly. On some devices, you can pick up the pressure of touch points, or for stylus input, pressure as well as the tilt of the pen and so forth.
The events are: pointerover, pointerenter, pointerdown, pointermove, pointerup, pointercancel, pointerout
and pointerleave
.
These events all fire PointerEvent data. Useful properties are:
- pointerId: Id of pointer causing event, useful for following individual fingertips
- clientX/clientY: coordinates
- offsetX/offsetY: coordinates in relation to event target
- pageX/pageY: coordinates in relation to document
- screenX/screenY: coordinates in relation to screen
- movementX/movementY: amount of movement since the last
mousemove
- pointerType: Commonly "mouse" or "touch"
Like the keyboard events described above, the pointer events also include booleans for whether the user is holding a modifier key during the action. These properties are: altKey, ctrlKey, metaKey
and shiftKey
.
Read more
- Pointer Events (MDN)
Interacting with elements
To associate the pointer with an element there are a few approaches.
The first is to attach your event handler to an element which contains all the elements you're interested in tracking, and then using the event data's target property to figure out which one was interacted with.
The below example listens to events on the SECTION
element, rather than listening on each individual DIV
. Element attributes are useful to associate visuals with data. For example, numeric attribute values could map to an array of data. In this case though, we just print out the value, and toggle a CSS class to show which element was the target.
<section>
<div data-attr="0">One</div>
<div data-attr="1">Two</div>
<div data-attr="2">Three</div>
</section>
function handle(e) {
const attr = e.target.getAttribute('data-attr');
// If there's no data-attr, the event didn't come from a child DIV but the SECTION. Igore these.
if (attr == null) return;
document.getElementById('log').innerText = ` ${e.type} fired with target ${attr}`;
// 'Select' DIV if clicked
if (e.type == "pointerup") {
if (!e.target.classList.contains('active')) {
// Remove the 'active' class from any other element
var x = document.getElementsByClassName('active');
for (var withClass of x) withClass.classList.remove('active');
// Add it to the one that's the current event target
e.target.classList.add('active');
}
}
}
// Listen to pointer events that happen within the section
const el = document.querySelector('section');
el.addEventListener("pointerup", handle);
el.addEventListener("pointerdown", handle);
el.addEventListener("pointermove", handle);
Another approach is to use hit testing - using the coordinates of the event to figure out what elements are relevant.
document.elementFromPoint returns the top-most element for a viewport coordinate. You can also calculate which elements intersect a coordinate by iterating through a list of elements you care about and checking their bounding rectangle.
// Returns TRUE if x,y is within rect
const within = (x, y, rect) => {
if (x < rect.left || y < rect.y) return false;
if (x > rect.left + rect.width) return false;
if (y > rect.top + rect.height) return false;
return true;
}
// Listen to pointer events that happen anywhere on document
document.addEventListener('pointermove', (e) => {
// Remove the 'active' class from all elements
const x = document.getElementsByClassName('active');
for (let withClass of x) withClass.classList.remove('active');
// Enumerate all children of the section
for (let child of document.querySelector('section').children) {
const r = child.getBoundingClientRect();
if (within(e.clientX, e.clientY, r)) {
// Overlapping! Add 'active' class to show it
child.classList.add('active');
}
}
});
This is only a simple example and doesn't handle all situations, mind you.