资讯专栏INFORMATION COLUMN

移动端 Modal 组件开发杂谈

learn_shifeng / 3101人阅读

摘要:网上谷歌一下滚动穿透关键字其实可以发现很多种解决方案,每个方案也各有优缺点,但我们选择的解决方案是团队的一姐一篇移动端体验优化的博文中得到的启示博文地址花式提升移动端交互体验。

Vant 是有赞开发的一套基于 Vue 2.0Mobile 组件库,在开发的过程中也踩了很多坑,今天我们就来聊一聊开发一个移动端 Modal 组件(在有赞该组件被称为 Popup )需要注意的一些

在任何一个合格的UI组件库中,Modal 组件应该是必备的组件之一。它一般用于用户处理事物,但又不希望跳转页面时,可以使用 Modal 在当前页面中打开一个浮层,承载对应的操作。相比PC端,移动端的 Modal 组件坑会更多,比如滚动穿透问题就不像PC端在 body 上添加 overflow: hidden 那么简单。

目录

一、API定义

二、水平垂直居中的方案

三、可恶的滚动穿透

四、position: fixed 失效

一、API定义

任何一个组件开始编码前都需要首先将API先定义好,才好根据API来提供对应的功能。Modal 组件提供了以下API:

更具体的 Api 介绍可以访问该链接查看:Popup

二、水平垂直居中方案

垂直居中的方案网上谷歌一下就能找到很多种,主流的方案有:

absolute(fixed) + 负边距

absolute(fixed) + transform

flex

table + vertical-align

首先说一下我们选择的是第二种:absolute(fixed) + transform,它是以上方案中最简单最方便的方案,代码实现量也很少。实现代码如下:

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

但是 transform 会导致一个巨大的,这个的具体细节会在下面的章节中详细讲到。

说完了我们选择的方案,再来说说为啥不选择其他的方案呢?

absolute(fixed) + 负边距

只能适合定高的场景,果断抛弃。如果要实现不定高度就要通过JS来计算了,增加了实现的复杂度。

flex

flex 布局一是在某些老版本的安卓浏览器上还不是很兼容,还有就是需要包裹一个父级才能水平垂直居中。

table + vertical-middle

CSS2 时代用这个方案来实现垂直居中是比较常见的方案,不足的地方就是代码实现量相对较大。

三、可恶的滚动穿透

开发过移动端UI组件的都知道,在移动端有个可恶的滚动穿透问题。这个问题可以描述为:在弹窗上滑动会导致下层的页面跟着滚动。

网上谷歌一下滚动穿透关键字其实可以发现很多种解决方案,每个方案也各有优缺点,但我们选择的解决方案是团队的一姐一篇移动端体验优化的博文中得到的启示(博文地址:花式提升移动端交互体验 | TinySymphony)。

具体的思路是:当容器可以滑动时,若已经在顶部,禁止下滑;若在底部,禁止上滑;容器无法滚动时,禁止上下滑。实现的方式就是在 document 上监听 touchstarttouchmove 事件,如滑动时,祖先元素并没有可滑动元素,直接阻止冒泡即可;否则判断手指滑动的方向,若向下滑动,判断是否滑动到了滑动元素的底部,若已经到达底部,阻止冒泡,向上滑动也类似。具体的代码实现可以看下面的代码:

const _ = require("src/util")
export default function (option) {
  const scrollSelector = option.scroll || ".scroller"
  const pos = {
    x: 0,
    y: 0
  }

  function stopEvent (e) {
    e.preventDefault()
    e.stopPropagation()
  }

  function recordPosition (e) {
    pos.x = e.touches[0].clientX
    pos.y = e.touches[0].clientY
  }

  function watchTouchMove (e) {
    const target = e.target
    const parents = _.parents(target, scrollSelector)
    let el = null
    if (target.classList.contains(scrollSelector)) el = target
    else if (parents.length) el = parents[0]
    else return stopEvent(e)
    const dx = e.touches[0].clientX - pos.x
    const dy = e.touches[0].clientY - pos.y
    const direction = dy > 0 ? "10" : "01"
    const scrollTop = el.scrollTop
    const scrollHeight = el.scrollHeight
    const offsetHeight = el.offsetHeight
    const isVertical = Math.abs(dx) < Math.abs(dy)
    let status = "11"
    if (scrollTop === 0) {
      status = offsetHeight >= scrollHeight ? "00" : "01"
    } else if (scrollTop + offsetHeight >= scrollHeight) {
      status = "10"
    }
    if (status !== "11" && isVertical && !(parseInt(status, 2) & parseInt(direction, 2))) return stopEvent(e)
  }
  document.addEventListener("touchstart", recordPosition, false)
  document.addEventListener("touchmove", watchTouchMove, false)
}
四、position: fixed 失效

