emoji-Picker-Element:一种节省内存的Web表情选择器

2020-06-29 14:58:17

表情符号选择器无处不在。似乎每个社交媒体和即时通讯应用都需要有一个可以点击的卡通面孔的小网格。

表情符号本身没有什么问题(它们很有趣!)。它们很受欢迎!他们让交流变得更加生动活泼!)。但目前在网络上使用它们的方式是浪费的。

主要的问题是有很多表情符号:最新版本是1814,Emoji v.13.0。这甚至不包括肤色变体(或肤色组合),而且每个表情符号都有相关的快捷码、标签、ASCII表情符号等。大多数基于网络的表情符号选择器的工作方式是,所有这些数据都存储在内存中。

管理表情数据的流行JavaScript库emojibase目前提供两个英文JSON文件:主JSON文件,大小为854kB;“紧凑”JSON文件,大小为543kB。现在想象一下,对于你打开的每个浏览器选项卡,每个网站都可能加载超过0.5兆字节的数据,仅仅是为了显示一个小小的表情符号网格!

根据互联网档案馆(Internet Archive)的数据,目前网页的中位数约为2MB。希望任何表情符号选择器的数据都是懒惰加载的,但无论哪种方式,在任何合理的性能预算中,50万字节都是一个很大的块。

你可以说,这些网站应该抛弃自定义选择器,只要求人们在他们的操作系统上使用内置的表情符号选择器。问题是很多人不知道这些东西的存在。(有多少Mac用户知道Cmd+Ctrl+Space?有多少Windows用户记住了Win+。?)。有些操作系统甚至没有原生的表情符号选择器(比如试试Ubuntu或普通的Android)。即使忽略了这些问题,网站通常也需要调整表情符号选择器,比如添加自己的自定义表情符号(例如,不和谐、松弛、乳臭未干)。

浏览器供应商不应该提供标准的表情符号选择器元素吗?类似<;input type=";emoji";>;的内容?实际上,我在过去就提出过这一点,但据我所知,没有任何浏览器供应商有兴趣采用它。

此外,标准化表情符号选择器可能需要JavaScript(TC39)和Web(W3C/WHATWG)标准之间的协调,因为理想情况下,除了实际的选择器元素之外,您还需要一个基于JavaScript的API来查询特定于Intl的表情数据(例如,显示自动补全)。浏览器供应商和标准团体之间的协作程度必须相当高,因此似乎不太可能很快实现。

当我第一次写“皮纳弗尔”的时候,我根本不想处理这种情况。我发现定制表情符号选择器的整个想法是荒谬的-只要使用操作系统内置的表情符号就行了!

然而,当我意识到操作系统提供的表情符号选择器是多么不可靠,并考虑到对Mastodon自定义表情符号的需求后,我最终决定使用Mastodon也使用的Reaction组件-emoji-Mart。然而,由于各种原因,我对emoji-Mart越来越失望(尽管我对此做出了贡献),我沉思着如何建立自己的表情市场。

理想的基于网络的表情符号选择器应该是什么样子的?我确定了几个要求:

数据应该存储在IndexedDB中,而不是存储在内存中。Unicode联盟永远不会停止添加表情符号,所以在某个时候,将所有表情符号及其元数据保存在内存中将变得不可持续(或者至少是难以操作)。

它应该是一个自定义元素。Web组件是一种东西;使用表情符号选择器应该像将<;emoji-Picker>;/emoji-Picker>;放到HTML中一样简单。

它应该是轻便的。如果每个网站都要使用自己的表情符号选择器,那么它至少应该有一个较低的JavaScript足迹。

它应该是可以接近的。可访问性不应该是事后才想到的;表情符号选择器应该适合屏幕阅读器用户、键盘用户-每个人。

因此,与我更好的判断相反,我开始了这项艰巨的任务:按照我认为应该建立的方式,构建一个完整的表情符号选择器。今天,它在NPM上可用,您可以在这里试用演示版本。我称它为(有点武断地):表情选择器元素(emoji-Picker-Element)。

在幕后,emoji-Picker-Element将下载表情库数据,对其进行解析,并将其存储在IndexedDB中。(默认情况下,它从jsdelivr获取。)。第二次加载选取器时,它将只执行HEAD请求,并检查ETag以查看是否有任何更改。

