Fix ChatGPT Scrolling Issues: Make PageUp, PageDown, Home, and End Work Like a Charm (Take 2)
Tired of PageUp, PageDown, Home, and End keys not working in ChatGPT? You’re not alone. Scrolling in ChatGPT can be frustrating, especially when these keys don’t behave as expected. But don’t worry — I’ve built a simple script that fixes scrolling issues and even adds smooth, dynamic scrolling to make your experience seamless. Whether it’s navigating the chat window or the conversation list, this solution has you covered. Say goodbye to broken scrolling and take control of your ChatGPT experience today!
So no, AI didn’t magically fix itself. The issue was that the script was both lazy and poorly designed. It worked for a while, but only because there were random characters in the class names that changed over time, breaking the functionality.
While using the script, I noticed additional issues that made it inconvenient. For example, in ChatGPT, there are actually two areas where you’d want scrolling to work properly with PageUp/PageDown and Home/End:
- The main chat window.
- The list of chats.
This made me realize that the script shouldn’t just fix scrolling for specific elements but for all scrollable elements on any page where scrolling doesn’t work. That way, it could be a universal fix — not just for ChatGPT, but for any site where scrolling is broken.
As usual, AI couldn’t write a complete script without human guidance. Maybe that’s understandable — AI doesn’t fully understand the context or the nuances of implementation. Or maybe it’s just not there yet. I even wondered, “Isn’t there already a script for this on GitHub?” Apparently not.
So, I had to step in, explaining every detail and implementing it myself. After many hours of trial and error, I finally arrived at a working solution.
On a side note, this situation highlights why web apps are better than native apps in some cases. Web apps let you customize their behavior. Native apps? Not so much. If a native app has broken scrolling, you’re stuck waiting — sometimes for months, years, or forever — until the developers fix it.
Speaking of customizable tools, if you’re into coding, you might enjoy our Python Deck. It’s a beautifully crafted resource to master Python essentials, designed to make learning fun and engaging. Check it out at Summon the JSON! Whether you’re sharpening your skills or just starting out, it’s a must-have for any Python enthusiast.
With web apps, you can tweak these small issues yourself and adapt the apps to your needs. This is becoming even more important as modern apps are increasingly rushed, cheaply made, and missing even the most basic features.
Key Considerations For Page Up / Page Down / End / Home Scrolling
Handling this from scratch might seem trivial, but it’s far from straightforward. For example, what should happen when a user long presses PageUp? Should it scroll continuously? When should it stop? Should the scrolling speed increase over time?
And what if the user presses the button multiple times in quick succession? Should it scroll at the same speed, or should the speed increase with each press? I experimented with accelerating the scroll for multiple presses but couldn’t get it to work perfectly — it only accelerates during a long press for now.
Another common issue with scrollable areas is that you usually have to click inside the area before you can use the scroll buttons. It’s a widespread problem, so I addressed it in this script: now, you only need to hover over a scrollable element to enable scrolling. Just a nice little cherry on top!
Then there’s the issue with inputs and text areas. You don’t want the script to override their native handling for PageUp, PageDown, Home, and End. Unfortunately, I couldn’t fully solve that issue in this iteration.
I’m sure there are more edge cases to address, but for now, I want to see how well this version works.
The core idea behind this script is to use a targetScrollPosition
. This requires a bit of calculation, as it’s not as simple as firing a scroll event. To provide a smooth experience for both long presses and multiple key presses, two processes need to run in parallel:
- Updating the Target: Every key press updates the
targetScrollPosition
. For example, pressing PageUp five times sets the target to scroll five "pages" up. - Reaching the Target: A separate function ensures the page keeps scrolling smoothly until it reaches the
targetScrollPosition
.
Since these two processes work independently, the scrolling behavior feels natural and fluid.
The Code
The code contains some faulty code for multiplying and text area detection, but it works:
function initializeScrollHandler() {
console.log("Initializing scroll handler...");
let targetScrollPosition = 0; // Desired scroll position
let isScrolling = false; // Flag to prevent multiple scrolling loops
const basePageDistance = window.innerHeight; // Base distance for one page scroll
let scrollSpeedMultiplier = 1; // Multiplier for increasing scroll speed
const multiplierResetTime = 300; // Time in ms to reset multiplier if no press
let lastKeyPressTime = 0; // Time of the last key press
let multiplierTimeout = null; // Timeout ID for resetting the multiplier
let isKeyHeld = false; // Tracks if the key is being held
function isScrollable(element) {
const style = window.getComputedStyle(element);
return (
element.scrollHeight > element.clientHeight ||
element.scrollWidth > element.clientWidth
) &&
(style.overflowY === 'scroll' || style.overflowY === 'auto');
}
function isInputTextareaOrContentEditableFocused() {
const activeElement = document.activeElement;
return (
activeElement &&
(
['INPUT', 'TEXTAREA'].includes(activeElement.tagName) ||
activeElement.isContentEditable
)
);
}
function getScrollableElement() {
const hoveredElement = Array.from(document.querySelectorAll('*')).find(
(element) => isScrollable(element) && element.matches(':hover')
);
return hoveredElement || document.activeElement.closest('*') || document.activeElement || window;
}
function updateTargetScrollPosition(key) {
const scrollContainer = getScrollableElement();
const currentScrollTop = scrollContainer === window ? window.scrollY : scrollContainer.scrollTop;
const pageDistance = basePageDistance * scrollSpeedMultiplier;
if (key === 'PageDown') {
targetScrollPosition = currentScrollTop + pageDistance;
} else if (key === 'PageUp') {
targetScrollPosition = currentScrollTop - pageDistance;
} else if (key === 'Home') {
targetScrollPosition = 0; // Scroll to the top
if (scrollContainer === window) {
window.scrollTo(0, targetScrollPosition); // Move immediately for window
} else {
scrollContainer.scrollTop = targetScrollPosition; // Move immediately for element
}
console.log("Home pressed: Moved to top immediately.");
return true; // Indicates immediate scroll
} else if (key === 'End') {
if (scrollContainer === window) {
targetScrollPosition = document.documentElement.scrollHeight - window.innerHeight;
window.scrollTo(0, targetScrollPosition); // Move immediately for window
} else {
targetScrollPosition = scrollContainer.scrollHeight - scrollContainer.clientHeight;
scrollContainer.scrollTop = targetScrollPosition; // Move immediately for element
}
console.log("End pressed: Moved to bottom immediately.");
return true; // Indicates immediate scroll
}
// Ensure target position doesn't exceed boundaries
if (scrollContainer !== window) {
targetScrollPosition = Math.max(
0,
Math.min(targetScrollPosition, scrollContainer.scrollHeight - scrollContainer.clientHeight)
);
} else {
targetScrollPosition = Math.max(0, targetScrollPosition); // No upper limit for window scroll
}
console.log(`Target scroll position updated: ${targetScrollPosition}, Speed multiplier: ${scrollSpeedMultiplier}`);
return false; // Indicates smooth scroll
}
function smoothScroll() {
if (isScrolling) return; // Prevent multiple loops
isScrolling = true;
function step() {
const scrollContainer = getScrollableElement();
const currentScrollTop = scrollContainer === window ? window.scrollY : scrollContainer.scrollTop;
if (Math.abs(currentScrollTop - targetScrollPosition) < 1) {
isScrolling = false; // Stop scrolling if close enough
console.log("Scrolling complete.");
return;
}
const distance = Math.min(50, Math.abs(targetScrollPosition - currentScrollTop)); // Speed of scroll
const direction = currentScrollTop < targetScrollPosition ? 1 : -1;
if (scrollContainer === window) {
window.scrollBy(0, distance * direction);
} else {
scrollContainer.scrollBy(0, distance * direction);
}
requestAnimationFrame(step); // Continue scrolling
}
requestAnimationFrame(step); // Start the loop
}
function handleKeyDown(event) {
if (['PageDown', 'PageUp', 'Home', 'End'].includes(event.key)) {
// Allow default behavior if input/textarea/contenteditable is focused
if (isInputTextareaOrContentEditableFocused()) {
console.log("Input/textarea/contenteditable focused: allowing default behavior.");
return;
}
const now = Date.now();
// Reset multiplier for a new key press
if (!isKeyHeld) {
isKeyHeld = true;
scrollSpeedMultiplier = 1;
} else if (now - lastKeyPressTime < multiplierResetTime) {
// Increment multiplier for rapid presses
scrollSpeedMultiplier += 1;
} else {
// Reset multiplier if timeout passed
scrollSpeedMultiplier = 1;
}
scrollSpeedMultiplier = Math.min(scrollSpeedMultiplier, 5); // Limit the multiplier
lastKeyPressTime = now; // Update last press time
const immediateScroll = updateTargetScrollPosition(event.key);
if (!immediateScroll) {
smoothScroll();
}
event.preventDefault(); // Prevent default browser behavior
}
}
function handleKeyUp(event) {
if (['PageDown', 'PageUp', 'Home', 'End'].includes(event.key)) {
isKeyHeld = false; // Reset key hold tracking
clearTimeout(multiplierTimeout); // Clear any previous timeout
multiplierTimeout = setTimeout(() => {
if (Date.now() - lastKeyPressTime >= multiplierResetTime) {
scrollSpeedMultiplier = 1; // Reset speed multiplier after timeout
console.log("Speed multiplier reset due to inactivity.");
}
}, multiplierResetTime);
console.log(`Key released: ${event.key}`);
}
}
function addKeyListeners() {
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
console.log("Keydown and keyup listeners added.");
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addKeyListeners);
} else {
addKeyListeners();
}
}
// Initialize the script
initializeScrollHandler();
There are even variables to adjust it to your needs. You can find it on GitHub if you’d like to contribute.
If this script helped you take control of scrolling in ChatGPT or inspired you to tweak web apps to better suit your needs, don’t keep it to yourself! Give this article a clap, a like, or share it with friends who might be facing the same frustration. Got ideas for improving the script or other web app fixes you’d love to see? Drop a comment below — I’d love to hear your thoughts! Let’s make the web a better place, one fix at a time. 🚀