1. 首页

vue组件库之popup弹窗组件

业务背景

在做活动时经常需要实现各种各样的弹窗,有一些常见的问题需要处理,包含:

  • 滑动穿透问题:滑动弹窗元素导致背景元素滚动
  • 多弹窗层级问题:当有多弹窗时,最新的弹窗永远在最上层
  • 出现/消失过渡动画

实现

已发布npm包欢迎使用反馈和star~

Js中文网 – 前端进阶资源教程 www.javascriptC.com,typescript 中文文档
一个帮助开发者成长的社区,你想要的,在这里都能找到


npm install jdc-popup -S

github: github.com/jinglecjy/j…
demo: jinglecjy.github.io/jdc-popup/d…

demo

滑动穿透问题

初始的解决方案

打开浮层时fixed底部元素,同时为了保持body的位置与打开浮层前一致,设置top偏移为当前scrollTop;
关闭浮层时恢复底部元素状态和滚动高度;

controlledBgScrolled() {
    let bgEle = document.getElementById('app');
    // 打开浮层
    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%';
    }
    //码农进阶题库,每天一道面试题 or Js小知识 https://www.javascriptc.com/interview-tips/
    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;
    }
}

这个方案的缺点是:

  1. 打开/关闭弹窗瞬间,可以看到有闪动
  2. 在APP内嵌页面,与APP原生有几率发生冲突

使用库实现

测试提出bug的时间比较紧急,所以直接引入了已有的库解决了该问题,经过测试,ios8+,android4.4+下没有发现问题。
库的源码比较清晰,之前的个人的思路都是想要一套代码兼容各端,这个库是将问题细分,对不同端进行了不同的处理,更容易去兼容。这三种方案都无法完美兼容所有端,详细可以查看参考[2]。

PC端

PC端实现比较简单,通过在body设置overflow: hidden就OK了。