这是使用IndexedDB的好处:我们可以避免第二次下载、解析和处理emoji数据,因为它已经在磁盘上了!遵循离线优先的原则,如果数据发生变化,emoji-Picker-Element也会在后台懒惰地更新。

emoji-picer-element在emojibase定义的类别中只显示本地emoji(没有精灵表)。如果操作系统或浏览器不支持任何表情符号,则这些表情符号将被隐藏。

像大多数表情符号选择器一样,你还可以进行文本搜索,设置肤色,并查看常用表情符号的列表。我还添加了对自定义类别的自定义表情符号的支持,这对于想要为实例添加一些活力和个性的Mastodon管理员来说是一个重要的功能。

为了保持较小的包大小和快速的运行时性能,我使用Svelte3来实现选取器。树抖动的Svelte“运行时”与组件捆绑在一起,因此消费者不必关心我使用的是什么框架-它“只是工作”。

另外,我正在使用Shadow DOM,它可以很好地封装样式,同时还使用CSS变量提供了一个整洁的样式API:

(如果您想知道为什么会这样,那是因为CSS变量穿透了阴影DOM。)。

我做得有多好?在我的脑海中,最重要的考虑因素是性能,所以下面是颜文字拾取器元素是如何堆积起来的。

这对我来说是最有趣的一次。我的假设是正确的,即将表情数据存储在IndexedDB中会减少内存占用吗?

如您所见,emoji-Picker-Element占用的内存比仅仅加载“紧凑的”JSON本身要少!这就是呈现组件所需的全部HTML、CSS和JavaScript,而且它已经比表情符号数据本身小了。

请注意,JSON文件的大小(在本例中,完整数据的大小为854kB,压缩数据的大小为543kB)与将其解析为JavaScript对象时的内存使用量不同。这就是为什么实际解析JSON以获得真实的内存使用情况非常重要。

考虑到emoji-picer-element将数据从内存移入IndexedDB,您可能还想知道它在磁盘上占用了多少空间。浏览器中有三种主要的IndexedDB实现(Chrome/Chromium、Firefox/Gecko和Safari/WebKit),因此我编写了一个脚本来计算IndexedDB磁盘使用量。这里是:

(请注意,这些值可能有点不一致-似乎所有浏览器都会随着时间的推移进行一定数量的压缩。这是我看到的最低价。)。

这些数字并不小,但考虑到表情符号数据的大小,以及数据库包含用于文本搜索的索引的事实,这些数字是有意义的。考虑到节省的内存,我发现这是一个合理的折衷方案。

要计算加载时间,在呈现第一组表情符号和收藏夹栏之后,我测量从自定义元素的构造函数的开头到requestPostAnimationFrame(PolyFill)。

在Android 6上运行Chrome的Nexus 5(2013年发布)上,5次迭代的中位数如下:

(这是在本地网络上运行的-我更关注CPU性能而不是网络性能。)。

查看First Load的Chrome跟踪显示,Nexus 5花了大约200ms呈现初始视图(即“正在加载”状态),200ms将数据下载到本地Intranet,400ms进行处理,650ms将数据插入IndexedDB,这大约是IndexedDB交易成本的另一秒,然后250ms呈现最终结果:

在第二次加载时,大多数IndexedDB开销都消失了,我们只需从IDB获取并呈现:

有趣的是,因为它是IndexedDB,所以我们实际上可以将初始数据加载移动到Web Worker,从而释放主线程。在这种情况下,初始负载如下所示:

在这种情况下,总时间约为2.5秒。显然,我们刚刚将成本从一个线程转移到了另一个线程(我们还没有开发出更快的线程),但是我们可以通过这种方式避免jack,这是相当巧妙的!最后,只有大约300ms的工作发生在主线程上。

这指向了一个很好的应用程序级优化:假设表情符号选取器是延迟加载的,应用程序可以主动启动一个工作线程来填充数据库。这将确保当用户第一次打开选取器时表情数据已经准备好。

目前,emoji-Picker元素的捆绑大小为39.66kB缩小,12.3kB缩小+Brotli。这两个对我的口味来说都有点太大了,但它们确实比NPM上的其他表情符号拾取器更好:我能找到的具有类似功能集的最小的是交织表情符号拾取器,缩小了40.7kB。

