1. Problem description

  • On the mobile sideH5Page, we often encounterClick button -> Popover -> Select OptionsThis is the scene. When there are too many options, scroll the scrollbar to the bottom or top of the container. When you drag the scroll bar up or down, the scroll action will appear to penetrate the bottombodyIt’s going to roll along.
  • Problem summary: Scrolling penetration occurs when content is scrolled to the top or bottom of the container and then forced to scroll up or down
  • Best example: Jingdong white bar popover

Second, solution exploration

Referring to a lot of online solutions, can be divided into three types of methods. After careful thinking and analysis, my personal summary is as follows:

usejsTo control and changecss

  1. The popover appears 1.1. Record the scrollTop 1.2 where click the popover button appears. Give the body style {‘overflow’: ‘hidden’}

  2. Unblock the body style {‘overflow’: ‘hidden’} 2.2. Give the body style {‘top’: scrollTop}

    Advantages: simple and fast implementation disadvantages: in the popover opening and closing time, if the popover is not covered with the whole window, you will see the body flashing

usejsTo control the default scrolling events in the popover content area

  1. Listen for the touchStart and TouchMove events 1.2 for the content container layoutBox. Listen for the TouchStart event for the start of the content area targetY 1.3. Listen for the TouchMove event to see where newTargetY 1.4 changed during scrolling. ScrollTop/scrollHeight/height of the current container clientHeight 1.5. ScrollTop/scrollHeight/height of the current container clientHeight 1.5. Prevents the default behavior of the content container when scrolling to the top and bottom. (Key points)

  2. The popover closes properly

    Advantages: Starting from the source of the problem of scrolling penetration, the problem can be solved. Js implementation does not have ios compatibility problems. Disadvantages: Real machine verification, certain brand models have compatibility problems

Popover content area prohibited scrolling, usejsSimulated scroll bar

  1. 1.1. Listen for TouchMove events, blocking default behavior 1.2 throughout. Listen to the TouchStart and TouchMove events to record the distance the finger has moved, and use the Transform: Translate3D () property to scroll the content

  2. The popover closes properly

    Pros: JS implementation does not have ios compatibility issues Disadvantages: ios lost the native scroll bar rebound experience

The second solution adopted by the author, let us continue to go down

Three, reviewscrollTop / scrollHeight / clientHeight

First, we go to MDN to query the definitions of these three attributes

  • scrollTop

  • scrollHeight

  • clientHeight

  • combination
Image from MDN, link: https://developer.mozilla.org/zh-CN/docs/Web/API/Element/clientHeight https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollHeight https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollTop infringement is deleted immediatelyCopy the code

Iv. Problems encountered

Touchmove does not block the default behavior.

On the mobile device, TouchMove cannot directly prevent the default behavior. The reason is that using passive mode improves the scrolling performance

Abstract:

Knowledge: EventTarget. AddEventListener (type, the listener, the options) the passive in the options field

To block page scrolling, set passive to false in Options.

V. Initial realization

I’ll write it as a mixin

/ * * *@author cunhang_wei
 * @description Fixed popover content area scrolling through to body *@param $refs.layoutBox needs to specify the content container in advance */

