Analog
The analog element is a touch input mimicking analog inputs like joysticks on controllers.
It is a versatile element, designed specifically for game-like controls with touch, mirroring the Gamepad API axes properties.
- html
- React
<xyba-analog></xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => <Analog />;
Install
npm install xyba-elements
Importing
Once installed, you can import via xyba-elements
.
// Custom Elements
import "xyba-elements/elements/analog/analog.js";
// React
import { Analog } from "xyba-elements/react";
Or import from CDN,
<script src="https://cdn.jsdelivr.net/npm/xyba-elements/dist/cdn/elements/analog/analog.js">
Examples
Adapt to initial value
Sets the value from the initial touch point always calculated from the center of the input, also when track-from
is in use.
- html
- React
<xyba-analog adapt></xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => <Analog adapt />;
Trail along the pointer
Have the analog input trailing along the pointer when exceeding the edge.
- html
- React
<xyba-analog trail></xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => <Analog trail />;
Relocate to pointer location
Relocates to pointer touch point, useful is combination with track-from
to track from a larger area.
- html
- React
<xyba-analog relocate></xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => <Analog relocate />;
Persist the location
Leaves the analog input where it is inactivated.
- html
- React
<xyba-analog persistent adapt trail></xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => <Analog persistent adapt trail />;
Capture pointer
Add the attribute or property capture
to prevent interacting with other elements when active.
- html
- React
<xyba-analog trail capture></xyba-analog> <xyba-button>A</xyba-button>
import { Analog, Button } from "xyba-elements/react";
export default () => (
<>
<Analog trail capture />
<Button capture>A</Button>
</>
);
Track from element or document
You can have analog input track from another HTML element using the attribute track-from
or property .trackFrom
using a query selector.
- html
- React
- css
<div id="track-from-element">
<xyba-analog track-from="#track-from-element" trail></xyba-analog>
</div>
import { Analog } from "xyba-elements/react";
export default () => (
<div id="track-from-element">
<Analog trackFrom="#track-from-element" trail />
</div>
);
#track-from-element {
width: 300px;
max-width: 100%;
height: 300px;
border: 1px dashed currentColor;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
}
Limit to axis X / Y
Limit the analog input to only x
or y
direction.
- html
- React
<xyba-analog axis="x">X</xyba-analog>
<br />
<xyba-analog axis="y">Y</xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => (
<>
<Analog axis="x">X</Analog>
<br />
<Analog axis="y">Y</Analog>
</>
);
Specify deadzone
The threshold from 0
(center) to 1
(outer) before triggering a direction and direction event.
- html
- React
- css
<xyba-analog id="deadzone-example" deadzone="0.5"></xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => <Analog id="deadzone-example" deadzone="0.5" />;
#deadzone-example {
opacity: 0.5;
&[active] {
opacity: 0.5;
}
&:not([direction="none"]) {
opacity: 1;
}
}
Offset knob
Set the offset of the knob in percentage of the knob size eg. 50%
or in pixel value eg. 20px
or 20
.
- html
- React
<xyba-analog knob-offset="50%"></xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => <Analog knobOffset="30%" />;
Set custom radius
Set a custom radius in px instead of automatically calculating radius from the size of the analog input element.
- html
- React
<xyba-analog radius="60"></xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => <Analog radius="0" />;
Customizing style
Disable default visual style
Add the attribute no-style
to remove all default visual styling to implement your own style. The base styles for layout and pointer-events are preserved.
- html
- React
<xyba-analog no-style>A</xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => <Analog noStyle />;
Style using ::part
The input can be styled using ::parts
- html
- React
- css
<xyba-analog id="parts-demo" no-style knob-offset="33%" adapt>
<svg viewBox="0 0 24 24" width="24" height="24">
<path
fill="currentColor"
d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"
></path>
</svg>
</xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => (
<Analog id="parts-demo" noStyle knobOffset="33%" adapt>
<svg viewBox="0 0 24 24" width="24" height="24">
<path
fill="currentColor"
d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"
></path>
</svg>
</Analog>
);
#parts-demo {
--base-border-opacity: 10%;
--base-scale: 0.9;
--knob-border-opacity: 80%;
width: 120px;
&::part(base) {
border: 2px solid
color-mix(in srgb, currentColor var(--base-border-opacity), transparent);
transform: scale(var(--base-scale));
transition: transform 0.05s ease-in-out;
}
&::part(knob) {
border: 2px solid
color-mix(in srgb, currentColor var(--knob-border-opacity), transparent);
width: 40px;
height: 40px;
transition: all 0.1s linear;
}
&[active] {
--base-border-opacity: 80%;
--base-scale: 1;
--knob-border-opacity: 100%;
&::part(knob) {
background: currentColor;
transition: none;
}
svg {
filter: invert(100%);
}
}
}
Slotted elements
The analog elements provides the slots base
, knob
and overlay
.
- html
- React
<xyba-analog>
<img
src="https://cdn.theatlantic.com/thumbor/vDZCdxF7pRXmZIc5vpB4pFrWHKs=/559x0:2259x1700/1080x1080/media/img/mt/2017/06/shutterstock_319985324/original.jpg"
slot="base"
width="128"
height="128"
loading="eager"
/>
<img
src="https://cdn.iconscout.com/icon/premium/png-256-thumb/yarn-ball-4163435-3452680.png?f=webp"
slot="knob"
width="48px"
height="48px"
loading="eager"
/>
</xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => (
<Analog>
<img
src="https://cdn.theatlantic.com/thumbor/vDZCdxF7pRXmZIc5vpB4pFrWHKs=/559x0:2259x1700/1080x1080/media/img/mt/2017/06/shutterstock_319985324/original.jpg"
slot="base"
width="128"
height="128"
loading="eager"
/>
<img
src="https://cdn.iconscout.com/icon/premium/png-256-thumb/yarn-ball-4163435-3452680.png?f=webp"
slot="knob"
width="48px"
height="48px"
loading="eager"
/>
</Analog>
);
Events
Use inline events
Call global functions directly in the html using inline events. All events are available with the prefix on{event_name}
as attributes and properties.
- html
- React
<xyba-analog
onupenter="console.log('upenter')"
onupleave="console.log('upleave')"
onrightenter="console.log('rightenter')"
onrightleave="console.log('rightleave')"
ondownenter="console.log('downenter')"
ondownleave="console.log('downleave')"
onleftenter="console.log('leftenter')"
onleftleave="console.log('leftleave')"
onactive="console.log('active')"
oninactive="console.log('inactive')"
ondirection="(e) => console.log(e)"
onchange="(e) => console.log(e)"
></xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => (
<Analog
onUpEnter="console.log('upenter')"
onUpLeave="console.log('upleave')"
onRightEnter="console.log('rightenter')"
onRightLeave="console.log('rightleave')"
onDownEnter="console.log('downenter')"
onDownLeave="console.log('downleave')"
onLeftEnter="console.log('leftenter')"
onLeftLeave="console.log('leftleave')"
onActive="console.log('active')"
onInactive="console.log('inactive')"
onDirection="(e) => console.log(e)"
onChange="(e) => console.log(e)"
/>
);
Use event listeners
You can also listen to all events with event listeners.
- html
- React
- javascript
<xyba-analog id="event-listener-demo"></xyba-analog>
import { Analog } from "xyba-elements/react";
export default () => (
<Analog
onUpEnter="console.log('upenter')"
onUpLeave="console.log('upleave')"
onRightEnter="console.log('rightenter')"
onRightLeave="console.log('rightleave')"
onDownEnter="console.log('downenter')"
onDownLeave="console.log('downleave')"
onLeftEnter="console.log('leftenter')"
onLeftLeave="console.log('leftleave')"
onActive="console.log('active')"
onInactive="console.log('inactive')"
onDirection="(e) => console.log(e)"
onChange="(e) => console.log(e)"
/>
);
const analogElement = document.getElementById("event-listener-demo");
analogElement.addEventListener("upenter", () => console.log("upenter"));
analogElement.addEventListener("upleave", () => console.log("upleave"));
analogElement.addEventListener("rightenter", () => console.log("rightenter"));
analogElement.addEventListener("rightleave", () => console.log("rightleave"));
analogElement.addEventListener("downenter", () => console.log("downenter"));
analogElement.addEventListener("downleave", () => console.log("downleave"));
analogElement.addEventListener("leftenter", () => console.log("leftenter"));
analogElement.addEventListener("leftleave", () => console.log("leftleave"));
analogElement.addEventListener("active", () => console.log("active"));
analogElement.addEventListener("inactive", () => console.log("inactive"));
analogElement.addEventListener("direction", (e) => console.log(e));
analogElement.addEventListener("change", (e) => console.log(e));
Read state in game loop
It's also possible to read the current state instead of handling events
- html
- javascript
- React
<xyba-analog id="game-loop-demo"></xyba-analog>
function startGameLoop(update, fps = 60) {
let interval = 1000 / fps;
let lastTime = 0;
let rafId = null;
let running = true;
const loop = (currentTime) => {
if (!lastTime) {
lastTime = currentTime;
}
const deltaTime = currentTime - lastTime;
if (deltaTime >= interval) {
update(deltaTime);
lastTime = currentTime - (deltaTime % interval);
}
if (running) {
rafId = requestAnimationFrame(loop);
}
};
rafId = requestAnimationFrame(loop);
return () => {
running = false;
if (rafId) {
cancelAnimationFrame(rafId);
}
};
}
const analogElement = document.getElementById("game-loop-demo");
const update = (deltaTime) => {
if (analogElement.active) {
console.log(`Analog element value: ${analogElement.value}`); // [x, y].
}
};
const stopGameLoop = startGameLoop(update, 30); // Set FPS to 30
import React, { useEffect, useRef } from "react";
import { Analog, AnalogElement } from "xyba-elements/react";
function startGameLoop(
update: (deltaTime: number) => void,
fps: number = 60,
): () => void {
const interval = 1000 / fps;
let lastTime = 0;
let rafId: number | null = null;
let running = true;
const loop = (currentTime: number) => {
if (!lastTime) {
lastTime = currentTime;
}
const deltaTime = currentTime - lastTime;
if (deltaTime >= interval) {
update(deltaTime);
lastTime = currentTime - (deltaTime % interval);
}
if (running) {
rafId = requestAnimationFrame(loop);
}
};
rafId = requestAnimationFrame(loop);
return () => {
running = false;
if (rafId !== null) {
cancelAnimationFrame(rafId);
}
};
}
const App = () => {
const analogRef = useRef<AnalogElement>(null);
useEffect(() => {
const update = () => {
const analogElement = analogRef.current;
if (analogElement && analogElement.active) {
console.log(`Analog element value: ${analogElement.value}`); // [x, y].
}
};
const stopGameLoop = startGameLoop(update, 30); // Set FPS to 30
return () => {
stopGameLoop();
};
}, []);
return <Analog ref={analogRef} trail />;
};
Properties / Attributes
property | attribute | type | default | description | readonly |
---|---|---|---|---|---|
trail | trail | boolean | false | If the analog element should follow the pointer when active | |
relocate | relocate | boolean | false | If the analog element should relocate to the pointer when activated | |
knobOffset | knob-offset | "string" | `${number}%` | number | 0 | How much the knob should offset from the edge of the analog element in pixel or percentage | |
axis | axis | 'x' | 'y' | undefined | The axis to limit the analog input to | ||
adapt | adapt | boolean | false | If the analog element starts with the position of the initial pointer position | |
deadzone | deadzone | number | undefined | The center threshold of the analog element where it's considered to be in the center | ||
radius | radius | number | undefined | The effective radius of the analog element | ||
trackFrom | track-from | string | "document" | undefined | The query selector of the element to use as the track container or document | ||
active | active | boolean | false | If the analog element is active | |
persistent | persistent | boolean | false | If the analog element should be persistent in the location is is when inactive. | |
capture | capture | boolean | false | If the analog element should capture the pointer events | |
direction | direction | 'none' | 'up' | 'up_right' | 'right' | 'down_right' | 'down' | 'down_left' | 'left' | 'up_left' | | "none" | The current direction of the analog element | |
directions | { up: boolean; right: boolean; down: boolean; left: boolean; } | The current directions of the analog element | readonly | ||
noStyle | no-style | boolean | false | Whether the element should use default style | |
value | number[] | Gets the axes value | |||
axes | number[] | Gets the axes value | readonly | ||
angleRadians | number | Gets the angle in radians | readonly | ||
angleDegrees | number | Gets the angle in degrees | readonly | ||
magnitude | number | Gets the magnitude from center (0 - 1) | readonly | ||
inDeadzone | boolean | Whether it is withing the deadzone threshold | readonly | ||
position | Vector | undefined | The current position of the element | readonly | ||
movement | { x: number; y: number } | The movement of the pad since last time it was requested. | readonly | ||
onupenter | onupenter | The event attribute / property for the `upenter` event. | |||
onupleave | onupleave | The event attribute / property for the `upleave` event. | |||
onrightenter | onrightenter | The event attribute / property for the `rightenter` event. | |||
onrightleave | onrightleave | The event attribute / property for the `rightleave` event. | |||
ondownenter | ondownenter | The event attribute / property for the `downenter` event. | |||
ondownleave | ondownleave | The event attribute / property for the `downleave` event. | |||
onleftenter | onleftenter | The event attribute / property for the `leftenter` event. | |||
onleftleave | onleftleave | The event attribute / property for the `leftleave` event. | |||
onactive | onactive | The event attribute / property for the `active` event. | |||
oninactive | oninactive | The event attribute / property for the `inactive` event. | |||
onchange | onchange | The event attribute / property for the `change` event. | |||
ondirection | ondirection | The event attribute / property for the `direction` event. |
CSS Slots
name | description |
---|---|
The default content slotted into the knob (Only when no knob slot element present). | |
base | The base element of the analog element. |
overlay | The overlay element of the analog element. |
knob | The knob element of the analog element. |
Events
name | description | type |
---|---|---|
upenter | Dispatched when the analog element enters the up direction. | AnalogDirectionEvent |
upleave | Dispatched when the analog element leaves the up direction. | AnalogDirectionEvent |
rightenter | Dispatched when the analog element enters the right direction. | AnalogDirectionEvent |
rightleave | Dispatched when the analog element leaves the right direction. | AnalogDirectionEvent |
downenter | Dispatched when the analog element enters the down direction. | AnalogDirectionEvent |
downleave | Dispatched when the analog element leaves the down direction. | AnalogDirectionEvent |
leftenter | Dispatched when the analog element enters the left direction. | AnalogDirectionEvent |
leftleave | Dispatched when the analog element leaves the left direction. | AnalogDirectionEvent |
active | Dispatched when the analog element is activated. | AnalogActiveEvent |
inactive | Dispatched when the analog element is deactivated. | AnalogActiveEvent |
change | Dispatched when the analog element changes. | ChangeEvent |
direction | Dispatched when the analog element changes direction. | AnalogDirectionEvent |
CSS Custom Properties
name | description | default |
---|---|---|
--xyba-analog-color | The color used for inheritance. | |
--xyba-analog-size | The size of the analog element. | |
--xyba-analog-base-backdrop-filter | The backdrop filter of the analog element. | |
--xyba-analog-base-background | The background of the analog element. | |
--xyba-analog-base-background-active | The background of the analog element when active. | |
--xyba-analog-base-border-width | The border width of the analog element. | |
--xyba-analog-base-border-color | The border color of the analog element. | |
--xyba-analog-base-border-color | The border color of the analog element when active. | |
--xyba-analog-base-border-style | The border style of the analog element. | |
--xyba-analog-knob-size | The size of the knob element. | |
--xyba-analog-knob-background | The background of the knob element. | |
--xyba-analog-knob-backdrop-filter | The backdrop filter of the knob element. | |
--xyba-analog-knob-border-width | The border width of the knob element. | |
--xyba-analog-knob-border-color | The border color of the knob element. | |
--xyba-analog-knob-border-color | The border color of the knob element when active. | |
--xyba-analog-knob-border-style | The border style of the knob element. | |
--xyba-analog-knob-text-color | The text or icon color of the knob element. | |
--xyba-analog-knob-text-color-active | The text or icon color of the knob element when active. |
CSS Parts
name | description |
---|---|
base | The base element of the analog element. |
overlay | The overlay element of the analog element. |
knob | The knob element of the analog element. |