在前端工程师的世界观里,position: fixed 一直是相对浏览器视口来定位的。有一天,你在固定定位元素的父元素上应用了 transform 属性,当你刷新浏览器想看看最新的页面效果时,你竟然发现固定定位的元素竟然相对于父元素来定位了。是不是感觉人生观都崩塌了。

这个问题,目前只在Chrome浏览器/FireFox浏览器下有。也有人给 Chrome 提bug:Fixed-position element uses transformed ancestor as the container,但至今尚未解决。

例如下面的代码:



垂直居中方案 position: fixed + transform 的选择导致了 Modal 组件使用上的一个坑。当我们在 Modal 组件里面嵌套了一个 Modal 时,内层的Modal 就是相对外层的 Modal 来定位,而不是浏览器的 viewport。这也限制了我们 Modal 的使用场景,如果你想实现嵌套的 Modal,就要选择其他的垂直居中方案了,有舍必有得嘛。

关于 position: fixed 失效的更多细节可以参考以下几篇博文:

Eric’s Archived Thoughts: Un-fixing Fixed Elements with CSS Transforms

CSS3 transform对普通元素的N多渲染影响

总结

开发组件库不易,开发移动端组件库更不易。移动端组件库相对PC端会有更多的奇葩的坑。当遇到坑,肯定是要选择跨越它,而不是逃避它,因此也才有了我们这篇文章,后续我们也还会有一些介绍 Vant 组件库开发过程中遇到的坑,或者一些优化相关的文章,敬请期待。

如果觉得这篇文章讲的还不够,完整源代码实现请移步Github:popup。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92268.html

相关文章

  • VueCli+Node+mongodb打造个人博客(含前台展示及后台管理系统)(下)

    摘要:前文上篇中篇地址现在只剩下把东西展示出来了页面这里有四种页面其实是四个组件文章,杂谈,收藏,具体的文章或杂谈前三个虽然布局一样,但功能有细微差别,同时考虑到以后可能要针对不同种类做不同的布局方法我还是定义了三个组件以及具体的那个可以看到它们 前文 上篇:https://segmentfault.com/a/11...中篇:https://segmentfault.com/a/11......

    YacaToy 评论0 收藏0
  • VueCli+Node+mongodb打造个人博客(含前台展示及后台管理系统)(下)

    摘要:前文上篇中篇地址现在只剩下把东西展示出来了页面这里有四种页面其实是四个组件文章,杂谈,收藏,具体的文章或杂谈前三个虽然布局一样,但功能有细微差别,同时考虑到以后可能要针对不同种类做不同的布局方法我还是定义了三个组件以及具体的那个可以看到它们 前文 上篇:https://segmentfault.com/a/11...中篇:https://segmentfault.com/a/11......

    Atom 评论0 收藏0
  • VueCli+Node+mongodb打造个人博客(含前台展示及后台管理系统)(下)

    摘要:前文上篇中篇地址现在只剩下把东西展示出来了页面这里有四种页面其实是四个组件文章,杂谈,收藏,具体的文章或杂谈前三个虽然布局一样,但功能有细微差别,同时考虑到以后可能要针对不同种类做不同的布局方法我还是定义了三个组件以及具体的那个可以看到它们 前文 上篇:https://segmentfault.com/a/11...中篇:https://segmentfault.com/a/11......

    EdwardUp 评论0 收藏0
  • VueCli+Node+mongodb打造个人博客(含前台展示及后台管理系统)(下)

    摘要:前文上篇中篇地址现在只剩下把东西展示出来了页面这里有四种页面其实是四个组件文章,杂谈,收藏,具体的文章或杂谈前三个虽然布局一样,但功能有细微差别,同时考虑到以后可能要针对不同种类做不同的布局方法我还是定义了三个组件以及具体的那个可以看到它们 前文 上篇:https://segmentfault.com/a/11...中篇:https://segmentfault.com/a/11......

    liangdas 评论0 收藏0
  • 开发一个简单的 Vue 弹窗组件

    摘要:取消确认现在弹窗组件的结构已经搭建出来了。另外还有两个方法,分别是点击取消和确认的回调函数,它们的作用是触发对应的事件。到这里,一个简单的弹窗组件已经完成了样式后面再说。一个弹窗组件的拖拽一般通过三个事件来控制,分别是。 更多文章 一个弹窗组件通常包含两个部分,分别是遮罩层和内容层。 遮罩层是背景层,一般是半透明或不透明的黑色。 内容层是放我们要展示的内容的容器。 ...

    hiyang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<