const $body = document.querySelector('body')
const bodyStyle = { ...$body.style }
const scrollBarWidth = window.innerWidth - document.body.clientWidth
// 打开浮层时
$body.style.overflow = 'hidden'
$body.style.boxSizing = 'border-box'
$body.style.paddingRight = `${scrollBarWidth}px`
// 关闭浮层时,恢复原始设置
['overflow', 'boxSizing', 'paddingRight']
.forEach((x: OverflowHiddenPcStyleType) => {
    $body.style[x] = bodyStyle[x] || ''
}

Android端

Android端的实现与个人的方案思路基本一致,打开弹层时将底部元素fixed并设置top偏移,关闭浮层时恢复现场,注意到底部元素必须同时设置html和body。


const scrollTop = $html.scrollTop || $body.scrollTop const htmlStyle = { ...$html.style } const bodyStyle = { ...$body.style } // 打开浮层时,fixed底部 $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' // 关闭浮层时,恢复现场 $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)

iOS端

iOS端在打开浮层时,禁用了底部的touchmove事件,如果弹窗内部元素需要可滚动,则通过另外的函数自行处理。关闭浮层时移除所有事件监听。经测试,该方案在Android下弹窗滚动到边界时,底部元素有几率出现滚动。该库在iPhone 6p下初次打开无法滚动。


/*** 打开浮层时,处理浮层和底部元素的滚动事件 ***/ // 1. targetElement为需要滚动的元素容器,处理其滚动 //码农进阶题库,每天一道面试题 or Js小知识 https://www.javascriptc.com/interview-tips/ if (targetElement && lockedElements.indexOf(targetElement) === -1) { targetElement.ontouchstart = (event) => { initialClientY = event.targetTouches[0].clientY } targetElement.ontouchmove = (event) => { if (event.targetTouches.length !== 1) return // 手动处理滚动 handleScroll(event, targetElement) } // 记录可滚动元素 //码农进阶题库,每天一道面试题 or Js小知识 https://www.javascriptc.com/interview-tips/ lockedElements.push(targetElement) } const handleScroll = (event, targetElement) => { const clientY = event.targetTouches[0].clientY - initialClientY if (targetElement) { const { scrollTop, scrollHeight, clientHeight } = targetElement // 向上滚动时 且 已经到达顶部 const isOnTop = clientY > 0 && scrollTop === 0 // 当向下滚动 且 已经到达底部 const isOnBottom = clientY < 0 && scrollTop + clientHeight + 1 >= scrollHeight if (isOnTop || isOnBottom) { return preventDefault(event) } } event.stopPropagation() return true } // 2. 禁止document的touchMove事件 if (!documentListenerAdded) { document.addEventListener( 'touchmove', preventDefault, eventListenerOptions) documentListenerAdded = true } /*** 关闭浮层时,移除时间监听 ***/ 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 }

ui组件库都是怎么实现的?

用库可以较好的完成这个需求,但是对于滑动穿透这样的小问题,每次都引入库解决有点小题大做。基于上述考虑,选取了京东的nutui/有赞的vant/饿了么的mintui对比其实现方案,对比如下:

组件库

实现思路

实现形式

vant

touch事件处理

mixins,抽取了复杂复用逻辑

mint

touch事件处理

mixins,抽取了复杂逻辑,但实现方案上依赖组件结构,复用性不强

nut

fixed底层背景

组件内部函数,简洁易读,复用性不强

对比了一下三个方案,并实际测试(iOS8+/Android4+),还是无法兼容所有机型,最终的还是按照库的基本思路包装了一下组件。
另外加入overscroll-behavior虽然兼容性不佳,但是安卓原生浏览器和chrome浏览器仍然有部分支持。


// 对于半透明蒙层阻止滚动 mask.addEventListener( 'touchmove', this.preventDefault, { capture: false, passive: false }, false); // 对可滚动元素容器手动处理滚动 onTouchMove(event, targetElement) { ... if (targetElement) { const { scrollTop, scrollHeight, clientHeight } = targetElement // 向上滚动时 且 已经到达顶部 const isOnTop = this.deltaY > 0 && scrollTop === 0 // 当向下滚动 且 已经到达底部 const isOnBottom = this.deltaY < 0 && scrollTop + clientHeight + 1 >= scrollHeight if (isOnTop || isOnBottom) { this.preventDefault(event) } } event.stopPropagation() return true }//码农进阶题库,每天一道面试题 or Js小知识 https://www.javascriptc.com/interview-tips/ // 关闭弹窗时,移除所有事件 ...

多弹窗层级问题

当前后打开两个弹窗,用户的预期是按照打开的先后顺序,越后打开的弹窗在越上层,简而言之就是新弹窗永远在最上层。可以通过记录当前出现过的最大zIndex,新弹窗zIndex = zIndex+1。 另外滑动穿透问题在多弹窗情况下也需要处理,对于非当前最高层级弹窗,不应当收到滚动影响。


this.$el.style.zIndex = context.zIndex + 1; context.zIndex += 1;

扩展

业务上的弹窗样式一般比较复杂,如果只是简单通用的弹窗样式,想要解决滑动穿透问题,可以通过Vue.extend扩展,将弹窗组件直接挂载到document下(element-ui中就使用了类似的做法),不会和主体内容互相影响,调用起来也更加灵活。

参考

[1] tua-scroll-body-lock:tuateam.github.io/tua-body-sc…
[2] 滑动穿透(锁body)终极探索]:juejin.im/post/5ca481…
[3] vant-popup]:blog.csdn.net/riddle1981/…
[4] NUTUI:github.com/jdf2e/nutui

作者:吉玉
链接:https://juejin.im/post/5e00dc586fb9a016091dfcb7

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:vue组件库之popup弹窗组件

链接:https://www.javascriptc.com/3164.html

« 通过编写简易版虚拟DOM,来理解虚拟DOM 的原理
【TypeScript 进化史 — 3】标记联合类型 与 never 类型»
Flutter 中文教程资源

相关推荐

QR code