[英]React: How to maintain caret position when editing contentEditable div?
目前
<div>
時存儲一個newValue
,並在用戶輸入時更新newValue
。 注意:我以這種方式設置此行為有兩個主要原因:(1)我不想發送要在每次擊鍵時保存的數據,以及(2)我計划使用此 div 的變體,其中檢查每個輸入以驗證輸入是否為數字。<div>
失去焦點時發送newValue
保存,然后重置 prop 的狀態。問題
onChangeHandler
正在將可編輯 div 中插入符號的位置移動到左側。 這導致擊鍵123456
顯示為654321
代碼:
class Input extends Component {
constructor(props) {
super(props);
this.state = {
//newValue input by user
newValue : undefined
}
}
//handler during key press / input
onChangeHandler = event => {
let targetValue = event.currentTarget.textContent;
this.setState({"newValue": targetValue})
}
//handler when user opens input form
onBlurHandler = event => {
//some code that sends the "newValue" to be saved, and resets state
}
render() {
//determine which value to show in the div
let showValue;
//if there is a new value being input by user, show this value
if (this.state.newValue !== undefined) {
showValue = this.state.newValue;
} else {
//if prop has no value e.g. null or undefined, use "" placeholder
if (this.props.value) {
showValue = this.props.value;
} else {
showValue = "";
}
}
return (
<table>
<tbody>
<td>
<div
contentEditable="true"
suppressContentEditableWarning="true"
onInput={this.onChangeHandler.bind(this)}
onBlur={this.onBlurHandler}
>{showValue}
</div>
</td>
</tbody>
</table>
)
}
}
export default Input;
筆記
<textarea>
來做這個,但切換到<div>
以更好地控制自動調整 div 高度行為(參考CSS:刪除滾動條並用可變高度替換文本區域中的表 <td> )我能夠在https://stackoverflow.com/a/13950376/1730260 中使用以下解決方案來解決此問題
主要變化:
EditCaretPositioning.js
:(1) saveSelection 以保存插入符位置,以及 (2) restoreSelection 以恢復插入符位置。Input
組件的狀態下保存插入符號位置saveSelection()
restoreSelection()
作為設置狀態后的回調id
添加到<div>
以便可以在restoreSelection()
函數中引用編輯CaretPositioning.js
const EditCaretPositioning = {}
export default EditCaretPositioning;
if (window.getSelection && document.createRange) {
//saves caret position(s)
EditCaretPositioning.saveSelection = function(containerEl) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
}
};
//restores caret position(s)
EditCaretPositioning.restoreSelection = function(containerEl, savedSel) {
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl], node, foundStart = false, stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType === 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
} else if (document.selection && document.body.createTextRange) {
//saves caret position(s)
EditCaretPositioning.saveSelection = function(containerEl) {
var selectedTextRange = document.selection.createRange();
var preSelectionTextRange = document.body.createTextRange();
preSelectionTextRange.moveToElementText(containerEl);
preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
var start = preSelectionTextRange.text.length;
return {
start: start,
end: start + selectedTextRange.text.length
}
};
//restores caret position(s)
EditCaretPositioning.restoreSelection = function(containerEl, savedSel) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(containerEl);
textRange.collapse(true);
textRange.moveEnd("character", savedSel.end);
textRange.moveStart("character", savedSel.start);
textRange.select();
};
}
更新了 contentEditable div 組件:
import CaretPositioning from 'EditCaretPositioning'
class Input extends Component {
constructor(props) {
super(props);
this.state = {
//newValue input by user
newValue : undefined,
//stores positions(s) of caret to handle reload after onChange end
caretPosition : {
start : 0,
end : 0
}
}
}
//handler during key press / input
onChangeHandler = event => {
let targetValue = event.currentTarget.textContent;
//save caret position(s), so can restore when component reloads
let savedCaretPosition = CaretPositioning.saveSelection(event.currentTarget);
this.setState({
"newValue": targetValue,
"caretPosition" : savedCaretPosition
}, () => {
//restore caret position(s)
CaretPositioning.restoreSelection(document.getElementById("editable"), this.state.caretPosition);
})
}
//handler when user opens input form
onBlurHandler = event => {
//some code that sends the "newValue" to be saved, and resets state
}
render() {
//determine which value to show in the div
let showValue;
//if there is a new value being input by user, show this value
if (this.state.newValue !== undefined) {
showValue = this.state.newValue;
} else {
//if prop has no value e.g. null or undefined, use "" placeholder
if (this.props.value) {
showValue = this.props.value;
} else {
showValue = "";
}
}
return (
<table>
<tbody>
<td>
<div
id="editable"
contentEditable="true"
suppressContentEditableWarning="true"
onInput={this.onChangeHandler.bind(this)}
onBlur={this.onBlurHandler}
>{showValue}
</div>
</td>
</tbody>
</table>
)
}
}
export default Input;
ContentEditable 是一個棘手的問題,尤其是對於 react,因為您必須考慮許多不同類型的行為。 我建議你看看 Facebook 的 DraftJS。
他們采用了 contentEditable 並阻止了所有默認行為,並構建了一個很好的框架來使標簽可編輯,他們將其用於富文本編輯器,但您可以使用相同的框架而無需所有花里胡哨的東西來控制可編輯的內容。
我也遇到過這個問題,同時嘗試在<td>
上編輯內容。 編輯時光標轉到開始,我嘗試了多種方法但沒有任何幫助!
后來,我最終使用了這個“contenteditable”的 React 包。
https://github.com/lovasoa/react-contenteditable
以前,我使用的代碼是這樣的
<td
onInput={(e) =>this.handleInput(e, user, "name", index)}
contentEditable={user.isEditable}
className={
user.isEditable ? "border border-success" : ""
}
>
{user.name}
</td>
包含包后,我使用以下代碼對其進行了更改。 現在它可以正常工作了。
<td className={user.isEditable ? "border border-success" : ""}>
<ContentEditable
html={user.name}
disabled={!user.isEditable}
onChange={(e) =>
this.handleInput(e, user, "name", index)
}
style={{ "text-decoration": "none" }}
/>
</td>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.