摘要:具體表現(xiàn)是什么樣的呢圖安卓下不期望的輸入行為可以看到,在安卓手機下每次格式化發(fā)生的時候,本來應(yīng)該一直在最后的光標會錯格一位,導致連續(xù)輸入出現(xiàn)問題。
今天要來說的是有關(guān)于有數(shù)值格式化的場景中,React input 光標的一些異常的表現(xiàn)和對應(yīng)的處理辦法。故事要從一個 issue 說起,有用戶反映在使用 NumberField 組件輸入時安卓下會出現(xiàn)光標位置異常,導致連續(xù)輸入會達不到期望的結(jié)果。具體表現(xiàn)是什么樣的呢?
圖1 安卓下不期望的輸入行為
可以看到,在安卓手機下每次格式化發(fā)生的時候,本來應(yīng)該一直在最后的光標會錯格一位,導致連續(xù)輸入出現(xiàn)問題。而這個問題在 PC Chrome 和 iOS 上都沒有出現(xiàn),于是可以判定是一個兼容性問題。但這個兼容性問題是如何產(chǎn)生的呢?
分析一下格式化的話的過程,如上面的情況,輸入 18758 時,因為要做針對卡號的格式化,所以會將原有的值轉(zhuǎn)變?yōu)?"1875 8",從字符串長度上來看,從 5 位變成了 6 位,那么如果此時光標位置沒有在值變化時跳到最后一位,則會停留在空格處,看起來就好像錯格了一位,連續(xù)輸入時就會有問題。
單從輸入框的光標變化行為來看,這好像也不算是一種異常的變化,只是不響應(yīng)值的變化跳到尾部而已。但引申出來的問題是為什么在 iOS 和 PC Chrome 下又會跳動到尾部呢。
圖2: 相同的代碼在 PC Chrome 下表現(xiàn)與安卓不同。
于是去網(wǎng)上搜索,輾轉(zhuǎn)在 React 的 github 中找到這樣一個 issue, Cursor jumps to end of controlled input。在這里 React 的主要維護者之一的 @sophiebits(spicyj) 給出了一個比較確切的答案。
圖3 sophiebits 關(guān)于 React controlled input value 變化時光標行為的解釋
原來因為 value 的變化具有非常大的不確定性,因此 React 無法使用一種可靠且通用的邏輯去保存光標的位置在一個合適的位置,因此 React 在受控模式下的重新渲染都會時光標移動到最后的位置。這個至少解釋了PC Chrome 和 iOS 下光標跳動到結(jié)尾的原因,但安卓下為什么沒有表現(xiàn)出同樣的行為到目前位置我還沒有找到合理的解釋。
那有沒有辦法使安卓上的表現(xiàn)和 iOS 中一致呢?又是一陣翻閱和嘗試,最后發(fā)現(xiàn)如果將重新渲染的過程和 input 的 onChange 置于前后兩個 tick 中就可以使安卓中 input 的表現(xiàn)和其他平臺上表現(xiàn)一致,即表現(xiàn)為光標在重新渲染時跳到最后,示意代碼如下。
import React from "React"; class Demo extends React.Component { constructor(props) { super(props); this.state = { value: "xxx", }; } handleChange(e) { const value = e.target.value; // 通過 setTimeout 異步 // 使 re-render 和 onChange 處于兩個 tick 中 setTimeout(() => { this.setState({ value, }); }); } render() { return ( { this.handleChange(e); }} /> ); } }
這樣終于使得表現(xiàn)的行為在安卓和 iOS 上表現(xiàn)一致,并且正常輸入的情況下表現(xiàn)得比較符合期望了,然而等等,這樣就可以了嗎?從之前的 React issue 中得出的結(jié)論可以看出,無論是如何的修改都會跳至 input 的結(jié)尾,這樣如果是從中間修改的話會變成什么樣?
圖4:中間編輯時又會出現(xiàn)問題
從上面的圖里可以看出,因為 React 無論何種修改都會將光標置尾,如果從中間進行修改,那么表現(xiàn)地又會很不符合用戶預(yù)期,沒有辦法做到連續(xù)輸入。這回倒是兩端行為保持一致,都是不期望的狀態(tài)。。
但是都不正常也有好處,不需要根據(jù)平臺去寫一些 ifelse,可以統(tǒng)一地去做處理。從上面的討論中我們可以知道 React 沒有保存光標的位置是因為沒有一個通用并且可靠的算法去支撐這一行為。這是因為 input 的變化可能是增加空格做格式化,也可能是過濾過些字符,也可能是觸發(fā)某些條件直接變成了其他字符等各種無法預(yù)測的變化行為。但是細化到數(shù)字格式化這一單一場景時,光標位置的保存邏輯就變得簡單和清晰的多了。
在用戶輸入的過程中,只存在兩種情況,在結(jié)尾中追加和在中間修改。在結(jié)尾追加的 case 中,例如 18758^ 時,由于一直是在向后追加的狀態(tài),我們只要一直保持光標在最后即可(即默認狀態(tài) 1875 8^ ),在中間編輯的 case 下,光標并不處于結(jié)尾,如 187^5 8,此時如果在 7 后面追加了一個 8,那么理想的圖標應(yīng)該維持在 8 之后(即 1878^ 58),此時就應(yīng)該保存光標的位置在上次 format 之前的狀態(tài)。
邏輯清楚了,接下來就是如何實現(xiàn)的問題了。那么如何探測和修改光標位置呢?這就涉及了 input 中選區(qū)相關(guān)的屬性,我們知道我們可以通過一些方式(如鼠標拖拽和長按屏幕等)在 input 中完成對一段話的選區(qū),因此光標的位置其實是由選區(qū)的開始點(selectionStart)和結(jié)束點(selectionEnd)決定的。那么其實我們就可以通過讀取,儲存和設(shè)置這兩個屬性來達到我們想要實現(xiàn)的目的,實例代碼如下。
class Demo extends React.Component { ... componentDidUpdate(prevProps) { const { value } = prevProps; const { inputSelection } = this; if (inputSelection) { // 在 didUpdate 時根據(jù)情況恢復光標的位置 // 如果光標的位置小于值的長度,那么可以判定屬于中間編輯的情況 // 此時恢復光標的位置 if (inputSelection.start < this.formatValue(value).length) { const input = this.input; input.selectionStart = inputSelection.start; input.selectionEnd = inputSelection.end; this.inputSelection = null; } } } handleChange(e) { // 在 onChange 時記錄光標的位置 if (this.input) { this.inputSelection = { start: this.input.selectionStart, end: this.input.selectionEnd, }; } ... } render() { return ( { this.input = c; }} value={this.state.value} onChange={(e) => { this.handleChange(e); }} /> ); } }
至此,我們終于在追加和中間編輯的情況下都實現(xiàn)了我們想要的效果。這是一個比較小的技術(shù)點,但是由于里面涉及了一些 React 內(nèi)部的處理邏輯及平臺差異性問題,排查和解決起來并不是那么容易,希望可以給有類似問題的同學在處理時有所啟發(fā)。
文中涉及的各端及瀏覽器信息Android
Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; CLT-AL00 Build/HUAWEICLT-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/11.9.4.974 UWS/2.13.1.48 Mobile Safari/537.36
iOS
Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15F79
PC Chrome
Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36文中涉及的組件庫
SaltUI: https://github.com/salt-ui/sa...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98573.html
摘要:踩的大坑前言最近有個需求要將全平臺的交易密碼由原來的位復雜密碼改為位純數(shù)字交易密碼,涉及到非常多的業(yè)務(wù)場景,但修改起來也無非兩種設(shè)置交易密碼,使用交易密碼設(shè)置交易密碼普通長條輸入框彈起數(shù)字鍵盤支持明暗文切換查看使用交易密碼顯示六個格子彈起 input ios 踩的大坑 前言:最近有個需求要將全平臺的交易密碼由原來的 6-16位 復雜密碼改為6位純數(shù)字交易密碼,涉及到非常多的業(yè)務(wù)場景,但...
摘要:背景最近我們微信讀書將寫想法換成了基于的富文本編輯器,遇到了不少問題,這里我將簡單的介紹一下我們在開發(fā)過程中踩到的坑。 背景 最近我們微信讀書將寫想法換成了基于webview的富文本編輯器,遇到了不少問題,這里我將簡單的介紹一下我們在開發(fā)過程中踩到的坑。 實現(xiàn)富文本編輯器有兩個基本思路: 基于native實現(xiàn):比如coretext或者textkit 基于uiwebview實現(xiàn) 第一...
摘要:我們來從設(shè)計思想上,和官方團隊的回應(yīng)上,了解一下否決理由。此外,還有一個方法新的接口設(shè)計支持接收一個回調(diào)函數(shù),當其子組件掛載時,這個回調(diào)函數(shù)就會相應(yīng)觸發(fā)。 從 setState 那個眾所周知的小秘密說起... 在 React 組件中,調(diào)用 this.setState() 是最基本的場景。這個方法描述了 state 的變化、觸發(fā)了組件 re-rendering。但是,也許看似平常的 th...
摘要:我們來從設(shè)計思想上,和官方團隊的回應(yīng)上,了解一下否決理由。此外,還有一個方法新的接口設(shè)計支持接收一個回調(diào)函數(shù),當其子組件掛載時,這個回調(diào)函數(shù)就會相應(yīng)觸發(fā)。 從 setState 那個眾所周知的小秘密說起... 在 React 組件中,調(diào)用 this.setState() 是最基本的場景。這個方法描述了 state 的變化、觸發(fā)了組件 re-rendering。但是,也許看似平常的 th...
摘要:由于我們的富文本輸入框比較簡單,所以只需要處理兩類數(shù)據(jù)即可,其一是普通的文本類型數(shù)據(jù),包括表情其二則是圖片類型數(shù)據(jù)。 最近折騰 Websocket,打算開發(fā)一個聊天室應(yīng)用練練手。在應(yīng)用開發(fā)的過程中發(fā)現(xiàn)可以插入 emoji ,粘貼圖片的富文本輸入框其實蘊含著許多有趣的知識,于是便打算記錄下來和大家分享。 倉庫地址:chat-input-box預(yù)覽地址:https://codepen.io...
閱讀 3058·2023-04-26 00:49
閱讀 3725·2021-09-29 09:45
閱讀 987·2019-08-29 18:47
閱讀 2746·2019-08-29 18:37
閱讀 2728·2019-08-29 16:37
閱讀 3295·2019-08-29 13:24
閱讀 1778·2019-08-27 10:56
閱讀 2349·2019-08-26 11:42