一名宅。 - JavaScript https://zhaiyiming.com/tag/JavaScript/ 通过 JavaScript 脚本解决移动端弹出层滚动穿透的问题 https://zhaiyiming.com/archives/html-js-popup-scroll-problem.html 2019-03-21T15:01:00+08:00 前端开发中经常会遇到一个麻烦的问题:在通过fixed或者absolute定位的弹出层上进行滚动操作的时候,滚动事件会穿透到底部导致body跟着滚动,及其影响交互体验。常规做法是在弹出层时候禁止body的滚动,将其设置为overflow: hidden,但是会导致滚动高度丢失从而造成视觉上一次弹动,所以我决定从弹出层本身考虑这个问题。 <body> Body content. <div id="popup" style="position: fixed; top: 0; right: 0; left: 0; bottom: 0"> Popup layer. </div> <script type="text/javascript"> const canScrollEl = ($el, up) => { const overflow = up ? Math.abs($el.scrollTop) > Number.EPSILON : $el.scrollHeight - $el.scrollTop - $el.clientHeight > Number.EPSILON; if (!overflow) { return false; } const styles = getComputedStyle($el); if (styles['overflow-y'] !== 'auto' && styles['overflow-y'] !== 'scroll') { return false; } return true; }; const canScrollEls = ($child, $root, up) => { let $el = $child; while ($el) { if (canScrollEl($el, up)) { return true; } if ($el === $root) { break; } $el = $el.parentNode; } return false; }; const preventEvent = (e) => { e.preventDefault(); e.stopPropagation(); e.returnValue = false; return false; }; const eventData = { touchesPos: {}, moving: false, canScroll: false, }; const el = document.getElementById('popup'); el.addEventListener('mousewheel', (e) => { if (canScrollEls(e.target, el, e.wheelDelta > 0)) { return void 0; } return preventEvent(e); }, { capture: true, passive: false, once: false }); el.addEventListener('touchstart', (e) => { // record touch start pos Object.keys(e.changedTouches).forEach((i) => { const touch = e.changedTouches[i]; eventData.touchesPos[touch.identifier] = { startY: touch.clientY, currentY: touch.clientY, }; }); eventData.moving = false; eventData.canScroll = false; }, { capture: true, passive: false, once: false }); el.addEventListener('touchmove', (e) => { // update current touch pos and calc touches sum delta distance let touchDeltaY = 0; Object.keys(e.changedTouches).forEach((i) => { const touch = e.changedTouches[i]; const cache = eventData.touchesPos[touch.identifier]; if (cache) { touchDeltaY += touch.clientY - cache.currentY; cache.currentY = touch.clientY; } }); const canScroll = canScrollEls(e.target, el, touchDeltaY > 0); if (!eventData.moving) { // if first move cannot scroll this layer, all move after will not scroll this layer eventData.moving = true; eventData.canScroll = canScroll; } if (canScroll && eventData.canScroll) { return void 0; } return preventEvent(e); }, { capture: true, passive: false, once: false }); el.addEventListener('touchend', (e) => { if (e && e.touches && e.touches.length !== 0) { return; } eventData.touchesPos = {}; }, { capture: true, passive: false, once: false }); </script> </body> 如果直接禁止touchmove事件,那么子元素有滚动区域时将也无法滚动,所以我们用上面一段代码来判断,动态判断是否要禁止事件。 如果你是Vue开发者,直接使用封装好的包即可:https://www.npmjs.com/package/vue-prevent-overscroll.js