簡體   English   中英

React:編輯 contentEditable div 時如何保持插入符號位置?

[英]React: How to maintain caret position when editing contentEditable div?

目前

  1. 我有一個反應組件,當用戶點擊 contentEditable <div>時存儲一個newValue ,並在用戶輸入時更新newValue 注意:我以這種方式設置此行為有兩個主要原因:(1)我不想發送要在每次擊鍵時保存的數據,以及(2)我計划使用此 div 的變體,其中檢查每個輸入以驗證輸入是否為數字。
  2. <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;

筆記

  1. 我以前用一個沒有這個問題的<textarea>來做這個,但切換到<div>以更好地控制自動調整 div 高度行為(參考CSS:刪除滾動條並用可變高度替換文本區域中的表 <td> )
  2. 我已經能夠找到許多相關的答案,但沒有一個是特定於反應的,例如在 contenteditable div 中保持光標位置 我假設因為每次筆畫后 react 都在重新加載組件,所以發生了這個問題。
  3. 我以前沒有 ChangeHandler onInput,這工作正常,但我無法記錄每個按鍵並驗證字符是否為數字。

我能夠在https://stackoverflow.com/a/13950376/1730260 中使用以下解決方案來解決此問題

主要變化:

  1. 添加具有 2 個功能的新組件EditCaretPositioning.js :(1) saveSelection 以保存插入符位置,以及 (2) restoreSelection 以恢復插入符位置。
  2. Input組件的狀態下保存插入符號位置
  3. 在每個Change事件之后調用saveSelection()
  4. restoreSelection()作為設置狀態后的回調
  5. 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 並阻止了所有默認行為,並構建了一個很好的框架來使標簽可編輯,他們將其用於富文本編輯器,但您可以使用相同的框架而無需所有花里胡哨的東西來控制可編輯的內容。

https://draftjs.org/docs/getting-started

我也遇到過這個問題,同時嘗試在<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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM