《如何制作一个媲美原生体验的选择器组件-卡晨》演讲视频+文字版

简介: 《如何制作一个媲美原生体验的选择器组件-卡晨》演讲视频+文字版
编者按:本文是蚂蚁集团 Ant Design Mobile 工程师卡晨在 SEE Conf 2022 的演讲内容,包括演讲视频及文字内容,欢迎享用。

hi 大家好,我是卡晨,来自蚂蚁集团。在最近的一年里我主要在做 antd-mobile 5.0 版本的开发,这是一个移动端组件库。今天,我们来聊一聊其中的一个组件——选择器 Picker,它的实现思路和技术选型。

这个小分享大概会包括以下几个部分:API 设计、手势和动画、性能、无障碍。


API 设计

让我们从 API 设计开始,先来定义一下 Picker 组件的属性。Picker 是一个数据录入型的组件,所以它需要 value onConfirm 这两个属性来和外界进行数据的交互。然后它还需要一个 columns 属性,用来配置 Picker 中每一列的选项列表。这里我们省略了一些属性,只保留了这几个最关键的。

需要注意的是,columns 属性是一个二维的数组,第一级对应的是选项列,第二级对应的是每一列中的选项列表。

定义好属性之后,我们来拆解一个这个组件的元素构成。首先呢,我们在这里先放一个 Popup 组件,这是 antd-mobile 中的一个基础布局组件,我们可以直接用它来作为弹层的容器。然后我们往里面填充一些内容:我们在头部放两个操作按钮;在主内容区,根据 columns 属性渲染出对应的选项列表,也就是“拨轮”。拨轮的本质就是一个长长的列表,我们会通过 CSS 的 transform: translate 让它进行上下的偏移,并且把溢出的部分隐藏起来,这样看起来就像是一个轮盘在被转动了。最后,我们来增加一个遮罩层,在拨轮的上半部分和下半部分都盖上一层渐变的白色遮罩,这样用户能就看清当前选中的是哪一项了。

由于拨轮这块的逻辑会比较复杂,所以我们可以把它独立拆一个组件出来。这样我们往下的讨论也可以更聚焦一些。

Wheel 只有三个属性:column 选项列表;value 当前选中的值;onSelect 当用户进行选择时会触发的事件。


⼿势 & 动画

接下来呢,我们为它增加一些交互。在用户对 Picker 进行操作时,有两种可能的方式。第一种是直接点击选项,另外一种是通过手势拖拽。为了更好的描述我们如何去实现手势和动画,这里我们把这个问题抽象成一个最简化的版本。这里有一个小方块,我们可以按住鼠标,把它拖拽起来,当我们松开鼠标的时候,小方块会落回原位。

那这里首先呢是需要实现手势的处理,手势的本质就是鼠标或者是手指在 x 轴、y 轴方向上的移动距离。在 React 的生态社区之中,有很多的手势库,我们选择了 use-gesture。我们可以使用它提供的 useDrag hook 来定义一个手势,当触发了拖拽事件时,我们将滑块的 y 轴位移 translateY 设置为当前手势的移动量。接下来,我们需要在拖拽结束的时候,让小球落回原位,这里就涉及到了动画。相信大家提到动画,首先都会想到 CSS transition,因为它实在是太简单直接了。通过这行 CSS 代码,我们可以让滑块的 transform 属性(也就是它的位移)自动的实现动画过渡。但是我们其实只希望它在回落的时候有这个动画效果,而在拖拽中滑块应该是立即跟随鼠标位置的。所以这里我们在手势中增加一个判断,如果用户还正在进行拖拽,那我们给它增加一个 grabbing 的 class name,从而禁用掉 transition。当手势结束时,我们移除掉 grabbing 这个 class name,并且将它的位置设置回原位。这样就回出现一个回落的动画效果。来看一下效果,似乎还是不错的。但是,我们需要再考虑一种边界情况。如果我们在这个滑块正在下落的过程中,再次点击了它,那么我们应该需要实现的是能够在半空中“接住”这个滑块,并且继续拖动它。而事实上现在的效果是,我们一点这个滑块,它就会重新从原点开始被拖拽。这是因为,对于 CSS 动画来说,我们很难获取到它的动画进行到了哪个状态。如果我们希望得到在鼠标按下时滑块当前的位置,那可能就需要通过一些比较特殊的方法来实现了,比如说 getBoundingRect。所以,CSS 动画在某些场景下的确简单好用,但是当我们需要对动画状态做很多的干预和处理的时候,我们很容易会遇到一些它的短板。所以呢,我们需要换一种方案,也就是基于 js 的动画。让我们以 react-spring 为例,它是一个有点像物理引擎的动画库。

