背景

需求预沟通

  • 产品:我们需要做一个 IM,和微信一样
  • 我:打扰了,走错片场了,你们继续,,,,,,,
  • ....
  • 我:这个需求虽然不合理,但是我还是接了。。。。。。

输入框

来来来,我们来想一下我们一天中使用频率最高的 App——微信,英文名是 WeChat,它的聊天界面长啥样子,输入框在最底部(不知道你什么反应,我反正当场就打开了 weibo,facebook, twitter 等等等的 mobile 端,就没有一个是把输入框放在最底部的,好的,我当场就对产品发动了强烈的反抗,当然依旧没有成功),输入框默认单行展示,随着用户输入,输入框中的内容自动换行,最多显示 3 行,超出部分可以滚动,要求可以输入超链接,输入框左边有个插入表情按钮,右边是发送按钮,输入框下面有个 checkbox,,,,,

产品:这个需求很简单,怎么实现我不管,反正下个月要上线。

技术方案

虽然我很绝望,但是需求还是要做,,,,

输入框技术选型

来,我们来具体看需求,“输入框内容要自动换行”,排除 input;“输入框中可以输入超链接”,排除 textarea;还有啥,只有 contentEditable 为 true 的 div 了。

插入表情

我们知道,如果想做各端样式统一的表情的话,必然是用图片展示表情,这样的话,我们的成本就会增加很多,所幸在这一点上说服了产品,可以直接使用 emoji,那这里我们只需要找到一个比较全面的 emoji 字符列表就好了。

实现

输入框实现

这些能难住 CSS 高手我吗?不能够吧,直接上代码

  • placeholder 的实现
  • 自动换行,最多 3 行

emoji 实现

这里和设计同学确认了一下,表情列表的展示逻辑,一行展示 8 个效果最好,列表的高度最好与键盘高度一致

checkbox 实现

这个没什么好说的,CSS 背景图,js 控制选中态

发送按钮

按钮分为可以 active 与 disabled 两种状态

问题

做完了吗,嗯,是的,我用 chrome 的模拟器没啥问题了,那我们赶紧真机看一下效果吧,,,迫不及待搓手中,,,,然鹅,,,,,我是不是很菜?这不是真的吧?也许,睡一觉就好了吧!

键盘高度获取方案

  • Android:键盘弹起时,会触发 resize 事件,页面的高度会被挤压,也就说输入框只要一直固定在页面底部就可以了。

    监听 window 的 resize 事件,记得区分横竖屏的情况,计算 resize 事件触发前后 window.innerHeight 的变化,差值即为键盘高度。

  • iOS:键盘弹起前后,不会触发 resize 事件,页面的高度不变,同时会把页面上 fixed 的元素变为 absolute 处理(万恶之源)

    这里没有成熟的前端获取键盘高度的方法,所以依赖了客户端的接口。这里有降级方案,输入框在顶部就可以了。

键盘弹起时,禁止页面滚动,只允许输入框内容的滚动

如果你知道将外层容器改成 fixed,让其无法滚动的方法,其实你已经很棒了,但是我们前面有提到过,iOS 键盘弹起时,会把 fixed 布局变为 absolute 布局,显然这种方法是不行的;只有监听 touchmove 事件了,关键代码如下:

重新聚焦 input,光标在所有文字的最前面

这个是 contentEditable 属性的浏览器支持的 bug,解决方法其实也很简单,就是输入框聚焦后,手动的调整光标的位置,关键代码如下

iOS 下,输入内容后再删除至空,不会显示 placeholder

经过排查发现,经过用户输入和删除等操作后,光标后会多出一个 br 标签,这里监听一下 KeyUp 事件做一下处理就好,关键代码如下

部分安卓手机下,将键盘收起后,输入框不会失去焦点

键盘收起后,也会触发 resize 事件,这个时候手动触发 blur 就可以

输入内容安全问题

将输入框元素的 innerHTML,设置为一个新创建的 div 的内容,递归遍历所有子节点,将块级元素变为内容加上 BR 标签,A 标签只取 href 属性,重新生成 A 标签,保证最后得到的内容是文本、BR 标签与 A 标签的组合。

输入内容发送成功后的显示问题

如果把用户输入的 HTML 字符串直接放到 react 组件的 child 中,出于安全考虑,react 会把 HTML 标签作为字符串显示,这个时候需要用到 react 的 dangerouslySetInnerHTML 属性。

切换 checkbox 时,input 会失去焦点

第一反应就是,我明明在 checkbox 的 click 事件回调中,preventDefault 了啊,怎么会呢!如果你了解过 Passive event listeners,你就会知道原因了,曾经一度绝望,react 不支持事件回调中传 passive 参数,翻了一下 react 的 issue,发现解决方案是有的,绑定 touchend 事件是可以成功阻止 input 失焦的。

插入表情时,每一次都是插在 input 最后,不能记住上次光标的位置

这里我们需要维护一下用户的光标位置,在插入表情时,直接插在上一次光标位置后,关键代码如下

总结

经过本次填坑,对移动端的输入框又有了更加深入的了解,遇到问题不要慌,先来翻一翻 AlloyTeam 的博客,AlloyTeam 长期招优秀前端工程师,欢迎入坑。

原创文章转载请注明:

转载自AlloyTeam:http://www.alloyteam.com/2019/07/13847/

  1. ting 2019 年 8 月 13 日

    希望贵平台能继续更新深度好博文,感觉前端娱乐圈自 16 年开始全网前端博文数量开始下降,可能是大家开始沉淀下来,但我们还是需要不少优质好文来慰藉下,不然上班划水都不知道消磨时间

发表评论