export default {
    data () {
        return {
            targetY: 0
        }
    },

    mounted () {
        if (this.$refs.layoutBox) {
            this.$el.addEventListener('touchstart'.this.handleTouchstart)
            this.$el.addEventListener('touchmove'.this.handleTouchmove, {
                passive: false})}},methods: {
        handleTouchstart (e) {
            this.targetY = Math.floor(e.targetTouches[0].clientY) // The initial touch position of the finger
            console.log('handleTouchstart'.this.targetY)
        },
        handleTouchmove (e) {
            let layoutBox = this.$refs.layoutBox // The content container
            let newTargetY = Math.floor(e.targetTouches[0].clientY) // Touch position in finger slide
            let sTop = layoutBox.scrollTop // The contents scroll to the height of the top of the container
            let sHeight = layoutBox.scrollHeight // The scrolling height of the content
            let cliHeight = layoutBox.clientHeight // The height of the current content container
            if (sTop <= 0 && newTargetY - this.targetY > 0 && e.cancelable) {
                console.log('Drop down to the top of the page')
                e.preventDefault()
            } else if (sTop >= sHeight - cliHeight && newTargetY - this.targetY < 0 && e.cancelable) {
                console.log('Scroll up to the bottom of the page') e.preventDefault()}}}, beforeDestroy () {if (this.$refs.layoutBox) {
            this.$el.removeEventListener('touchstart'.this.handleTouchstart)
            this.$el.removeEventListener('touchmove'.this.handleTouchmove)
        }
    }
}
Copy the code

After writing, I found that every time I control the scrolling through the popover, I need to introduce this mixin file, which is a bit cumbersome. After checking Vue’s official document, I found a better way, that is, global instructions

Vi. Optimization of writing method

Write it as a global instruction no-through

/ * * *@author cunhang_wei
 * @description Fixed popover content area scrolling through the body (90% coverage) *@description - no - through the usage * (ul v > * < li > < / li > * < li > < / li > * < / ul > * * /

// The global variable targetY
var targetY = 0
export default {
    name: 'no-through'.bind: function (el, binding) {
        // Add a property to the binding object so that it can be fetched when unbind
        binding.handleTouchstart = function (event) {
            targetY = Math.floor(event.targetTouches[0].clientY) // The initial touch position of the finger
        }
        binding.handleTouchmove = function (event) {
            let newTargetY = Math.floor(event.targetTouches[0].clientY) // Touch position in finger slide
            let sTop = el.scrollTop // The contents scroll to the height of the top of the container
            let sHeight = el.scrollHeight // The scrolling height of the content
            let cliHeight = el.clientHeight // The height of the current content container
            if (sTop <= 0 && newTargetY - targetY > 0 && event.cancelable) {
                console.log('Drop down to the top of the page')
                event.preventDefault()
            } else if (sTop >= sHeight - cliHeight && newTargetY - targetY < 0 && event.cancelable) {
                console.log('Scroll up to the bottom of the page')
                event.preventDefault()
            }
        }
        el.addEventListener('touchstart', binding.handleTouchstart)
        el.addEventListener('touchmove', binding.handleTouchmove, {
            passive: false})},unbind: function (el, binding) {
        // Reset the global variable targetY
        targetY = 0
        el.removeEventListener('touchstart', binding.handleTouchstart)
        el.removeEventListener('touchmove', binding.handleTouchmove, {
            passive: false}}})// Finally, go to main.js and register it as a global directive.
Copy the code

Seven, real machine test

  • Ios test passed ios13
  • Xiaomi and Redmi phones tested through Android 10
  • The OnePlus phone tested android 10
  • Huawei phone test passed emui11 android 10
  • – There is a compatibility issue with Samsung S8 (it is estimated that it is related to the underlying implementation of Samsung webView)

Viii. Summary and thinking

  • The key to solving the problem lies in:
  1. Do you want to know when rolling penetration occurs
  2. Want to knowscrollTop / scrollHeight / clientHeightSo these three properties, combined with them how do you tell if you’re scrolling at the top or the bottom
  3. H5 mobile terminaltouchmoveEvents are not supported by defaultpreventDefault(), you need to manually enable it
  4. Event Capture and Event Bubble knowledge:

  • Optimization of writing method:
  1. We were using theVueFramework when reusable JS logic, it is recommended to write as mixedmixins
  2. And for the need to targetDOMIf the element has changed, it is recommended to write instructionsdirectives

Finally, good study will not be bad! I love your little ship, remember to like after watching

Next update (2021/6/25)

On a recent visit to Github, I found a more perfect solution: body-scroll-lock, which solves this problem perfectly

Practice down, the effect is also very good, you can have a try