首先,我们使用它提供的 useSpring 创建出一个弹簧动画,动画的初始位置是 y 等于 0。然后我们通过 animated.div 这样一个特殊的元素,把刚刚得到的动画量 y 和 slider 的位移量绑定在一起。接下来我们对手势的处理逻辑进行一下调整,当手势结束时,我们把 y 重置为 0。而如果手势还在拖拽中,我们把 y 立即更新为当前的手势偏移量。然后这里需要特别注意的是,我们在手势的配置中增加了一个 from 参数,它表示的是每一次手势开始的时候,初始的偏移量是多少。而大家可以看到,这里我们给它设置的初始偏移量是 y 这个动画变量的当前值。也就是说,我们会让手势从动画当前进行到位置继续,而不是强制它从原点开始。现在,我们就可以拥有完美的手势体验了。基于同样的思路,我们可以给 Picker 也增加上手势和动画效果,当然会更复杂一些,但是思路是相通的。说完手势和动画,我们再来考虑另一个问题。当我们在使用选择器时,很多个拨轮之间可能是有联动关系的。比如省市区的选择。但是就目前的数据结构而言,Picker 组件的多个拨轮之间是彼此独立的。所以我们需要能够在第一列变化时动态调整第二列的选项列表。OK,那我们来改一下 columns 属性,让它允许用户传入函数动态生成 columns。然后我们基于 Picker 的这个能力,封装出一个 CascadePicker。

所以我们现在有了两个组件:一个通用的 Picker 和一个专用于级联场景的 CascadePicker。他们的属性几乎是完全一样的,但是 Picker 的选项是通过 columns 配置的,columns 是一个二维的数组。而 CascadePicker 的选项是通过 options 配置的。和 columns 不同的是,它是一个树形的结构,更准确的说,是多个树形节点组成的数组。

大家可以看到,这两个组件,有 16 个完全相同的属性,却只有一个不同的属性那为什么不把这两个组件合二为一呢?就像这样:

我们可以让 columns 属性同时支持两种类型的数据格式,然后在 Picker 组件中去做判断,看用户当前的数据是不是级联模式,然后用对应的逻辑去进行处理。听起来也不难实现。但是这种方案也存在着一些弊端:首先,左右两种数据结构看起来只是增加了一个可选的 children 属性,但是数据类型已经不一致了,左边是一个两级的数组,而右边是一个一级的数组。如果不特别注意,非常容易搞混和写错。第二点就是 columns 属性已经不再名副其实了,如果说 columns 是一个数组,代表的是很多列选项,那这个数组其中的每一个元素应该是一列选项才对,就像左边这样,第一列是省,第二列是市。而对于右边这种级联的数据结构,数组中的第一个元素是浙江节点,第二个元素是江苏节点,我们能说浙江是一列、江苏是另一列么?显然是不可以的。如果我们给这种结构的数据起个名字,那也应该叫做 tree、treeNodes 或者是 options。第三点,是这样做会导致很多逻辑被塞在 Picker 组件中,而过度的逻辑耦合可能会对组件的后续改动造成一些限制。假如说我们想给 Picker 组件增加一个新属性,那就要同时考虑级联和非级联的两种情况。而如果我们希望做一些内部实现上的优化,复杂的参数结构也可能会带来额外的成本。我们的原则是,可以选择重复,但不应该去选择错误的抽象。优秀的代码结构和 API 设计不应该以组件数量、属性数量来进行评判。如果我们不去谨慎地处理抽象,仅仅是依靠外在的“形似”来做聚合,那反而可能会导致代码越来越难以维护。基于类似的思路,我们还可以制作日期选择器组件。它最核心的部分也是这个 columns 的生成函数。


性能

我们来试一下:感觉还是很流畅的。不过,如果我们把 CPU 的性能做一些限制,让它变慢 6 倍,就会发现有一点点的卡顿,在 Chrome 的性能面板中,我们可以很明显的观察到有几帧丢失了。

那我们来排查一下,这里我们用到了 React dev tools 的 profiler 工具,我们对刚刚这个操作进行一下录制和分析,会发现 Wheel 组件被频繁的渲染了。如果我们把被渲染的组件高亮一下,就会发现,当滑动第三个拨轮的时候,可以看到五个拨轮全部都被重新渲染了,这显然是不符合预期的。解决方案自然是给 Wheel 加个 memo,但是这里直接套一层 memo 是不会有效果的,我们还需要做一些特殊的处理,包括调整 Wheel 的属性、自定义 props 的对比逻辑。因为今天时间有限,这些我们就不具体展开了,感兴趣的朋友可以在 GitHub 上阅读 antd-mobile 的源代码。OK,经过这波优化之后,我们再来测试一下效果,可以看到转动第三个拨轮,只会触发它自己的重新渲染。React Profiler 中也可以看到组件的渲染有明显的减少。丢帧的情况也不存在了。


⽆障碍

