17

rolling penetration

滚动穿透.gif

Problem Description

In the development of mobile WEB (the applet is also the same), as shown in the above screen recording, if a scroll bar appears when the page exceeds one screen height, slide on the fixed-positioned pop-up mask layer, and the content below it will also be displayed. Following the scrolling, it looks as if the event penetrates to the underlying DOM element, let's call it scroll penetration.

problem causes

It can be guessed that the scroll event of the document (document) is triggered, if you can disable the scroll event, it will be easier.

Case Pseudocode

<div class="btn">点击出现弹窗</div>

<div class="popup">
  <div class="popup-mask"></div>
  <div class="popup-body popup-bottom">
    <div class="header">我是标题</div>
    <div class="content">
      <div>0</div>        
      <div>1</div>
      <div>...</div>
    </div>
  </div>
</div>
.popup-mask {
  background-color: rgba(0, 0, 0, 0.5);
  position: fixed;
  z-index: 998;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

.popup-body {
  padding: 0 50px 40px;
  background-color: #fff;
  position: fixed;
  z-index: 999;
}

✅ Solution A (touch-action)

By default, pan (scroll) and zoom gestures are handled exclusively by the browser, but the behavior of touch gestures can be changed through the CSS property touch-action. Extract the values of several touch-actions as follows.

valuedescribe
autoEnables the browser to handle all pan and zoom gestures.
noneDisables browser handling of all pan and zoom gestures.
manipulationEnable pan and zoom gestures, but disable other non-standard gestures, such as double-tap to zoom.
pinch-zoomEnables multi-finger panning and zooming of the page.

So setting this property on the popup element and disabling all gestures on the element (and its non-scrollable descendants) solved the problem.

.popup {
  touch-action: none;
}

Note: [Accessibility Design] Preventing page zooming may prevent people with poor eyesight from reading and comprehending page content, but the applet itself doesn't seem to be zoomable!

✅ Solution B (event.preventDefault)

A standard from the W3C.

描述.jpg

To the effect, calling the preventDefault method on the touchstart and touchmove events prevents the default behavior of any associated events, including mouse events and scrolling.

So we can handle it like this.
Step 1. Listen to the touchmove event of the popup's outermost element (popup) and prevent the default behavior to disable all scrolling (including scrolling elements inside the popup).
Step 2. Release the scrolling element in the pop-up window and allow it to scroll: also monitor the touchmove event, but prevent the bubbling behavior (stopPropagation) of the scrolling element, so that the outermost element (popup) cannot receive the touchmove event when scrolling .

const popup = document.querySelector('.popup')
const scrollBox = document.querySelector('.content')

popup.addEventListener('touchmove', (e) => {
  // Step 1: 阻止默认事件
  e.preventDefault()
})

scrollBox.addEventListener('touchmove', (e) => {
  // Step 2: 阻止冒泡
  e.stopPropagation()
})

scroll overflow

滚动溢出.gif

Problem Description

As shown in the above screen recording, the pop-up window also contains scrolling elements. When the scrolling element scrolls to the bottom or top, scrolling down or up will also trigger the scrolling of the page. This phenomenon is called scroll chain ( scroll chaining), but the name overscroll feels more eloquent.

❌ Solution A (overscroll-behavior)

overscroll-behavior is a CSS feature that allows to control the behavior of the browser scrolling to the border. It has the following values.

valuedescribe
autoBy default, scrolling of an element is propagated to ancestor elements.
containBlocks the scroll chain, scrolling is not propagated to ancestor elements, but local effects of the node itself are displayed. For example the glow effect for overscrolling on Android or the rubber band effect on iOS.
noneSame as contain, but blocks its own over-effects.

So the problem can be solved like this:

.content {
  overscroll-behavior: none;
}

Simple, clean and high-performance, but Safari does not support the entire system, the compatibility is as follows, do you feel that Safari is a modern version of IE (I heard from passers-by by chance)!
兼容性.jpg

✅ Solution B (event.preventDefault)

Borrowing the ability of event.preventDefault, when the component scrolls to the bottom or top, all scrolling is blocked by calling event.preventDefault, so that page scrolling will not be triggered, and no processing will be done between scrolling.

let initialPageY = 0

scrollBox.addEventListener('touchstart', (e) => {
    initialPageY = e.changedTouches[0].pageY
})

scrollBox.addEventListener('touchmove', (e) => {
    const deltaY = e.changedTouches[0].pageY - initialPageY
    
    // 禁止向上滚动溢出
    if (e.cancelable && deltaY > 0 && scrollBox.scrollTop <= 0) {
        e.preventDefault()
    }

    // 禁止向下滚动溢出
    if (
        e.cancelable &&
        deltaY < 0 && 
        scrollBox.scrollTop + scrollBox.clientHeight >= scrollBox.scrollHeight
    ) {
        e.preventDefault()
    }
})

Complete solution demo

https://github.com/Barrior/cases/blob/main/overscroll.html#L107-L143


Barrior
598 声望61 粉丝

开源项目 JParticles 与 SchemaRender 作者