Business background

There are some common problems that need to be dealt with, including:

  • Sliding penetration problem: Sliding a window element causes background elements to scroll
  • Multi-popover hierarchy problem: When there are multiple popovers, the latest popover is always at the top
  • Appear/disappear transition animation

implementation

Published NPM packages are welcome to use feedback and STAR ~

npm install jdc-popup -S
Copy the code

github: Github.com/winniecjy/j…

demo: Winniecjy. Making. IO/JDC – the popup/d…

Sliding penetration problem

Initial solution

Fixed bottom element when floating layer is opened, and set top offset to current scrollTop in order to keep the position of body the same as before floating layer is opened. Restore bottom element state and roll height when closing float layer;

controlledBgScrolled() {
    let bgEle = document.getElementById('app');
    // Open the floating layer
    if (this.showPopup) {
        let top = document.documentElement.scrollTop
          || window.pageYOffset
          || document.body.scrollTop;
        this.scrollTop = top;
        bgEle.style.position = 'fixed';
        bgEle.style.top = ` -${top}px`;
        bgEle.style.height = '100%';
    } 
    else {
        bgEle.style.position = 'relative';
        bgEle.style.top = ' ';
        bgEle.style.height = '100%';
        document.documentElement.scrollTop = this.scrollTop; 
        window.pageYOffset = this.scrollTop;
        document.body.scrollTop = this.scrollTop; }}Copy the code

The disadvantages of this scheme are:

  1. Open/close the popover moment, you can see a flash
  2. Embedded pages in the APP may conflict with the original APP

Using library implementation

It was urgent to put forward the bug in the test, so the existing library was directly introduced to solve the problem. After the test, no problem was found under ios8+ and android4.4+. Library source code is more clear, before the personal idea is to want a set of code compatible with each end, the library is to subdivide the problem, the different end of the different processing, easier to compatible. None of these three schemes can be perfectly compatible with all terminals. Please refer to [2] for details.

PC

On the PC side, the implementation is relatively simple and is OK by setting overflow: Hidden in the body.

