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:
@@ -577,8 +577,78 @@
|
|||||||
initHTMXHandlers();
|
initHTMXHandlers();
|
||||||
handleLandscapeAccordions(); // Auto-open sidebar accordions in landscape mode
|
handleLandscapeAccordions(); // Auto-open sidebar accordions in landscape mode
|
||||||
initZoomControlButtons();
|
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)
|
// HYPERSCRIPT-POWERED FEATURES (NO JS NEEDED)
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
@@ -141,9 +141,7 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body {{if .ThemeClean}}class="theme-clean"{{end}}
|
<body {{if .ThemeClean}}class="theme-clean"{{end}}
|
||||||
_="on load call initScrollBehavior()
|
_="on keydown
|
||||||
on scroll from window call handleScroll()
|
|
||||||
on keydown
|
|
||||||
set tagName to event.target.tagName
|
set tagName to event.target.tagName
|
||||||
set isInputField to (tagName is 'INPUT' or tagName is 'TEXTAREA')
|
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
|
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);
|
||||||
Reference in New Issue
Block a user