iOS设备下含内部滚动组件的Next.js页面滚动冻结问题及禁用页面HTML滚动的方案咨询
Hey there, let's tackle this annoying scroll freezing issue you're hitting on iOS with your Next.js 15.2.3 app—this is such a common headache with nested scrolls in Safari!
First, let's break down why this happens: iOS Safari has that signature "bounce scroll" behavior. When you scroll your inner list to its top or bottom boundary and keep dragging, Safari tries to kick in the page-level bounce scroll. The switch between these two scroll contexts can jank up the browser's rendering thread, leading to that few-second freeze you're seeing.
Here are two practical solutions to fix this, starting with the most straightforward one that disables page-level HTML scroll entirely:
方案一:完全禁用页面级滚动,交给容器控制
This approach cuts off the root of the conflict by taking away Safari's ability to trigger page bounce scroll.
Step 1: Lock html/body in global styles
Add these styles to your global CSS file (likeglobals.cssin Next.js) to stop the top-level page from scrolling:html, body { height: 100%; overflow: hidden; touch-action: none; }Step 2: Make your main page container scrollable
Wrap your page content (excluding the inner scroll list, or use this as the main wrapper) in a container with these styles to handle the page's scroll instead:.main-page-container { height: 100vh; overflow-y: auto; touch-action: pan-y; -webkit-overflow-scrolling: touch; /* Keep iOS's smooth scroll feel */ }Step 3: Style your inner scroll component properly
Ensure your nested list has the right styles to scroll smoothly without conflicting:.inner-scrollable-list { overflow-y: auto; -webkit-overflow-scrolling: touch; /* Critical: Set a fixed/max height so the scroll triggers */ max-height: 60vh; }
方案二:阻止滚动事件冒泡(适合需要保留页面滚动的场景)
If you can't fully disable page scroll, you can intercept the scroll event on your inner list when it hits boundaries to stop it from bubbling up to the page:
import { useRef, useEffect } from 'react'; const InnerScrollList = ({ children }) => { const listRef = useRef(null); useEffect(() => { const handleScroll = (e) => { const listElement = listRef.current; if (!listElement) return; // Check if we're at the top or bottom of the inner list const isAtTop = listElement.scrollTop === 0; const isAtBottom = listElement.scrollHeight - listElement.scrollTop === listElement.clientHeight; // Block the event from reaching the page if at boundary if (isAtTop || isAtBottom) { e.preventDefault(); } }; const element = listRef.current; element?.addEventListener('touchmove', handleScroll, { passive: false }); // Cleanup the event listener on unmount return () => { element?.removeEventListener('touchmove', handleScroll); }; }, []); return ( <div ref={listRef} className="inner-scrollable-list"> {children} </div> ); }; export default InnerScrollList;
Quick notes to keep in mind:
- Don't skip
-webkit-overflow-scrolling: touch—without it, iOS scroll will feel choppy instead of smooth. touch-actionsettings are key:touch-action: nonelocks all touch actions on html/body, so you must explicitly enablepan-yon containers that need to scroll.- If you're using Next.js's App Router, apply the global styles in your root layout component to make sure they take effect everywhere.
I'd recommend starting with方案一 first—it's the most reliable fix for eliminating the freeze entirely by removing the scroll context switch. If you need the page to scroll in some cases, then方案二 can help mitigate the issue.
内容来源于stack exchange




