Files
wehub-resource-sync f4ed7901c3
Module Market Check / Validate modules/ (push) Failing after 1s
Android CI Build / check (push) Failing after 1s
Module Market Publish / Generate submissions.json (push) Failing after 1s
Android CI Build / package-apks (push) Has been skipped
chore: import upstream snapshot with attribution
2026-07-13 12:29:44 +08:00

207 lines
7.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Auto Scroll — official WebToApp sample.
//
// Demonstrates:
// - requestAnimationFrame-based smooth scrolling (not setInterval +
// scrollBy, which produces visible stuttering on devices)
// - Pause-on-touch and pause-on-tab-hidden
// - Speed control through a config item
// - Floating control panel with start/stop + speed slider
// - State persistence with sessionStorage so navigating to a new page on
// the same site continues scrolling
(function () {
var STATE_KEY = 'wta-auto-scroll-active';
var SPEED_KEY = 'wta-auto-scroll-speed';
// Speed: pixels-per-second (independent of frame rate). We map the
// 1..10 user-visible level to 30..300 px/s; level 5 is a leisurely
// article-reading pace.
function levelToPxPerSec(level) {
var n = Math.max(1, Math.min(10, level | 0));
return 30 * n;
}
// Read a possibly-overridden speed from sessionStorage first, else the
// user's saved config. Lets the floating panel +/- control do live
// tuning without polluting the persisted value.
function getSpeedLevel() {
var override = sessionStorage.getItem(SPEED_KEY);
if (override) return parseInt(override, 10) || 5;
return parseInt(getConfig('speedLevel', '5'), 10) || 5;
}
// ── core scroll engine ────────────────────────────────────────────
var rafId = null;
var lastTs = 0;
var paused = false;
var stopOnUser = String(getConfig('pauseOnInteract', 'true')) === 'true';
var reverse = String(getConfig('reverse', 'false')) === 'true';
var useShortcuts = String(getConfig('keyboardShortcuts', 'true')) === 'true';
function step(ts) {
if (paused) {
rafId = requestAnimationFrame(step);
lastTs = ts;
return;
}
if (!lastTs) lastTs = ts;
var dt = (ts - lastTs) / 1000; // seconds
lastTs = ts;
var px = levelToPxPerSec(getSpeedLevel()) * dt;
if (reverse) px = -px;
var before = window.scrollY;
window.scrollBy(0, px);
// If we didn't move (page bottom or scroll-locked), stop — it's
// pointless to keep burning frames.
if (Math.abs(window.scrollY - before) < 0.1) {
stop();
return;
}
rafId = requestAnimationFrame(step);
}
function start() {
if (rafId !== null) return;
if (reverse) window.scrollTo(0, document.documentElement.scrollHeight);
sessionStorage.setItem(STATE_KEY, '1');
paused = false;
lastTs = 0;
rafId = requestAnimationFrame(step);
refreshPanel();
}
function stop() {
if (rafId !== null) cancelAnimationFrame(rafId);
rafId = null;
sessionStorage.removeItem(STATE_KEY);
refreshPanel();
}
function toggle() {
if (rafId === null) start();
else stop();
}
// ── pause-on-interaction ──────────────────────────────────────────
// We pause briefly when the user touches the page (so manual scroll
// works), then resume after a short idle period.
var resumeTimer = null;
function onUserScroll() {
if (rafId === null || !stopOnUser) return;
paused = true;
if (resumeTimer) clearTimeout(resumeTimer);
resumeTimer = setTimeout(function () {
paused = false;
lastTs = 0;
}, 1500);
}
window.addEventListener('wheel', onUserScroll, { passive: true });
window.addEventListener('touchstart', onUserScroll, { passive: true });
window.addEventListener(
'keydown',
function (e) {
// Space / arrows / page down — assume user wants control back.
if (
e.key === ' ' ||
e.key === 'ArrowDown' ||
e.key === 'ArrowUp' ||
e.key === 'PageDown' ||
e.key === 'PageUp'
) {
onUserScroll();
}
},
{ passive: true }
);
// Pause when the tab is backgrounded — saves battery and stops the
// scroll position drifting while the user does something else.
// ── keyboard shortcuts (J toggle, [ slower, ] faster, R reverse) ──
if (useShortcuts) {
window.addEventListener('keydown', function (e) {
if (e.ctrlKey || e.metaKey || e.altKey) return;
if (e.target && /^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName)) return;
if (e.key === 'j' || e.key === 'J') { e.preventDefault(); toggle(); }
else if (e.key === '[') { e.preventDefault(); sessionStorage.setItem(SPEED_KEY, String(Math.max(1, getSpeedLevel() - 1))); refreshPanel(); }
else if (e.key === ']') { e.preventDefault(); sessionStorage.setItem(SPEED_KEY, String(Math.min(10, getSpeedLevel() + 1))); refreshPanel(); }
else if (e.key === 'r' || e.key === 'R') { e.preventDefault(); reverse = !reverse; if (rafId !== null) { stop(); start(); } }
});
}
document.addEventListener('visibilitychange', function () {
if (document.hidden) paused = true;
else if (rafId !== null) {
paused = false;
lastTs = 0;
}
});
// ── floating panel ────────────────────────────────────────────────
// A tiny remote control: play/pause toggle and speed +/-. Built with
// plain DOM so the code reads end-to-end with no framework knowledge.
var panel = null;
function ensurePanel() {
if (panel) return panel;
panel = document.createElement('div');
panel.id = 'wta-as-panel';
panel.innerHTML =
'<button data-action="toggle" type="button" class="wta-as-main"></button>' +
'<button data-action="slower" type="button"></button>' +
'<span class="wta-as-speed"></span>' +
'<button data-action="faster" type="button">+</button>';
panel.addEventListener('click', function (e) {
var btn = e.target.closest('button[data-action]');
if (!btn) return;
if (btn.dataset.action === 'toggle') toggle();
if (btn.dataset.action === 'slower') {
sessionStorage.setItem(SPEED_KEY, String(Math.max(1, getSpeedLevel() - 1)));
refreshPanel();
}
if (btn.dataset.action === 'faster') {
sessionStorage.setItem(SPEED_KEY, String(Math.min(10, getSpeedLevel() + 1)));
refreshPanel();
}
});
document.body.appendChild(panel);
return panel;
}
function refreshPanel() {
if (!panel) return;
var main = panel.querySelector('.wta-as-main');
var speed = panel.querySelector('.wta-as-speed');
if (rafId === null) {
main.textContent = '▶';
panel.classList.remove('wta-as-running');
} else {
main.textContent = '⏸';
panel.classList.add('wta-as-running');
}
speed.textContent = getSpeedLevel() + '×';
}
// ── panel button registration ─────────────────────────────────────
if (typeof __WTA_MODULE_UI__ !== 'undefined' && __WTA_MODULE_UI__.register) {
__WTA_MODULE_UI__.register({
id: __MODULE_INFO__.id,
name: __MODULE_INFO__.name,
icon: __MODULE_INFO__.icon,
uiConfig: __MODULE_UI_CONFIG__,
runMode: __MODULE_RUN_MODE__,
onClick: function () {
ensurePanel();
refreshPanel();
toggle();
},
});
}
// ── resume after navigation ───────────────────────────────────────
if (sessionStorage.getItem(STATE_KEY) === '1') {
ensurePanel();
refreshPanel();
start();
}
})();