大家可能会觉得,现在这个 Picker 组件总该算是做的差不多了吧?很遗憾,并不是。在我们的身边,有很多的失明人士,根据 2018 年的统计,在中国,每 80 个人中就有一位视力残疾患者。他们没有办法清楚地看到屏幕上显示的文字和按钮,那对于他们来说,想要操作手机,就需要借助于屏幕阅读器。大家可以想象出来,一位盲人,该如何使用这个 Picker 组件吗?我们来直接试验一下:相信这个体验不用多说,正常人都没办法忍受,那,又该怎么优化呢?作为开发者,我们大多数人应该都对无障碍这件事情挺头大的,特别是对于 Picker 这种看起来交互就很复杂的组件。无障碍能力本身并不复杂,通过屏幕阅读器,我们能做的操作就是非常简单的上一项、下一项、点击一下当前项,这本身再简单不过了。但是我们的网页是在一个二维平面上呈现的,而屏幕阅读器却只能把它识别为一个一维的线性的信息结构,这之间的差异,才是导致无障碍能力实现困难的根源。我们尝试了很多办法,最终,选择了去为屏幕阅读器单独实现一套交互。

首先,我们让下面的这层拨轮被屏幕阅读器忽略掉,然后我们再往上面叠加一层内容,包括一段文字,表示当前这一列选择了哪个选项,以及两个操作按钮,用来选择上一项和选择下一项。最后,我们把上面这一层内容设置为完全透明,让我们来看一下实际效果。说了这么多,我们已经差不多要结束了,但在最后,我想替整个 antd-mobile 的社区,以及所有 antd-mobile v5 的使用者感谢参与过 v5 版本建设的同学,从 21 年 7 月推送到 GitHub 开始,到年底为止,在这短短半年的时间里,我们收到了来自 50 位社区同学的 325 个 PR , 460 个 commit 和 416 个 issue。不论是参与代码的编写,还是反馈问题或者是参与讨论,这些都对 antd-mobile 的建设都有着非常非常重要的意义,感谢大家对 antd-mobile 的支持。人们常说,“一个人可以走的更快,但一群人可以走的更远”,我想或许这也是对开源精神的最佳诠释吧。如果大家对 antd-mobile 中的 Picker 组件感兴趣的话,欢迎在 GitHub 上翻阅我们的代码仓库。谢谢大家。


相关文章
|
4月前
|
Dart 搜索推荐 API
打造个性化天气应用:从零开始的Flutter之旅
【9月更文挑战第3天】探索Flutter的强大功能,我们将一步步构建一个动态的天气应用。通过这个实践项目,你将学习到如何从无到有地设计用户界面、处理数据流和集成第三方API。本指南适合所有水平的开发者,无论你是Flutter新手还是寻求提高的资深开发者,都能在这里找到价值。让我们开始吧,创造属于你的天气小助手!
|
5月前
|
JavaScript UED 前端开发
JSF 富文本编辑器横空出世,如魔法神器开启震撼富文本输入之旅!
【8月更文挑战第31天】在现代Web应用中,用户常需输入带样式、颜色及图片等功能的富文本。为此,JSF可集成如CKEditor等富文本编辑器,提供强大输入体验。首先选择合适编辑器并下载引入库文件,使用`<textarea>`与JavaScript实例化编辑器。后台通过`value`属性获取内容。此外,还需配置编辑器选项、处理特殊字符和进行充分测试以确保稳定性和安全性,提升用户体验。
49 0
|
移动开发 算法 HTML5
提升网页阅读体验的三款优秀大纲插件(2023)
阅读长篇文章或复杂网页时,清晰的大纲结构可以帮助我们更好地理解和组织信息。在这篇博客中,我将向大家推荐三款优秀的网页大纲插件,帮助您提升阅读效率和体验。
提升网页阅读体验的三款优秀大纲插件(2023)
|
编解码 算法 前端开发
《移动端的真实感渲染-慎思》演讲视频 + 文字版
《移动端的真实感渲染-慎思》演讲视频 + 文字版
173 0
|
存储 前端开发 算法
1024程序节|完全开源、编程原生体验、轻量化,不来试试吗
1024程序节|完全开源、编程原生体验、轻量化,不来试试吗
199 0
1024程序节|完全开源、编程原生体验、轻量化,不来试试吗
|
编解码 移动开发 数据可视化
自研开源项目正式动工,顺便分享 10个酷炫后台模板
自研开源项目正式动工,顺便分享 10个酷炫后台模板
337 0
自研开源项目正式动工,顺便分享 10个酷炫后台模板
|
编解码 移动开发 测试技术
|
视频直播
短视频系统源码——“潮流”标签之下蕴藏哪些实用功能
随着某音某手的普及,短视频似乎已经融入了我们的生活之中,成为了生活里的一部分。短视频不仅能给我们提供不同的乐趣,还能够作为电商带货的一种新渠道。在各方面因素的助力下,短视频系统源码的发展之快,一度被作为年轻人的“潮流”,而后越来越多不同年龄的人也融入到乐短视频APP之中,那么“潮流”的标签之下都蕴藏了哪些实用的功能。
短视频系统源码——“潮流”标签之下蕴藏哪些实用功能
|
缓存 开发工具 数据安全/隐私保护
直播平台源代码的基础功能和首屏优化,小白必看点
对于任何一个程序软件来说,程序代码都是至关重要的,所以在搭建直播平台的时候,直播平台源代码显得尤为重要。
直播平台源代码的基础功能和首屏优化,小白必看点
|
数据采集 算法 测试技术

热门文章

最新文章