fix: Replace hyperscript scroll handler with JavaScript implementation

The hyperscript-based scroll behavior was not working reliably across all browsers.
Replaced with a pure JavaScript implementation that:

Desktop (>900px):
- Hides action bar on scroll down (past 100px threshold)
- Shows action bar on scroll up
- Shows action bar at top of page

Mobile (≤900px):
- Always keeps action bar visible
- Actively removes header-hidden class on mobile
- Handles viewport resize for responsive testing

Changes:
- Added initScrollBehaviorJS() function to main.js
- Removed hyperscript scroll handlers from body tag in index.html
- Kept keyboard shortcut handlers in hyperscript (still working)
- Uses passive scroll listener for better performance

This fixes the bug where:
- Desktop: bar would hide but not show again on scroll up
- Mobile: bar was incorrectly hiding despite CSS override
This commit is contained in:
juanatsap
2025-11-30 04:13:50 +00:00
parent acc9031cb9
commit cb5e72a5f2
4 changed files with 149 additions and 3 deletions
BIN
View File
Binary file not shown.
+70
View File
@@ -577,8 +577,78 @@
initHTMXHandlers();
handleLandscapeAccordions(); // Auto-open sidebar accordions in landscape mode
initZoomControlButtons();
initScrollBehaviorJS(); // JavaScript fallback for scroll behavior
});
/**
* JavaScript-based scroll behavior (fallback for hyperscript)
* Desktop: Hide header on scroll down, show on scroll up
* Mobile (≤900px): Always show header (CSS handles this, but we skip adding class)
*/
function initScrollBehaviorJS() {
let lastScrollY = 0;
const scrollThreshold = 100;
const actionBar = document.querySelector('.action-bar');
const navMenu = document.querySelector('.navigation-menu');
if (!actionBar) return;
function isMobileViewport() {
return window.innerWidth <= 900;
}
function handleScroll() {
const currentScrollY = window.scrollY || window.pageYOffset;
// Skip header hiding logic on mobile - CSS override handles visibility
// but we still avoid adding the class for cleaner state
if (isMobileViewport()) {
// On mobile, always ensure header is visible
actionBar.classList.remove('header-hidden');
if (navMenu) navMenu.classList.remove('header-hidden');
lastScrollY = currentScrollY;
return;
}
// At top of page - always show header
if (currentScrollY <= scrollThreshold) {
actionBar.classList.remove('header-hidden');
if (navMenu && navMenu.classList.contains('menu-open')) {
navMenu.classList.remove('header-hidden');
}
}
// Scrolling DOWN past threshold - hide header
else if (currentScrollY > lastScrollY && currentScrollY > scrollThreshold) {
actionBar.classList.add('header-hidden');
if (navMenu && navMenu.classList.contains('menu-open')) {
navMenu.classList.add('header-hidden');
}
}
// Scrolling UP - show header
else if (currentScrollY < lastScrollY) {
actionBar.classList.remove('header-hidden');
if (navMenu && navMenu.classList.contains('menu-open')) {
navMenu.classList.remove('header-hidden');
}
}
lastScrollY = currentScrollY;
}
// Use passive listener for better scroll performance
window.addEventListener('scroll', handleScroll, { passive: true });
// Also handle resize (viewport changes)
window.addEventListener('resize', function() {
if (isMobileViewport()) {
actionBar.classList.remove('header-hidden');
if (navMenu) navMenu.classList.remove('header-hidden');
}
});
console.log('Scroll behavior JS initialized');
}
// =============================================================================
// HYPERSCRIPT-POWERED FEATURES (NO JS NEEDED)
// =============================================================================
+1 -3
View File
@@ -141,9 +141,7 @@
</script>
</head>
<body {{if .ThemeClean}}class="theme-clean"{{end}}
_="on load call initScrollBehavior()
on scroll from window call handleScroll()
on keydown
_="on keydown
set tagName to event.target.tagName
set isInputField to (tagName is 'INPUT' or tagName is 'TEXTAREA')
if event.key is '?' and not event.ctrlKey and not event.metaKey and not event.altKey and not isInputField
@@ -0,0 +1,78 @@
#!/usr/bin/env node
import { chromium } from 'playwright';
async function test() {
const browser = await chromium.launch({ headless: true });
console.log('Testing scroll behavior with JavaScript implementation...\n');
// Desktop test
const ctx = await browser.newContext({ viewport: { width: 1278, height: 800 } });
const page = await ctx.newPage();
await page.goto('http://localhost:1999/?lang=en');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Check console for init message
page.on('console', msg => {
if (msg.text().includes('Scroll behavior')) console.log('Console:', msg.text());
});
console.log('1. Initial state:');
let state = await page.evaluate(() => ({
hidden: document.querySelector('.action-bar')?.classList.contains('header-hidden'),
scrollY: window.scrollY
}));
console.log(` ScrollY: ${state.scrollY}, Hidden: ${state.hidden}`);
console.log('\n2. Scroll DOWN 500px:');
await page.evaluate(() => window.scrollTo(0, 500));
await page.waitForTimeout(300);
state = await page.evaluate(() => ({
hidden: document.querySelector('.action-bar')?.classList.contains('header-hidden'),
scrollY: window.scrollY
}));
console.log(` ScrollY: ${state.scrollY}, Hidden: ${state.hidden}`);
console.log(state.hidden ? ' ✅ Bar hidden' : ' ❌ Bar should be hidden!');
console.log('\n3. Scroll UP to 300px:');
await page.evaluate(() => window.scrollTo(0, 300));
await page.waitForTimeout(300);
state = await page.evaluate(() => ({
hidden: document.querySelector('.action-bar')?.classList.contains('header-hidden'),
scrollY: window.scrollY
}));
console.log(` ScrollY: ${state.scrollY}, Hidden: ${state.hidden}`);
console.log(!state.hidden ? ' ✅ Bar visible' : ' ❌ Bar should be visible!');
await ctx.close();
// Mobile test
console.log('\n4. Mobile (375px) - scroll down:');
const mobileCtx = await browser.newContext({
viewport: { width: 375, height: 667 },
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)',
hasTouch: true
});
const mobilePage = await mobileCtx.newPage();
await mobilePage.goto('http://localhost:1999/?lang=en');
await mobilePage.waitForLoadState('networkidle');
await mobilePage.waitForTimeout(500);
await mobilePage.evaluate(() => window.scrollTo(0, 500));
await mobilePage.waitForTimeout(300);
state = await mobilePage.evaluate(() => ({
hidden: document.querySelector('.action-bar')?.classList.contains('header-hidden'),
transform: getComputedStyle(document.querySelector('.action-bar')).transform
}));
console.log(` Hidden class: ${state.hidden}, Transform: ${state.transform}`);
const isVisible = !state.hidden || state.transform === 'none' || state.transform.includes('0, 0)');
console.log(isVisible ? ' ✅ Bar visible on mobile' : ' ❌ Bar should stay visible!');
await mobileCtx.close();
await browser.close();
console.log('\nDone.');
}
test().catch(console.error);