填充IOS下输入框的两个坑
前言
IOS的坑向来就不会少,这不,最近在重构组件库的时候,就发现了两个坑,然后也找到解决办法去把坑给填上,这里记录一下,以备后面回忆。
坑一:Input输入框配合系统输入法在实时搜索的应用中会出现错误的行为
该坑的示例代码如下:
特意录制了以下ios手机的效果:
从视频我们看到在我们不断的从键盘中输入一直会触发onChange事件,导致Toast一直在变化。
想要填坑,需要先了解一下:compositionstart和compositionend事件
compositionstart事件:
compositionstart 事件触发于一段文字的输入之前(类似于 keydown 事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词)
compositionend事件:
compositionend事件触发于一段文字的输入完成或者取消()
如果不使用上述两个事件,那么每次输入的时候,都会触发onChange
事件,从而出现了上面gif图片的那种错误的行为。
现在我们使用这两个事件来修复这个问题:
给input标签加入下面的监听事件:(react版本)
this._isCompositing = false
changeHandler = (event: any) => {
event.persist()
const {
onChange,
} = this.props
let value = event.target.value
this.setState({ value }, () => {
// composition 的事件触发顺序一般如下:
// compositionStart -> input * n -> compositionEnd
// 但是在 firefox 中变成这样子:
// compositionStart -> input * n -> compositionEnd -> input
// 这里我们在 compositionEnd 之后统一触发一次
// 此处留个坑,在 firefox 下会连续触发两次onChange, 不过可以用去抖来处理,配合Input组件的debounce属性
// 如果需要在汉字或者语音等连续输入的时候不触发onChange
// 这时候如果还在连续输入的话,不作任何处理
if (this._isCompositing) {
return
}
if (debounce) {
this._debounceChangeHandler(event)
} else if (onChange) {
onChange(event)
}
})
}
compositionStartHandler = (event: any) => {
this._isCompositing = true
const { onCompositionStart } = this.props
if (onCompositionStart) {
onCompositionStart(event)
}
}
compositionEndHandler = (event: any) => {
const { onCompositionEnd } = this.props
this._isCompositing = false
this.changeHandler(event)
if (onCompositionEnd) {
onCompositionEnd(event)
}
}
render() {
return (
<input
ref={n => this._input = n}
onChange={this.changeHandler}
onCompositionStart={this.compositionStartHandler}
onCompositionEnd={this.compositionEndHandler}
/>
)
}
效果如下:
从视频可以看到使用了composition事件之后,只有等我们输入完整的中文点击确定后才会触发onChange事件。
坑二:Input输入框focus之后,点击输入框外部的任何区域都没法blur掉
这个行为在安卓是没问题的,但是牛逼的ios就是这么设计的,无奈的我们就只能去给这种设计做一个workaround的解决办法。因为safari不会主动blur,所以就需要我们去主动blur。
解决代码如下:(react版本, 非完整,仅供参考)
this._input = null
componentDidMount() {
this._isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
if (this._isIOS) {
document.addEventListener('touchend', this.documentClickHandler)
}
}
documentClickHandler = (e: any) => {
if (this._input) {
const activeElement = document.activeElement
if (activeElement.tagName === 'INPUT' && this._input === activeElement && e.target !== this._input) {
activeElement.blur()
}
}
}
render() {
return (
<input
ref={n => this._input = n}
/>)
}
上述的解决办法用到了document.activeElement
,该元素的定义如下:
activeElement
在Document
和ShadownRoot
接口中是只读的属性,它返回那些在DOM
或者Shadow
树中被聚焦的节点。大部分时候如果输入框有文本输入的时候activeElement
会返回一个<input>
或者<textarea>
节点,如果是的话我们可以使用selectionStart
和selectionEnd
属性获取更多的详情。另外一些情况可能返回一个<select>
元素(在menu中)或者是button
、checkbox
、radio
类型的input元素。
如果没有选中任何节点,那么返回的是body
或者null
另外提到的ShadowRoot
是Shadow DOM
的根节点,Shadwon DOM
是一颗DOM
树,一般附着在正常页面DOM
节点中,正常页面DOM附着的节点叫做shadow host
,其层次结构是:
在shadow DOM
中,所有的标签和CSS样式都是在shadown host
节点内部生效,也就是说定义在Shadow Root
的CSS样式不会影响到它的父文档,定义在Shadow Root
之外的CSS样式不会影响到主页面。
举个例子:
从示例图上可以看到video标签内部其实实现了一套自己的shadow dom
。
打开查看shadow dom
的配置如下:
参考:
公众号关注一波~
网站源码:linxiaowu66 · 豆米的博客
Follow:linxiaowu66 · Github
关于评论和留言
如果对本文 填充IOS下输入框的两个坑 的内容有疑问,请在下面的评论系统中留言,谢谢。