By Robert Laing
Learning coding is like playing cards — you learn the rules, then you play, then you go back and learn the rules again, then you play again. — Mozilla’s Web Audio API page
Via JavaScript’s Element.addEventListener(type, listener), events such as keyboard input or mouse clicks can get dispatched to listener handlers.
document.addEventListener("DOMContentLoaded", startGame);
DOM.newgame.addEventListener("click", newGame);
document.addEventListener("pointerdown", pointerdownHandler);
document.addEventListener("pointermove", pointermoveHandler);
document.addEventListener("pointerup", pointerupHandler);
document.addEventListener("pointercancel", pointerupHandler);
document.addEventListener("ondragstart", () => false);The types of events is fairly overwhelming, so I’ve focused on just using pointer events.
The Pointer Events specification defines a unified hardware-agnostic framework for handling input from various devices, including mice, touchscreens, and pens/styluses. By providing a single set of events (e.g., pointerdown, pointermove, pointerup), it allows developers to support diverse input methods without writing device-specific logic for each. — W3C Specification
Setting ondragstart to false is to disable
the default drag
and drop event which I don’t want to use because it assumes mouse
events, so doesn’t work on mobile phones.
The drag event’s
ev.dataTransfer.setData("text/plain", ev.target.innerText);
can be done more simply using an object literal which the
pointer event handlers can all access by making it global in their
module.
/**
* Data is moved from pointerup to pointerdown via this object.
* @namespace {Object} dragObject
* @property {Object} clone - HTML img element getting dragged
* @property {string} fromID - Start div's ID.
* @property {string[]} legals - IDs of legal move targets.
* @property {boolean} moving - Set to true by pointer down and false by pointer up.
* @property {number} shiftX - Pixels to offset x.
* @property {number} shiftY - Pixels to offset y.
* @property {?string} toID - End div's ID or null.
*/
const dragObject = {moving: false};Something I didn’t think of initially is that on a smartphone, people can use several fingers to drag more than one card simultaniously. There may be reasons to allow that, but since I’m trying to use the same code for a mouse or finger, a simple way to fix that was having the pointerdown handler check three things:
null
for images.Since I want to keep the original image’s size, dragObject.clone
= pointerEvent.target.cloneNode();. This replaces
pointerEvent.target, leaving it free to represent whatever
is under the dragged image — the next card in a deck or blank.
By tagging a “not convertible to JSON” type (a DOM element)
to dragObject, I lose the ability to store it in localStorage,
which is why I don’t simply add its properties to state.
The idea is dragObject holds ephemeral data which only updates the state
once a legal move is completed via pointerdown. (There
should probably be an undo option then, but I’ve not got that advanced
yet).
/**
* @function pointerdownHandler
* @listens pointerdown
*/
function pointerdownHandler(pointerEvent) {
if ( !dragObject.moving &&
pointerEvent.target.localName === "img" &&
!pointerEvent.target.src.includes("blank")
) {
dragObject.fromID = getId(pointerEvent.target);
pointerEvent.preventDefault();
dragObject.moving = true;
dragObject.toID = null;
DOM[dragObject.fromID].setPointerCapture(pointerEvent.pointerId);
dragObject.clone = pointerEvent.target.cloneNode();
dragObject.clone.style.position = "absolute";
dragObject.clone.style.left = `${DOM[dragObject.fromID].offsetLeft}px`;
dragObject.clone.style.top = `${DOM[dragObject.fromID].offsetTop}px`;
dragObject.clone.style["z-index"] = 12;
document.body.append(dragObject.clone);
dragObject.shiftX = pointerEvent.clientX - dragObject.clone.getBoundingClientRect().left;
dragObject.shiftY = pointerEvent.clientY - dragObject.clone.getBoundingClientRect().top;
dragObject.clone.style.filter = `drop-shadow(3px 3px hsl(120 100 20 / 80%))`;
pointerEvent.target.src = "./cards/blank.svg";
highlightLegals();
}
}Note at this stage I’m only changing things in the DOM, not the game state which only gets updated on the completion of a legal move.