Add scroll normalization for consistent scroll speed

Introduces CSS and JavaScript to normalize scroll speed across all sites by intercepting wheel events and applying a consistent scroll delta. The normalization is injected into webviews on load to ensure uniform scrolling behavior regardless of site-specific overrides.
This commit is contained in:
2025-12-28 10:50:52 +13:00
parent 37345b267b
commit 399e8da5b4
2 changed files with 207 additions and 1 deletions
+101
View File
@@ -5,6 +5,104 @@
const ipcRenderer = window.electronAPI;
// =============================================================================
// SCROLL NORMALIZATION (consistent scroll speed across all sites)
// =============================================================================
const SCROLL_NORMALIZATION_CSS = `
/* Disable smooth scrolling behavior that some sites force */
*, *::before, *::after {
scroll-behavior: auto !important;
}
html, body {
scroll-behavior: auto !important;
}
`;
const SCROLL_NORMALIZATION_JS = `
(function() {
if (window.__nebulaScrollNormalized) return;
window.__nebulaScrollNormalized = true;
// Consistent scroll amount in pixels per wheel delta unit
const SCROLL_SPEED = 100;
// Intercept wheel events to normalize scroll speed
document.addEventListener('wheel', function(e) {
// Don't interfere if modifier keys are pressed (zoom, horizontal scroll, etc.)
if (e.ctrlKey || e.metaKey || e.altKey) return;
// Get the scroll target
let target = e.target;
let scrollable = null;
// Find the nearest scrollable element
while (target && target !== document.body && target !== document.documentElement) {
const style = window.getComputedStyle(target);
const overflowY = style.overflowY;
const overflowX = style.overflowX;
if ((overflowY === 'auto' || overflowY === 'scroll') && target.scrollHeight > target.clientHeight) {
scrollable = target;
break;
}
if ((overflowX === 'auto' || overflowX === 'scroll') && target.scrollWidth > target.clientWidth && e.shiftKey) {
scrollable = target;
break;
}
target = target.parentElement;
}
// If no scrollable container found, use the document
if (!scrollable) {
scrollable = document.scrollingElement || document.documentElement || document.body;
}
// Calculate normalized scroll delta
// deltaMode: 0 = pixels, 1 = lines, 2 = pages
let deltaY = e.deltaY;
let deltaX = e.deltaX;
if (e.deltaMode === 1) {
// Line mode - multiply by line height approximation
deltaY *= SCROLL_SPEED;
deltaX *= SCROLL_SPEED;
} else if (e.deltaMode === 2) {
// Page mode - multiply by viewport height
deltaY *= window.innerHeight;
deltaX *= window.innerWidth;
} else {
// Pixel mode - normalize to consistent speed
// Clamp the delta to prevent extremely fast scrolling from some sites
const sign = deltaY > 0 ? 1 : -1;
deltaY = sign * Math.min(Math.abs(deltaY), SCROLL_SPEED * 3);
const signX = deltaX > 0 ? 1 : -1;
deltaX = signX * Math.min(Math.abs(deltaX), SCROLL_SPEED * 3);
}
// Apply scroll
e.preventDefault();
scrollable.scrollBy({
top: deltaY,
left: e.shiftKey ? deltaX : 0,
behavior: 'auto'
});
}, { passive: false, capture: true });
})();
`;
// Function to apply scroll normalization to a webview
function applyScrollNormalization(webview) {
try {
webview.insertCSS(SCROLL_NORMALIZATION_CSS);
webview.executeJavaScript(SCROLL_NORMALIZATION_JS);
console.log('[BigPicture] Applied scroll normalization to webview');
} catch (err) {
console.warn('[BigPicture] Failed to apply scroll normalization:', err);
}
}
// =============================================================================
// CONFIGURATION
// =============================================================================
@@ -1489,6 +1587,9 @@ function navigateTo(url) {
state.webviewContentsId = webview.getWebContentsId();
console.log('[BigPicture] WebContents ID:', state.webviewContentsId);
// Apply scroll normalization for consistent scroll speed
applyScrollNormalization(webview);
// Inject script to detect input field focus and notify the host
injectInputFocusDetection(webview);
} catch (err) {
+106 -1
View File
@@ -3,6 +3,103 @@ const ipcRenderer = window.electronAPI;
const DEBUG = false;
const debug = (...args) => { if (DEBUG) console.log(...args); };
// Scroll normalization CSS and JS to ensure consistent scroll speed across all sites
const SCROLL_NORMALIZATION_CSS = `
/* Disable smooth scrolling behavior that some sites force */
*, *::before, *::after {
scroll-behavior: auto !important;
}
html, body {
scroll-behavior: auto !important;
}
`;
const SCROLL_NORMALIZATION_JS = `
(function() {
if (window.__nebulaScrollNormalized) return;
window.__nebulaScrollNormalized = true;
// Consistent scroll amount in pixels per wheel delta unit
const SCROLL_SPEED = 100;
// Intercept wheel events to normalize scroll speed
document.addEventListener('wheel', function(e) {
// Don't interfere if modifier keys are pressed (zoom, horizontal scroll, etc.)
if (e.ctrlKey || e.metaKey || e.altKey) return;
// Get the scroll target
let target = e.target;
let scrollable = null;
// Find the nearest scrollable element
while (target && target !== document.body && target !== document.documentElement) {
const style = window.getComputedStyle(target);
const overflowY = style.overflowY;
const overflowX = style.overflowX;
if ((overflowY === 'auto' || overflowY === 'scroll') && target.scrollHeight > target.clientHeight) {
scrollable = target;
break;
}
if ((overflowX === 'auto' || overflowX === 'scroll') && target.scrollWidth > target.clientWidth && e.shiftKey) {
scrollable = target;
break;
}
target = target.parentElement;
}
// If no scrollable container found, use the document
if (!scrollable) {
scrollable = document.scrollingElement || document.documentElement || document.body;
}
// Calculate normalized scroll delta
// deltaMode: 0 = pixels, 1 = lines, 2 = pages
let deltaY = e.deltaY;
let deltaX = e.deltaX;
if (e.deltaMode === 1) {
// Line mode - multiply by line height approximation
deltaY *= SCROLL_SPEED;
deltaX *= SCROLL_SPEED;
} else if (e.deltaMode === 2) {
// Page mode - multiply by viewport height
deltaY *= window.innerHeight;
deltaX *= window.innerWidth;
} else {
// Pixel mode - normalize to consistent speed
// Clamp the delta to prevent extremely fast scrolling from some sites
const sign = deltaY > 0 ? 1 : -1;
deltaY = sign * Math.min(Math.abs(deltaY), SCROLL_SPEED * 3);
const signX = deltaX > 0 ? 1 : -1;
deltaX = signX * Math.min(Math.abs(deltaX), SCROLL_SPEED * 3);
}
// Apply scroll
e.preventDefault();
scrollable.scrollBy({
top: deltaY,
left: e.shiftKey ? deltaX : 0,
behavior: 'auto'
});
}, { passive: false, capture: true });
})();
`;
// Function to apply scroll normalization to a webview
function applyScrollNormalization(webview) {
try {
// Inject CSS to disable smooth scrolling
webview.insertCSS(SCROLL_NORMALIZATION_CSS);
// Inject JS to normalize wheel scroll speed
webview.executeJavaScript(SCROLL_NORMALIZATION_JS);
debug('[Scroll] Applied scroll normalization to webview');
} catch (err) {
console.warn('[Scroll] Failed to apply scroll normalization:', err);
}
}
// Site history management using localStorage
function getSiteHistory() {
try {
@@ -285,8 +382,11 @@ function createTab(inputUrl) {
if (e.favicons.length > 0) updateTabMetadata(id, 'favicon', e.favicons[0]);
});
// Send bookmarks to home page when it loads
// Send bookmarks to home page when it loads and apply scroll normalization
webview.addEventListener('dom-ready', () => {
// Apply scroll normalization to all sites for consistent scrolling
applyScrollNormalization(webview);
if (inputUrl === 'browser://home') {
webview.executeJavaScript(`
if (window.receiveBookmarks) {
@@ -594,6 +694,11 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
webview.addEventListener('did-finish-load', () => {
scheduleUpdateNavButtons();
});
// Apply scroll normalization when webview is ready
webview.addEventListener('dom-ready', () => {
applyScrollNormalization(webview);
});
webview.addEventListener('new-window', e => {
// Unified behavior: always open http(s) targets in a new tab (no extra window)