React: How to maintain caret position when editing contentEditable div?
当前
问题
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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} </td> </tbody> </table> ) } } export default Input; |
注意事项
我能够在https://stackoverflow.com/a/13950376/1730260
主要更改:
EditCaretPositioning.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | 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组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | 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} </td> </tbody> </table> ) } } export default Input; |
ContentEditable是一个棘手的问题,尤其是在做出反应时,因为您必须考虑许多不同种类的行为。我可以建议您看看Facebook的DraftJS。
他们采用contentEditable并阻止了所有默认行为,并建立了一个很好的框架来使标签可编辑,他们将其用于富文本编辑器,但是您可以使用相同的框架而不必费吹灰之力即可控制内容可编辑。
https://draftjs.org/docs/getting-started
在尝试在
后来,我最终使用了这个"内容可编辑的" React包。
https://github.com/lovasoa/react-contenteditable
以前,我使用的代码是这样的
1 2 3 4 5 6 7 8 9 | <td onInput={(e) =>this.handleInput(e, user,"name", index)} contentEditable={user.isEditable} className={ user.isEditable ?"border border-success" :"" } > {user.name} </td> |
在包含该软件包之后,我使用以下代码对其进行了更改。现在它可以正常工作了。
1 2 3 4 5 6 7 8 9 10 | <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> |