前端开发中经常会遇到一个麻烦的问题:在通过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
版权属于:一名宅。
本文链接:https://zhaiyiming.com/archives/html-js-popup-scroll-problem.html
转载时须注明出处及本声明