Keyboard based scrolling should be predictable over time and keyboard repeat rate

Keyboard based scrolling should be predictable over time and keyboard repeat rate

  • +1

    Votes: 3 75.0%
  • -1

    Votes: 1 25.0%
  • +-0

    Votes: 0 0.0%

  • Total voters
    4
  • Poll closed .
Group Leader
Joined
Jan 18, 2018
Messages
127
Current issue:

Keyboard based scrolling is currently somewhat jittery and based on number of presses/repeats over time. Vertical scroll can currently be overridden, however overriding horizontal scroll cannot be done in a normal manner without potentially bringing other issues along (using
Code:
event.stopImmediatePropagation()
). Currently, well-written code can override this and only pass through on first frame. Ideally, it would be done in an automatic fashion.

Resolve:

TypeScript implementation (complex form). Use keybinds.ts for actual bindings to the internal functions.

Code:
const dirs = new Uint8ClampedArray(4);
const _d = new Int32Array(dirs.buffer, dirs.byteOffset, 1);
// init using global `var SCROLL_SPEED = {x: +n, y: +n}`
const dtxy = typeof SCROLL_SPEED === 'object' ? SCROLL_SPEED : {x: 0.84, y: 0.94};
Object.seal(dtxy);
let t0 = 0.0;
let raf = 0;
/** direction, default is anticlockwise starting from bottom (down). */
export const enum Dirs { Down, Right, Up, Left }
const scroller = (T: DOMHighResTimeStamp) => {
	if (_d[0] !== 0) {
		const dt = Math.min(T - t0, 50);
		const dx = (dirs[Dirs.Down] - dirs[Dirs.Up]);// & 0x800000ff;
		const dy = (dirs[Dirs.Right] - dirs[Dirs.Left]);// & 0x800000ff;
		const sc = document.fullscreenElement || document.scrollingElement || document.body;
		sc.scrollBy(dt * dtxy.x * dx, dt * dtxy.y * dy);
		t0 = T;
		raf = requestAnimationFrame(scroller);
	} else raf = 0;
}

interface TimeStamp { timeStamp: DOMHighResTimeStamp; }
/** scroll loop */
export const scr = <T extends TimeStamp>(e: T, d: Dirs) => {
	++dirs[d];
	raf || (
		raf = requestAnimationFrame(scroller),
		t0 = e.timeStamp
	);
}
/** decrements the direction */
export const dec = (d: Dirs) => { --dirs[d]; }
/** clears the animation for later use */
export const can = () => {
	cancelAnimationFrame(raf);
	_d[0] = raf = 0;
}
/** set x and y speeds, optionally share state to other tabs */
export function sxy(x: number, y: number, send?: boolean) {
	dtxy.x = Math.abs(x);
	dtxy.y = Math.abs(y);
	if (send) bc.postMessage([dtxy.x, dtxy.y]);
}
document.addEventListener('visibilitychange', can);

const bc = new BroadcastChannel('scroll-speeds');
bc.onmessage = e => sxy(e.data[0], e.data[1]);

The above implementations achieve three goals:[ul][*]Scroll is time-based, rather than jump/repeat based[/*][*]Composable and option based[/*][*]Does not add a spin loop if it's not in use[/*][/ul]Additionally, it holds state across multiple tabs, and always closes/cancels its requests when changing tabs/windows in order to avoid memory leaks in js.
 

Users who are viewing this thread

Top