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

二、水平垂直居中方案

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

  1. absolute(fixed) + 负边距
  2. absolute(fixed) + transform
  3. flex
  4. 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,但至今尚未解决。

例如下面的代码:

```<style>
body {
padding: 50px;
}
.demo {
background: #ccc;
height: 100px;
transform: scale(1);
}
.fixed-box {
position: fixed;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: red;
}
</style>

<div class="demo">

<div class="fixed-box"></div>

</div>


<p>垂直居中方案 <code>position: fixed + transform</code> 的选择导致了 <code>Modal</code> 组件使用上的一个坑。当我们在 <code>Modal</code> 组件里面嵌套了一个 <code>Modal</code> 时,内层的<code>Modal</code> 就是相对外层的 <code>Modal</code> 来定位,而不是浏览器的 viewport。这也限制了我们 <code>Modal</code> 的使用场景,如果你想实现嵌套的 <code>Modal</code>,就要选择其他的垂直居中方案了,有舍必有得嘛。</p>
<p>关于 <code>position: fixed</code> 失效的更多细节可以参考以下几篇博文:</p>
<ul>
<li><a href="http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/" rel="nofollow noreferrer">Eric’s Archived Thoughts: Un-fixing Fixed Elements with CSS Transforms</a></li>
<li><a href="http://www.zhangxinxu.com/wordpress/2015/05/css3-transform-affect/" rel="nofollow noreferrer">CSS3 transform对普通元素的N多渲染影响</a></li>
</ul>
<h2>总结</h2>
<p>开发组件库不易,开发移动端组件库更不易。移动端组件库相对PC端会有更多的奇葩的坑。当遇到坑,肯定是要选择跨越它,而不是逃避它,因此也才有了我们这篇文章,后续我们也还会有一些介绍 <a href="https://www.youzanyun.com/zanui/vant" rel="nofollow noreferrer">Vant</a> 组件库开发过程中遇到的坑,或者一些优化相关的文章,敬请期待。</p>
<p>如果觉得这篇文章讲的还不够,完整源代码实现请移步Github:<a href="https://github.com/youzan/vant/tree/dev/packages/popup" rel="nofollow noreferrer">popup</a>。</p> 原文地址:

移动端 Modal 组件开发杂谈的更多相关文章

  1. React-Native 组件开发方法

    前言 React Native的开发思路是通过组合各种组件来组织整个App,在大部分情况下通过组合View.Image等几个基础的组件,可以非常方便的实现各种复杂的跨平台组件,不过在需要原生功能支持. ...

  2. js组件开发-移动端地区选择控件mobile-select-area

    移动端地区选择控件mobile-select-area 由于之前的[js开源组件开发]js手机联动选择地区仿ios 开源git 很受欢迎,于是我又对其进行了一些优化,包括可选的范围变大了,添加了默认空 ...

  3. [Asp.net core 3.1] 通过一个小组件熟悉Blazor服务端组件开发

    通过一个小组件,熟悉 Blazor 服务端组件开发.github 一.环境搭建 vs2019 16.4, asp.net core 3.1 新建 Blazor 应用,选择 asp.net core 3 ...

  4. KVM&amp;Libvirt基本概念及开发杂谈

    导读 大家好,本次肖力分享的主题是KVM&Libvirt基本概念及开发杂谈,内容有些凌乱松散,主要基于自己早期整理的笔记内容和实践感悟,有些内容难免有失偏颇,望见谅.前面先介绍下需要了解的基本 ...

  5. 移动端报表JS开发示例

    最近对移动端的报表开发颇有研究,细磨精算了好久,虽然到现在还是“囊中羞涩”,但决定还是先抛砖引玉,拿点小干货出来和大家分享. 研究的工具是比较有代表性的FineReport. 1.  移动端哪些地方支 ...

  6. 饿了么基于Vue2.0的通用组件开发之路(分享会记录)

    Element:一套通用组件库的开发之路 Element 是由饿了么UED设计.饿了么大前端开发的一套基于 Vue 2.0 的桌面端组件库.今天我们要分享的就是开发 Element 的一些心得. 官网 ...

  7. 利用bootstrap的modal组件自定义alert,confirm和modal对话框

    由于浏览器提供的alert和confirm框体验不好,而且浏览器没有提供一个标准的以对话框的形式显示自定义HTML的弹框函数,所以很多项目都会自定义对话框组件.本篇文章介绍自己在项目中基于bootst ...

  8. 【2015上半年总结】js开源组件开发系列索引

    js开源组件开发系列一索引 2015.8 by 田想兵 个人网站 从3月份进入新公司以来,时经五个月,我以平均每周1个小组件的速度,已经完成的js组件有22个之余了,已基本上全部用到实际项目中,这些小 ...

  9. Agile.Net 组件式开发平台 - 组件开发示例

    所谓组件式开发平台,它所有的功能模块都是以组件的形式扩展的,下面我来演示一个简单的组件开发例程. Agile.Net开发管理平台项目,已经托管在开源中国码云平台(http://git.oschina. ...

随机推荐

  1. Ubuntu下gcc安装

    在Ubuntu下安装GCC和其他一些Linux系统有点不一样. 方法一: 该方法超简单:sudo apt-get  build-depgcc 就上面这条命令就可以搞定 方法二:sudo apt-get ...

  2. Windows常见软件故障及解决方案

    HM NIS Edit: HM NIS Edit 新建程序向导无效,提示“Please specify the setup lang” 说明 NSIS 安装不对.解决方案有二种: 1. 重装 NSIS ...

  3. SVN Commit报错 svn: E155037: Previous operation has not finished; run 'cleanup' if it was interrupted

    svn commit 文件出错 svn: E155037: Commit failed (details follow): svn: E155037: Previous operation has n ...

  4. C# 运算符 ?、??、?: 、?. 、 各种问号的用法和说明

    1. 可空类型修饰符(?):引用类型可以使用空引用表示一个不存在的值,而值类型通常不能表示为空.例如:string str=null; 是正确的,int i=null; 编译器就会报错.为了使值类型也 ...

  5. Glidar: 一个基于OpenGL的开源实时3D传感器仿真器

    1 简介 这篇博文将介绍一个简单易用的3D传感器仿真器,可以用来模拟Lidars,立体视觉,基于时间飞行技术的ToF相机和微软的Kinect实时产生3D点云数据.Glidar仿真器并不是针对特定的某一 ...

  6. 解决css兼容性

    关于CSS对各个浏览器兼容已经是老生常谈的问题了, 网络上的教程遍地都是.以下内容没有太多新颖, 纯属个人总结, 希望能对初学者有一定的帮助. 一.CSS HACK 以下两种方法几乎能解决现今所有HA ...

  7. 前端那些事之----jQuery

    1.jquery是什么     一个js的框架,可以方便的使用js 2 什么是jQuery对象     是由jQuery封装后的DOM对象     注意:与DOM对象的方法不同,不可以混用,但是可以相 ...

  8. tree编译

    没有tree命令,就需要下载源代码 [root@fyc tree-1.7.0]#cd /opt/src [root@fyc tree-1.7.0]# wget ftp://mama.indstate. ...

  9. py_One

    1.Python 标识符 在 Python 里,标识符由字母.数字.下划线组成. 在 Python 中,所有标识符可以包括英文.数字以及下划线(_),但不能以数字开头. Python 中的标识符是区分 ...

  10. 紫书 习题 8-16 UVa 1618 (中途相遇法)

    暴力n的四次方, 然而可以用中途相遇法的思想, 分左边两个数和右边两个数来判断, 最后合起来判断. 一边是n平方logn, 合起来是n平方logn(枚举n平方, 二分logn) (1)两种比较方式是相 ...