React:编辑contentEditable div时如何保持插入位置?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React:编辑contentEditable div时如何保持插入位置?相关的知识,希望对你有一定的参考价值。
目前
- 我有一个react组件,当用户点击contentEditable
newValue
时存储<div>
,并在用户输入时更新newValue
。注意:有两个主要原因可以解释为什么我这样设置这种行为:(1)我不想发送要保存在每个按键上的数据,(2)我打算使用这个div的变体检查每个输入以验证输入是否是数字。 - 当
newValue
失去焦点时,发送<div>
被保存,然后重置道具的状态。
问题
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高度行为(ref CSS: Remove scroll bar and replace with variable height for textarea in a table <td>) - 我已经能够找到许多相关的答案,但没有一个具体反应,例如Maintain cursor position in contenteditable div。我假设因为反应在每次中风后重新加载组件,这个问题正在发生。
- 我以前没有ChangeHandler onInput,这工作正常,但我无法记录每个按键并验证该字符是否为数字。
答案
ContentEditable是一个棘手的问题,尤其是反应,因为你必须考虑很多不同类型的行为。我建议你看看Facebook的DraftJS。
他们使用contentEditable并阻止了所有默认行为,并构建了一个很好的框架来使标签可编辑,他们将它用于富文本编辑器,但是您可以使用相同的框架,而不需要所有的钟声和哨声来控制可编辑的内容。
https://draftjs.org/docs/getting-started
另一答案
我能够在https://stackoverflow.com/a/13950376/1730260解决这个问题
主要变化:
- 添加具有2个函数的新组件
EditCaretPositioning.js
:(1)saveSelection以保存插入位置,以及(2)restoreSelection以恢复插入位置。 - 将插入符号位置保存在
Input
组件的状态中 - 在每次Change事件后调用
saveSelection()
- 设置状态后
restoreSelection()
作为回调 - 将
id
添加到<div>
所以可以参考restoreSelection()
函数
EditCaretPositioning.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;
以上是关于React:编辑contentEditable div时如何保持插入位置?的主要内容,如果未能解决你的问题,请参考以下文章
如何在具有 contentEditable 的元素上启用“可拖动”?