行为描述
当子元素和父元素属于嵌套关系的时候,滑动子元素至顶部/底部,会接着触发父元素的滚动。
在某些情景下需要禁掉父元素的滚动行为。

解决方案
overflow:hidden
鼠标划入子元素的时候,设置父元素css的overflow:hidden
const usePreventRootScroll = ({ rootDom, targetDom }) => {
let previousBodyOverflowSetting, previousBodyPaddingRight
const setOverflowHidden = () => {
const scrollWidth = rootDom.offsetWidth - rootDom.clientWidth
previousBodyOverflowSetting = targetDom.style.overflow
rootDom.style.overflow = 'hidden'
previousBodyPaddingRight = targetDom.style.paddingRight
rootDom.style.paddingRight = `${scrollWidth}px`
}
const clearOverflowHidden = () => {
if (previousBodyOverflowSetting !== undefined) {
rootDom.style.overflow = previousBodyOverflowSetting
}
if (previousBodyPaddingRight !== undefined) {
rootDom.style.paddingRight = previousBodyPaddingRight
}
}
React.useEffect(() => {
if (rootDom && targetDom) {
targetDom.addEventListener('mouseenter', setOverflowHidden)
targetDom.addEventListener('mouseleave', clearOverflowHidden)
}
return () => {
if (rootDom && targetDom) {
targetDom.removeEventListener('mouseenter', setOverflowHidden)
targetDom.removeEventListener('mouseleave', clearOverflowHidden)
}
}
}, [])
}
缺陷
-
移动端需要兼容,特别是安卓。
-
切换overflow属性导致滚动条显示/隐藏状态的切换,带来的页面轻微抖动。( windows 上面,返回的滚动条宽度不准确)
overscroll-behavior
设置子元素的css属性 overscroll-behavior :contain
缺陷
- 移动端兼容性问题巨大; safari / ie 团灭

监听 wheel 事件
const usePreventRootScroll = ({ rootDom, targetDom }) => {
// 阻止自元素滚动触底之后父元素滚动
const firstDeltaY = React.useRef(0)
const setScrollTop = React.useMemo(() => {
const debounceSetScrollTop = debounce(value => {
targetDom.scrollTop = value
}, 50)
return debounceSetScrollTop
}, [targetDom])
const setFirstDeltaY = value => {
if (value !== 'init') {
firstDeltaY.current = value
} else {
firstDeltaY.current = 0
}
}
const changePreventScroll = event => {
const { deltaY } = event
const { scrollTop, clientHeight, scrollHeight } = targetDom
// 如果是第一次,则直接阻止,后续,判断滚轮+ - 状态,不相同,则不阻止。
if (targetDom && targetDom.contains(event.target)) {
if (scrollTop === 0 || Math.abs(scrollTop + clientHeight - scrollHeight) < 1) {
if (firstDeltaY.current) {
const value = firstDeltaY.current
const checkNumberType = num => {
if (num === 0) {
return 0
}
if (num < 0) {
return '-'
} else {
return '+'
}
}
if (checkNumberType(value) === checkNumberType(deltaY)) {
event.preventDefault()
} else {
// 如果滚轮 deltaY === 0 的话,则进行矫正。
const offset = Math.max(Math.abs(deltaY), 7) // 小于 5 的话,会有抖动。
setScrollTop(scrollTop === 0 ? offset : scrollTop - offset)
setFirstDeltaY('init')
}
} else {
setFirstDeltaY(deltaY)
event.preventDefault()
}
} else {
setFirstDeltaY('init')
}
}
}
React.useEffect(() => {
if (rootDom && targetDom) {
rootDom.addEventListener('wheel', changePreventScroll)
}
return () => {
if (rootDom && targetDom) {
rootDom.removeEventListener('wheel', changePreventScroll)
}
}
}, [])
return [setFirstDeltaY]
}
缺陷
-
由于只是监听滚轮,如果是通过js设置scrollTop直接到底部,那么就会锁死滚轮事件;
-
判断是否解锁滚轮事件,取决于滚轮的方向,而滚轮方向是可以用户设置的,目前并没有方式能够取到用户向上滚动滚轮,视窗是否应该向上滚动。