如何实现元素固定滚动直至其内部内容完全滚动完毕?
问题描述
我用HTML实现了一个移动端设备组件,需求是:当窗口滚动到该区域时,组件保持固定状态,直至内部内容完全滚动完毕。当前方案能实现内部滚动并阻止主页面滚动(模拟固定效果),但内部内容滚动完成后,页面无法恢复正常滚动行为——要么直接跳回顶部,要么滚动卡顿。
解决方案
原代码的核心问题是重复绑定滚轮事件(每次窗口滚动都会重新绑定一次),加上滚动逻辑判断时机错误,导致事件冲突和滚动行为异常。以下是修复方案:
1. 组件固定优化:用CSS原生特性替代JS模拟
给.err-mobile添加position: sticky,让组件自然固定在视口顶部,无需通过修改body的overflow属性强制固定,这是更稳定的原生实现方式:
.err-mobile { font-family: "NeuzeitGro-Bol", Sans-serif; display: block; width: 100%; height: calc(100vh - 10px); margin-top: 5px; overflow: hidden; position: sticky; /* 新增 */ top: 5px; /* 和margin-top值对应,保持组件位置一致 */ }
2. 重构JS逻辑:避免重复绑定,精准控制滚动
修改script.js,只绑定一次滚轮事件,同时准确判断组件是否在视口内、内部内容是否滚动到边界,从而决定是否允许页面滚动:
jQuery(document).ready(function ($) { const mobileDevice = $(".err-mobile"); const mobileView = $(".err-mobile .err-device .err-view"); let isDeviceInView = false; // 检查组件是否处于视口内 function checkDeviceInView() { const rect = mobileDevice[0].getBoundingClientRect(); // 组件顶部进入视口、底部未完全离开视口时,判定为在视口内 isDeviceInView = rect.top <= 5 && rect.bottom >= window.innerHeight - 5; } // 初始化时检查一次 checkDeviceInView(); // 监听窗口滚动,更新组件可视状态 $(window).scroll(function () { checkDeviceInView(); }); // 仅绑定一次滚轮事件,避免重复绑定引发的异常 $(document).on("wheel", function (e) { if (!isDeviceInView) return; // 组件不在视口时,允许页面正常滚动 const maxScroll = mobileView.prop("scrollHeight") - mobileView.height(); const currentScroll = mobileView.scrollTop(); const deltaY = e.originalEvent.deltaY; // 向上滚动,且内部内容未到顶部 → 只滚动组件内部 if (deltaY < 0 && currentScroll > 0) { e.preventDefault(); mobileView.scrollTop(currentScroll + deltaY); } // 向下滚动,且内部内容未到底部 → 只滚动组件内部 else if (deltaY > 0 && currentScroll < maxScroll) { e.preventDefault(); mobileView.scrollTop(currentScroll + deltaY); } // 内部内容已滚动到边界,允许页面继续滚动 }); });
修复说明
- 用
position: sticky实现组件固定,完全替代原方案中通过body overflow:hidden模拟固定的方式,避免了页面滚动状态强制切换导致的异常 - 仅绑定一次滚轮事件,解决了重复绑定引发的多次触发问题
- 通过
getBoundingClientRect()精准判断组件是否在视口内,仅在组件可视时介入滚动逻辑 - 滚动逻辑分层处理:优先响应组件内部滚动,只有内部滚动到边界时,才允许页面继续滚动,衔接更自然
修改后的完整代码
index.html(无修改)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>HTML Device</title> <link rel="stylesheet" href="styles.css" /> </head> <body> <div class="section-full">1</div> <div class="section-full">2</div> <div class="section-full">3</div> <div class="section-full">4</div> <div class="err-mobile"> <div class="err-device"> <div class="err-ring"> <div class="err-mic"></div> <div class="err-view"> <div class="err-cards"> <div class="err-card"> <div class="err-head">Communication<br><span class="err-secondary">Gaps</span></div> <div class="err-body"> <span class="err-icon no"></span> <p>Misunderstandings, and lack of clarity. Facing difficulty in locating effective questioners.</p> </div> </div> <div class="err-card"> <div class="err-head"><span class="err-secondary">High</span><br>Costs</div> <div class="err-body"> <span class="err-icon no"></span> <p>Unable to find a vendor that meets budget or offers reasonable pricing.</p> </div> </div> <div class="err-card"> <div class="err-head"><span class="err-secondary">Exhausting</span><br>Interviews</div> <div class="err-body"> <span class="err-icon no"></span> <p>Unable to find or retain all-star talent. Drainer of arranging extensive evaluations inhouse.</p> </div> </div> <div class="err-card"> <div class="err-head">Identifying<br><span class="err-secondary">Challenges</span></div> <div class="err-body"> <span class="err-icon warning"></span> <p>Need assistance identifying and addressing challenges during the entire business development process.</p> </div> </div> <div class="err-card"> <div class="err-head">Timeline<br><span class="err-secondary">Constraints</span></div> <div class="err-body"> <span class="err-icon no"></span> <p>Unable to locate a service provider who consistently meets specified timeline constraints for delivery.</p> </div> </div> <div class="err-card"> <div class="err-head"><span class="err-secondary">Quality</span><br>Issues</div> <div class="err-body"> <span class="err-icon no"></span> <p>Defects in produce services, or processes resulting in unsatisfactory outcomes.</p> </div> </div> <div class="err-card"> <div class="err-head"><img src="https://example.com/wp-content/uploads/2023/11/Logo.svg" alt="Logo"></div> <div class="err-body"> <span class="err-icon yes"></span> <p>Beyond Solutions, Building Partnerships</p> </div> </div> </div> </div> <div class="err-navigator"></div> <div class="err-power"></div> <div class="err-volume"> <div class="err-ringer"></div> <div class="err-up"></div> <div class="err-down"></div> </div> </div> </div> </div> <div class="section-full">-2</div> <div class="section-full">-1</div> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <script src="script.js"></script> </body> </html>
styles.css(修改部分已标注)
* { box-sizing: border-box; } body { font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; padding: 0; margin: 0; } .section-full { height: 100vh; } .err-secondary { color: #e37373; } .err-mobile { font-family: "NeuzeitGro-Bol", Sans-serif; display: block; width: 100%; height: calc(100vh - 10px); margin-top: 5px; overflow: hidden; position: sticky; /* 新增 */ top: 5px; /* 新增 */ } .err-mobile .err-device { max-width: 340px; height: 100%; margin: 0 auto; border: 3px solid #dcd2d2; border-radius: 40px; position: relative; } .err-mobile .err-device .err-ring { background-image: url(https://example.com/wp-content/uploads/2023/12/iphone-wallpaper-scaled.jpg); background-size: cover; background-repeat: no-repeat; width: 100%; height: 100%; border: 6px solid #000; border-radius: 40px; } .err-mobile .err-device .err-mic { max-width: 100px; height: 30px; border-radius: 30px; background-color: #000; margin: 5px auto; position: relative; z-index: 1; } .err-mobile .err-device .err-mic::after { content: ""; width: 20px; height: 20px; border-radius: 50%; background-color: #121212; position: absolute; top: 5px; right: 5px; } .err-mobile .err-device .err-view { max-width: 100%; height: 100%; overflow: hidden; overflow-y: auto; margin-top: -40px; border-radius: 40px; -ms-overflow-style: none; scrollbar-width: none; } .err-mobile .err-device .err-view::-webkit-scrollbar { display: none; } .err-mobile .err-device .err-view .err-cards { display: flex; flex-direction: column; gap: 10px; padding: 10px; padding-top: 60px; padding-bottom: 20px; } .err-mobile .err-device .err-view .err-cards .err-card { background: rgb(255 247 249 / 94%); padding: 20px; border-radius: 30px; box-shadow: 0 0 20px 0 rgb(255 240 241); position: relative; } .err-mobile .err-device .err-view .err-cards .err-card .err-head { font-family: "Rafiloma", Sans-serif; font-size: 28px; font-weight: 600; position: relative; } .err-mobile .err-device .err-view .err-cards .err-card .err-head img { max-height: 40px; width: auto; } .err-mobile .err-device .err-view .err-cards .err-card .err-head::after { content: ""; background-image: url(https://example.com/wp-content/uploads/2023/12/Close-Icon.png); background-repeat: no-repeat; background-size: cover; background-position: center; width: 16px; height: 16px; position: absolute; right: 0px; top: 25px; } .err-mobile .err-device .err-view .err-cards .err-card:last-child .err-head::after { background-image: url(https://example.com/wp-content/uploads/2023/11/success-icon.png); width: 20px; height: 20px; top: 10px; } .err-mobile .err-device .err-view .err-cards .err-card .err-body { font-size: 16px; font-weight: 500; display: flex; gap: 10px; align-items: center; } .err-mobile .err-device .err-view .err-cards .err-card .err-body .err-icon.no::before { content: ""; background-image: url(https://example.com/wp-content/uploads/2023/11/Error-Icon.png); background-repeat: no-repeat; background-size: cover; background-position: center; width: 50px; height: 50px; display: block; } .err-mobile .err-device .err-view .err-cards .err-card .err-body .err-icon.warning::before { content: ""; background-image: url(https://example.com/wp-content/uploads/2023/11/warning-icon.png); background-repeat: no-repeat; background-size: cover; background-position: center; width: 50px; height: 44px; display: block; } .err-mobile .err-device .err-view .err-cards .err-card .err-body .err-icon.yes::before { content: ""; background-image: url(https://example.com/wp-content/uploads/2023/11/success-icon.png); background-repeat: no-repeat; background-size: cover; background-position: center; width: 50px; height: 50px; display: block; } .err-mobile .err-device .err-navigator { max-width: 160px; height: 5px; border-radius: 5px; background-color: #ffffff; margin: 5px auto; margin-top: -10px; z-index: 1; } .err-mobile .err-device .err-power { content: ""; width: 5px; height: 80px; position: absolute; top: 40%; right: -5px; background-color: #dcd2d2; border-radius: 5px; } .err-mobile .err-device .err-volume .err-ringer, .err-mobile .err-device .err-volume .err-up, .err-mobile .err-device .err-volume .err-down { content: ""; width: 5px; height: 50px; position: absolute; top: 30%; left: -5px; background-color: #dcd2d2; border-radius: 5px; } .err-mobile .err-device .err-volume .err-ringer { height: 30px; } .err-mobile .err-device .err-volume .err-up { top: calc(30% + 55px); } .err-mobile .err-device .err-volume .err-down { top: calc(30% + 120px); }
script.js(替换为新代码)
jQuery(document).ready(function ($) { const mobileDevice = $(".err-mobile"); const mobileView = $(".err-mobile .err-device .err-view"); let isDeviceInView = false; // 检查组件是否处于视口内 function checkDeviceInView() { const rect = mobileDevice[0].getBoundingClientRect(); isDeviceInView = rect.top <= 5 && rect.bottom >= window.innerHeight - 5; } // 初始化时检查一次 checkDeviceInView(); // 监听窗口滚动,更新组件可视状态 $(window).scroll(function () { checkDeviceInView(); }); // 全局监听滚轮事件,控制滚动行为 $(document).on("wheel", function (e) { if (!isDeviceInView) return; const maxScroll = mobileView.prop("scrollHeight") - mobileView.height(); const currentScroll = mobileView.scrollTop(); const deltaY = e.originalEvent.deltaY; // 向上滚动,内部未到顶部 → 滚动内部 if (deltaY < 0 && currentScroll > 0) { e.preventDefault(); mobileView.scrollTop(currentScroll + deltaY); } // 向下滚动,内部未到底部 → 滚动内部 else if (deltaY > 0 && currentScroll < maxScroll) { e.preventDefault(); mobileView.scrollTop(currentScroll + deltaY); } // 内部已到边界,允许页面正常滚动 }); });
内容的提问来源于stack exchange,提问作者Noman Nawaz
相关产品推荐
相关产品推荐

