![](/img/trans.png)
[英]Stop cursor from jumping to end of input field in javascript replace
[英]How to stop cursor from jumping to the end of input
我有一個受控的 React 輸入組件,我正在格式化輸入,如 onChange 代碼中所示。
<input type="TEL" id="applicantCellPhone" onChange={this.formatPhone} name="applicant.cellPhone" value={this.state["applicant.cellPhone"]}/>
然后我的formatPhone function 是這樣的
formatPhone(changeEvent) {
let val = changeEvent.target.value;
let r = /(\D+)/g,
first3 = "",
next3 = "",
last4 = "";
val = val.replace(r, "");
if (val.length > 0) {
first3 = val.substr(0, 3);
next3 = val.substr(3, 3);
last4 = val.substr(6, 4);
if (val.length > 6) {
this.setState({ [changeEvent.target.name]: first3 + "-" + next3 + "-" + last4 });
} else if (val.length > 3) {
this.setState({ [changeEvent.target.name]: first3 + "-" + next3 });
} else if (val.length < 4) {
this.setState({ [changeEvent.target.name]: first3 });
}
} else this.setState({ [changeEvent.target.name]: val });
}
當我嘗試在中間某處刪除/添加一個數字然后 cursor 立即移動到字符串的末尾時,我開始面臨這個問題。
我在Sophie的解決方案中看到了一個解決方案,但我認為這不適用於這里,因為 setState 無論如何都會導致渲染。 我還嘗試通過 setSelectionRange(start, end) 操作插入符號 position,但這也無濟於事。 我認為導致渲染的 setState 使組件將編輯的值視為最終值並導致 cursor 移動到最后。
誰能幫我弄清楚如何解決這個問題?
僅onChange
是不夠的。
案例 1:如果target.value === 123|456
那么你不知道'-'
是如何被刪除的。 使用<del>
或使用<backspace>
。 所以你不知道結果值和插入符號 position 應該是12|4-56
還是123-|56
。
但是,如果您要保存以前的插入符號 position 和值怎么辦? 假設在之前的onChange
你有
123-|456
現在你有了
123|456
這顯然意味着用戶按下了<backspace>
。 但是來了...
案例2:用戶可以用鼠標改變cursor position。
onKeyDown
進行救援:
function App() {
const [value, setValue] = React.useState("")
// to distinguish <del> from <backspace>
const [key, setKey] = React.useState(undefined)
function formatPhone(event) {
const element = event.target
let caret = element.selectionStart
let value = element.value.split("")
// sorry for magical numbers
// update value and caret around delimiters
if( (caret === 4 || caret === 8) && key !== "Delete" && key !== "Backspace" ) {
caret++
} else if( (caret === 3 || caret === 7) && key === "Backspace" ) {
value.splice(caret-1,1)
caret--
} else if( (caret === 3 || caret === 7) && key === "Delete" ) {
value.splice(caret,1);
}
// update caret for non-digits
if( key.length === 1 && /[^0-9]/.test(key) ) caret--
value = value.join("")
// remove everithing except digits
.replace(/[^0-9]+/g, "")
// limit input to 10 digits
.replace(/(.{10}).*$/,"$1")
// insert "-" between groups of digits
.replace(/^(.?.?.?)(.?.?.?)(.?.?.?.?)$/, "$1-$2-$3")
// remove exescive "-" at the end
.replace(/-*$/,"")
setValue(value);
// "setTimeout" to update caret after setValue
window.requestAnimationFrame(() => {
element.setSelectionRange(caret,caret)
})
}
return (
<form autocomplete="off">
<label for="Phone">Phone: </label>
<input id="Phone" onChange={formatPhone} onKeyDown={event => setKey(event.key)} name="Phone" value={value}/>
</form>
)
}
您可能還對該任務的某些庫感興趣。 例如https://github.com/nosir/cleave.js但它移動插入符號的方式可能不符合您的口味。 無論如何,它可能不是唯一的圖書館。
恐怕如果您放棄對 React 的控制,state 的更改將不可避免地丟棄插入符號 position,因此唯一的解決方案是自己處理。
鑒於您的字符串操作並不是那么簡單,因此保留“當前位置”最重要的是......
為了嘗試更好地解決問題,我使用 react hooks 構建了一個解決方案,您可以在其中更好地查看發生了哪些 state 更改
function App() {
const [state, setState] = React.useState({});
const inputRef = React.useRef(null);
const [selectionStart, setSelectionStart] = React.useState(0);
function formatPhone(changeEvent) {
let r = /(\D+)/g, first3 = "", next3 = "", last4 = "";
let old = changeEvent.target.value;
let val = changeEvent.target.value.replace(r, "");
if (val.length > 0) {
first3 = val.substr(0, 3);
next3 = val.substr(3, 3);
last4 = val.substr(6, 4);
if (val.length > 6) {
val = first3 + "-" + next3 + "-" + last4;
} else if (val.length > 3) {
val = first3 + "-" + next3;
} else if (val.length < 4) {
val = first3;
}
}
setState({ [changeEvent.target.name]: val });
let ss = 0;
while (ss<val.length) {
if (old.charAt(ss)!==val.charAt(ss)) {
if (val.charAt(ss)==='-') {
ss+=2;
}
break;
}
ss+=1;
}
setSelectionStart(ss);
}
React.useEffect(function () {
const cp = selectionStart;
inputRef.current.setSelectionRange(cp, cp);
});
return (
<form autocomplete="off">
<label for="cellPhone">Cell Phone: </label>
<input id="cellPhone" ref={inputRef} onChange={formatPhone} name="cellPhone" value={state.cellPhone}/>
</form>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
我希望它有幫助
您嘗試的解決方案應該有效。
請注意 - 在反應中,state 是異步更新的。 要在 state 更新完成后立即執行您需要執行的操作,請使用setState
的第二個參數。
根據文檔
setState() 的第二個參數是一個可選的回調 function,它將在 setState 完成並重新渲染組件后執行。
所以只需編寫一個內聯 function 來執行setSelectionRange
並將其作為第二個參數傳遞給setState
像這樣
...
this.setState({
[changeEvent.target.name]: first3 + "-" + next3 + "-" + last4
},
() => changeEvent.target.setSelectionRange(caretStart, caretEnd)
);
...
代碼的工作副本在這里:
https://codesandbox.io/s/input-cursor-issue-4b7yg?file=/src/App.js
By saving cursor position in the beginning of the handler and restoring it after new state rendered, cursor position will always be in correct position.
但是,因為添加-
會改變 cursor position,所以需要考慮它對初始 position 的影響
import React, { useRef, useState, useLayoutEffect } from "react";
export default function App() {
const [state, setState] = useState({ phone: "" });
const cursorPos = useRef(null);
const inputRef = useRef(null);
const keyIsDelete = useRef(false);
const handleChange = e => {
cursorPos.current = e.target.selectionStart;
let val = e.target.value;
cursorPos.current -= (
val.slice(0, cursorPos.current).match(/-/g) || []
).length;
let r = /(\D+)/g,
first3 = "",
next3 = "",
last4 = "";
val = val.replace(r, "");
let newValue;
if (val.length > 0) {
first3 = val.substr(0, 3);
next3 = val.substr(3, 3);
last4 = val.substr(6, 4);
if (val.length > 6) {
newValue = first3 + "-" + next3 + "-" + last4;
} else if (val.length > 3) {
newValue = first3 + "-" + next3;
} else if (val.length < 4) {
newValue = first3;
}
} else newValue = val;
setState({ phone: newValue });
for (let i = 0; i < cursorPos.current; ++i) {
if (newValue[i] === "-") {
++cursorPos.current;
}
}
if (newValue[cursorPos.current] === "-" && keyIsDelete.current) {
cursorPos.current++;
}
};
const handleKeyDown = e => {
const allowedKeys = [
"Delete",
"ArrowLeft",
"ArrowRight",
"Backspace",
"Home",
"End",
"Enter",
"Tab"
];
if (e.key === "Delete") {
keyIsDelete.current = true;
} else {
keyIsDelete.current = false;
}
if ("0123456789".includes(e.key) || allowedKeys.includes(e.key)) {
} else {
e.preventDefault();
}
};
useLayoutEffect(() => {
if (inputRef.current) {
inputRef.current.selectionStart = cursorPos.current;
inputRef.current.selectionEnd = cursorPos.current;
}
});
return (
<div className="App">
<input
ref={inputRef}
type="text"
value={state.phone}
placeholder="phone"
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
</div>
);
}
在上面的代碼中,這些部分將保存 position:
cursorPos.current = e.target.selectionStart;
let val = e.target.value;
cursorPos.current -= (
val.slice(0, cursorPos.current).match(/-/g) || []
).length;
這些將恢復它:
for (let i = 0; i < cursorPos.current; ++i) {
if (newValue[i] === "-") {
++cursorPos.current;
}
}
還有一個微妙的地方,通過使用useState({phone:""})
我們確保輸入會重新渲染,因為它總是設置一個新的 object。
CodeSandbox 示例是https://codesandbox.io/s/tel-formating-m1cg2?file=/src/App.js
您可以在 formatPhone 函數中簡單地添加以下行--- --> if (.(event.keyCode == 8 || event.keyCode == 37 || event.keyCode == 39))--->
將此 if 條件添加到以 formatPhone function 編寫的整個代碼中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.