const $body = document.querySelector('body')
constbodyStyle = { ... $body.style }const scrollBarWidth = window.innerWidth - document.body.clientWidth
// Open the floating layer
$body.style.overflow = 'hidden'
$body.style.boxSizing = 'border-box'
$body.style.paddingRight = `${scrollBarWidth}px`
// When the float is closed, the original Settings are restored
['overflow'.'boxSizing'.'paddingRight']
.forEach((x: OverflowHiddenPcStyleType) = > {
    $body.style[x] = bodyStyle[x] || ' '
}
Copy the code

The Android end

The implementation of Android terminal is basically consistent with the personal solution idea. When opening the elastic layer, fixed element at the bottom and set top offset are set, and when closing the floating layer, the scene is restored. Note that HTML and body must be set at the bottom of the element.

const scrollTop = $html.scrollTop || $body.scrollTop
consthtmlStyle = { ... $html.style }constbodyStyle = { ... $body.style }// Open float layer, fixed bottom
$html.style.height = '100%'
$html.style.overflow = 'hidden'
$body.style.top = ` -${scrollTop}px`
$body.style.width = '100%'
$body.style.height = 'auto'
$body.style.position = 'fixed'
$body.style.overflow = 'hidden'

// Restore the scene when closing the floating layer
$html.style.height = htmlStyle.height || ' '
$html.style.overflow = htmlStyle.overflow || ' '
['top'.'width'.'height'.'overflow'.'position']
.forEach((x: OverflowHiddenMobileStyleType) = > {
  $body.style[x] = bodyStyle[x] || ' '
})

window.scrollTo(0, scrollTop)
Copy the code

The iOS side

When the floating layer is opened on iOS, the touchMove event at the bottom is disabled. If the elements inside the popover need to be scrollable, they will be handled by another function. Remove all event listeners when closing floats. After testing the scheme, the bottom element has a chance to scroll when scrolling to the boundary under Android popover. The library doesn’t scroll when first opened on the iPhone 6P.

/*** When the float layer is opened, the float layer and the bottom element are rolled ***/
// 1. targetElement is the element container that needs to be rolled
if (targetElement && lockedElements.indexOf(targetElement) === -1) {
    targetElement.ontouchstart = (event) = > {
        initialClientY = event.targetTouches[0].clientY
    }
    targetElement.ontouchmove = (event) = > {
        if(event.targetTouches.length ! = =1) return
        // Manually handle scrolling
        handleScroll(event, targetElement)
    }
    // Record scrollable elements
    lockedElements.push(targetElement)
}
const handleScroll = (event, targetElement) = > {
    const clientY = event.targetTouches[0].clientY - initialClientY
    if (targetElement) {
        const { scrollTop, scrollHeight, clientHeight } = targetElement
        // Scroll up and reach the top
        const isOnTop = clientY > 0 && scrollTop === 0
        // When you scroll down and reach the bottom
        const isOnBottom = clientY < 0
                          && scrollTop + clientHeight + 1 >= scrollHeight
        if (isOnTop || isOnBottom) {
            return preventDefault(event)
        }
    }
    event.stopPropagation()
    return true
}

// 2. Disallow the document touchMove event
if(! documentListenerAdded) {document.addEventListener(
      'touchmove',
      preventDefault,
      eventListenerOptions)
    documentListenerAdded = true
}
/*** remove time listener ***/ when closing float layer
if (targetElement) {
    const index = lockedElements.indexOf(targetElement)
    if(index ! = = -1) {
        targetElement.ontouchmove = null
        targetElement.ontouchstart = null
        lockedElements.splice(index, 1)}}if (documentListenerAdded) {
    document.removeEventListener(
      'touchmove',
      preventDefault,
      eventListenerOptions)
    documentListenerAdded = false
}
Copy the code

How is the UI component library implemented?

Using a library can be a good way to accomplish this requirement, but for a small problem like sliding penetration, it is a bit of a storm in a teacup to import the library every time. Based on the above considerations, the implementation schemes of NUtui of JD, Vant of Jd, and Mintui of Ele. me are selected for comparison. The comparison is as follows:

Component library Implementation approach The realization form
vant Touch event Handling Mixins extract complex reuse logic
mint Touch event Handling Mixins extract complex logic, but the implementation scheme depends on the component structure, and the reuse is not strong
nut Fixed Underlying background Component internal function, simple and easy to read, not strong reuse

Compared the three schemes, and the actual test (iOS8+/Android4+), or not compatible with all models, the final or according to the basic ideas of the library packaging components. In addition, although adding overscroll behavior is not compatible, it is partially supported by native Android browsers and Chrome browsers.

// Prevents scrolling for semi-transparent layers
mask.addEventListener(
  'touchmove'.this.preventDefault,
  { capture: false.passive: false },
  false);
// Manually handle scrolling for scrollable element containers
onTouchMove(event, targetElement){...if (targetElement) {
        const {
            scrollTop,
            scrollHeight,
            clientHeight
        } = targetElement
        // Scroll up and reach the top
        const isOnTop = this.deltaY > 0 && scrollTop === 0
        // When you scroll down and reach the bottom
        const isOnBottom = this.deltaY < 0
                          && scrollTop + clientHeight + 1 >= scrollHeight
        if (isOnTop || isOnBottom) {
            this.preventDefault(event)
        }
    }
    event.stopPropagation()
    return true
}
// Remove all events when closing the popover.Copy the code

Multiple popover levels

When two pop-ups are opened at the end of the current window, the user’s expectation is that the pop-ups that are opened at the end of the current window will be in the upper layer. In short, the new pop-ups will always be in the upper layer. You can record the current maximum zIndex, the new popup zIndex = zIndex+1. In addition, the sliding penetration problem also needs to be dealt with in the case of multiple popovers. For popovers not of the current highest level, they should not be affected by scrolling.

this.$el.style.zIndex = context.zIndex + 1; 
context.zIndex += 1;
Copy the code

extension

Business popover styles are generally complex. If the popover style is simple and universal, you can mount the popover component directly to the document through vue. extend (a similar approach is used in Element-UI) without interacting with the main content. It is also more flexible to call.

reference

[1] tua – scroll – body – lock: tuateam. Making. IO/tua – body – sc… [2] Sliding through (lock body) Ultimate quest] : juejin.cn/post/684490… [3] vant – popup] : blog.csdn.net/riddle1981/… [4] NUTUI:github.com/jdf2e/nutui

AD time

Feizhu is bytedance’s office suite product, which deeply integrates functions such as instant communication, online collaboration, audio and video conferencing, calendar, cloud disk and workbench to provide users with one-stop collaboration experience. At present, feishu service customers have covered science and technology, Internet, information technology, manufacturing, construction and real estate, enterprise services, education, media and other fields. Welcome to the bytedance flying book team, there are a large number of front-end and back-end HC ~ scan the TWO-DIMENSIONAL code or click the link to send, look for the flying book team 👍 ~

Internal push code: HZNVPHS, delivery link:job.toutiao.com/s/JaeUCoc

[Recruitment] Delivery link:job.toutiao.com/s/JaevUNo