当然,其他表情符号选择器也不会包含其框架的整个运行时。我将Svelte与该组件捆绑在一起的原因是:1)Svelte的运行时已经相当小,2)它也是摇摇欲坠,只包含您需要的内容,3)我假设大多数人都没有使用Svelte,因为它仍然是一个后起之秀的框架。

也就是说,我确实为那些已经在使用Svelte3的用户准备了一个单独的版本,在这种情况下,包的大小大约要小11kB(缩小)。无论哪种方式,理想情况下,emoji-Picker-Element都应该是懒惰加载的!

这个项目并不是每件事都进展顺利,所以我对未来可以改进的地方有一些想法。

首先,使用本土表情符号说起来容易做起来难。目前还没有操作系统安装Emoji V13,除非您使用的是苹果设备,否则您甚至不太可能安装Emoji V12。(只有19%的Android设备运行的是搭载Emoji v12的Android 10+,而75%的iOS设备运行的是搭载Emoji v12.1的iOS 13.2+。)。或者您的操作系统可能有不完整的表情符号(微软选择对标志使用两个字母的代码是…。充其量也就是奇怪)。如果你在Ubuntu上使用Chrome,你根本就没有原色表情符号,除非你安装一个单独的软件包。

GitLab有一个很棒的帖子,详细描述了支持本地表情符号的所有令人头疼的问题。它涉及到将emoji渲染到画布并检查渲染的颜色,以及处理边缘情况,例如当渲染不正确时可能显示为“双emoji”的连字。(例如,“红头发的人”可能会出现在他们旁边戴着漂浮的红发假发的人。)。简而言之,这里一团糟。

然而,我这个项目的目标是滑到冰球要去的地方,而不是它在哪里。我希望浏览器和操作系统能齐心协力,开始广泛支持原生颜色表情符号,而不会有任何破解或变通的办法。(如果他们不这样做,更多的网站将出现故障,而不仅仅是表情符号选择元素!因此,他们有充分的动机这样做。)。

此外,我不需要像GitLab那样经历那么多困难,因为我并不担心支持天底下的每一个表情符号。(如果我是,我只会使用精灵工作表!)。因此,我目前的策略是进行嗅探测试--检查每个表情符号版本中具有代表性的表情符号,如果一个表情符号不受支持,则隐藏整个表情符号集。这并不完美,但我相信对于大多数用例来说,它已经足够好了。随着本地表情符号支持的改善,它应该会随着时间的推移而改善。

这本身可能是一篇博客文章,但影子DOM是一把双刃剑。一方面,我喜欢它很容易封装CSS,并提供带有CSS变量的样式API。另一方面,我发现任何管理焦点的库-例如,Focus-Visible、11y-Dialog或我自己的箭头键导航-都必须进行调整,以解决阴影DOM。否则,聚焦行为将无法正常工作。

我不会深入细节(请参阅我的Mastodon线程)。长话短说:除了焦点可见之外,这些库都不能使用阴影DOM,这需要一些手动工作。因此,对于11y对话框,我对其进行了分叉,而对于箭头键导航,我必须实现影子DOM支持。有点烦人,而我真正想要的只是样式封装!

但再说一遍:我正试着滑到冰球要去的地方。我希望浏览器最终能解决影子DOM API的问题,使库更容易实现焦点陷阱、自定义焦点热键等。

构建表情选择器元素并不容易。正确呈现原生表情符号需要各种平台和浏览器上支持表情符号的神秘知识,以及如何检测它。添加肤色变体(可能支持也可能不支持,即使支持基本表情符号),很快就会变得非常复杂。

我非常感谢在我之前的人所做的工作:提供表情数据的emojibase,成为这个主题取之不尽的资源的Emojipedia,以及揭开如何检测表情支持的if-emoji和GitLab。

当然,我也要感谢所有现有的表情符号选择器,我的灵感都是从这些表情符号选择器中获得的-不用重新发明轮子真是太好了!大多数挑选表情符号的人都集中在一个惊人相似的设计上,所以我觉得我自己的设计没有必要特别有创意。(尽管我确实认为我的一些CSS动画很不错。)。

我仍然相信,表情符号选择器可能是浏览器应该自己处理的东西,以避免每个加载自己选择器的网页不可避免地膨胀。但就目前而言,至少应该有一个尽可能轻量级和高性能的基于网络的表情符号选择器。表情符号选择元素是我实现这一愿景的最好尝试。

你可以在fedarious和lobste.rs上讨论这篇文章。以下是代码和演示。