UNPKG

@rbuljan/gamepad

Version:

Multi-touch gamepad with buttons and joystick for JavaScript games, apps, IOT

203 lines 8.29 kB
import { createElement, normalize, userSelectNoneForAllBrowsers, } from "./utils"; export class Controller { constructor(options, type) { this.type = type; /** * the current and changing state of the controller */ this.state = { isPressed: false, isActive: false, isDrag: false, value: 0, angle: 0, x_start: 0, y_start: 0, x_diff: 0, y_diff: 0, x_drag: 0, y_drag: 0, dragDistance: 0, pointerIdentifier: -1, isInitialized: false, }; this.options = { id: "", parentElement: document.querySelector("body"), radius: 40, spring: true, fixed: true, position: { top: "50%", left: "50%" }, axis: "all", text: "", style: Object.assign({ color: "hsla(0, 90%, 100%, 0.5)", border: "2px solid currentColor" }, options.style), }; this.options = Object.assign(this.options, options); this.isJoystick = this.type === "joystick"; this.handleStart = this.handleStart.bind(this); this.handleMove = this.handleMove.bind(this); this.handleEnd = this.handleEnd.bind(this); let axisName = this.type; this.parentElement = this.options.parentElement; this.anchorElement = createElement("div", { className: "Gamepad-anchor", }); this.gamepadControllerElement = createElement("div", { id: this.options.id, innerHTML: this.options.text, className: `Gamepad-controller Gamepad-${axisName} axis-${axisName}`, }); } onInput() { if (this.options.onInput) { this.options.onInput(this.state); } } onStart() { } onMove() { } onEnd() { } _noDefault(evt) { evt.preventDefault(); } // Get relative mouse coordinates getPointerXY(evt) { const { clientX, clientY } = evt; const { left, top } = this.parentElement.getBoundingClientRect(); return { x: clientX - left, y: clientY - top, }; } handleStart(evt) { // Is already assigned? Do nothing if (this.state.pointerIdentifier > -1) { return; } // If a Gamepad Button was touched, don't do anything with the Joystick const target = evt.target; if (this.isJoystick && target.closest(".Gamepad-Button")) { return; } evt.preventDefault(); this.parentElement.setPointerCapture(evt.pointerId); const { x, y } = this.getPointerXY(evt); this.state.isPressed = true; this.state.isActive = this.options.spring ? true : !this.state.isActive; this.state.pointerIdentifier = evt.pointerId; this.state.x_start = x; this.state.y_start = y; if (!this.options.fixed) { this.anchorElement.style.left = `${this.state.x_start}px`; this.anchorElement.style.top = `${this.state.y_start}px`; } this.gamepadControllerElement.classList.toggle("is-active", this.isJoystick ? this.state.isPressed : this.state.isActive); this.onStart(); } handleMove(evt) { if (!this.parentElement.hasPointerCapture(evt.pointerId) || !this.state.isPressed || this.state.pointerIdentifier < 0) { return; } evt.preventDefault(); const { x, y } = this.getPointerXY(evt); this.state.isDrag = true; this.state.x_drag = x; this.state.y_drag = y; this.state.x_diff = this.state.x_drag - this.state.x_start; this.state.y_diff = this.state.y_drag - this.state.y_start; this.state.dragDistance = Math.min(this.options.radius, Math.sqrt(this.state.x_diff * this.state.x_diff + this.state.y_diff * this.state.y_diff)); // Finally set the angle (normalized) this.state.angle = normalize(Math.atan2(this.state.y_diff, this.state.x_diff)); this.onMove(); } handleEnd(evt) { // If touch was not registered on touch-start - do nothing if (this.state.pointerIdentifier < 0) { return; } // If a Gamepad Button was touched, don't do anything with the Joystick const target = evt.target; if (this.isJoystick && target.closest(".Gamepad-Button")) { return; } this.parentElement.releasePointerCapture(evt.pointerId); this.state.pointerIdentifier = -1; this.state.isDrag = false; this.state.isPressed = false; if (this.options.spring) { this.state.isActive = false; } this.gamepadControllerElement.classList.toggle("is-active", this.isJoystick ? this.state.isPressed : this.state.isActive); this.onEnd(); } init() { if (this.state.isInitialized) { this.destroy(); } this.state.isInitialized = true; // Styles for both Joystick and Button const stylesCommon = Object.assign({ boxSizing: "content-box", transform: "translate(-50%, -50%)", position: "absolute", fontSize: `${this.options.radius}px`, borderRadius: `${this.options.radius * 2}px`, display: "inline-flex", justifyContent: "center", alignItems: "center", cursor: "default" }, userSelectNoneForAllBrowsers); // Styles depending on controller type/axis let stylesByAxisType = { minWidth: `${this.options.radius * 2}px`, height: `${this.options.radius * 2}px`, }; if (this.options.axis === "x") { stylesByAxisType = { width: `${this.options.radius * 2}px`, height: `6px`, }; } if (this.options.axis === "y") { stylesByAxisType = { height: `${this.options.radius * 2}px`, width: `6px`, }; } const gamepadControllerStyles = Object.assign(Object.assign(Object.assign({}, stylesCommon), stylesByAxisType), this.options.style); // Add styles - Controller anchor Object.assign(this.anchorElement.style, Object.assign(Object.assign({ position: "absolute", width: "0", height: "0", touchAction: "none" }, userSelectNoneForAllBrowsers), this.options.position)); // Add styles - Controller Object.assign(this.gamepadControllerElement.style, gamepadControllerStyles); // Insert Elements to DOM this.anchorElement.append(this.gamepadControllerElement); this.options.parentElement.append(this.anchorElement); // Events const eventStarterElement = this.isJoystick || !this.options.fixed ? this.parentElement : this.gamepadControllerElement; eventStarterElement.addEventListener("pointerdown", this.handleStart, { passive: false, }); if (this.isJoystick) { this.parentElement.addEventListener("pointermove", this.handleMove, { passive: false, }); } this.parentElement.addEventListener("pointerup", this.handleEnd); this.parentElement.addEventListener("pointercancel", this.handleEnd); this.parentElement.addEventListener("contextmenu", this._noDefault); } destroy() { // Events const eventStarterElement = this.isJoystick || !this.options.fixed ? this.parentElement : this.gamepadControllerElement; eventStarterElement.removeEventListener("pointerdown", this.handleStart, { passive: false, }); if (this.isJoystick) { this.parentElement.removeEventListener("pointermove", this.handleMove, { passive: false, }); } this.parentElement.removeEventListener("pointerup", this.handleEnd); this.parentElement.removeEventListener("pointercancel", this.handleEnd); this.parentElement.removeEventListener("contextmenu", this._noDefault); // Remove element from DOM this.anchorElement.remove(); } } //# sourceMappingURL=controller.js.map