์ผ€์ด์Šค ce-0317-chrome-caret-jumps-to-end-dom-manipulation-ko ยท ์‹œ๋‚˜๋ฆฌ์˜ค scenario-chrome-caret-jumps-to-end-dom-manipulation

Caret jumps to end when DOM is manipulated during input in Chrome

OS: Any Any ๊ธฐ๊ธฐ: Desktop or Laptop Any ๋ธŒ๋ผ์šฐ์ €: Chrome Latest ํ‚ค๋ณด๋“œ: US ์ดˆ์•ˆ
chromecaretdom-manipulationnon-editable

Phenomenon

In Chrome, when the DOM is manipulated during user input in a contenteditable element, the caret may unexpectedly jump to the end of the content. This often occurs when programmatically inserting or deleting non-editable elements like <span contenteditable="false"> while the user is typing.

Reproduction example

  1. Create a contenteditable div with some text.
  2. Programmatically insert or delete a non-editable <span contenteditable="false"> element.
  3. Observe that the caret jumps to the end of the contenteditable element.
  4. This disrupts the userโ€™s typing position.

Observed behavior

  • Caret jumps to end: Caret position resets to end of contenteditable element.
  • DOM manipulation trigger: Occurs when DOM is modified during user input.
  • Non-editable elements: More common when working with contenteditable="false" elements.
  • Chrome-specific: This behavior is more prevalent in Chrome.
  • User disruption: Severely disrupts typing and editing experience.

Expected behavior

  • Caret position should be preserved during DOM manipulations.
  • Programmatic changes should not affect userโ€™s cursor position.
  • Editing should continue smoothly even when DOM is modified.

Analysis

Chromeโ€™s contenteditable implementation may reset the selection when DOM structure changes, especially when non-editable elements are involved. The browserโ€™s selection management doesnโ€™t properly track position across structural changes.

Workarounds

  • Preserve caret position before DOM manipulation:
    function saveCaretPosition(element) {
      const selection = window.getSelection();
      if (selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);
        return {
          startContainer: range.startContainer,
          startOffset: range.startOffset,
          endContainer: range.endContainer,
          endOffset: range.endOffset
        };
      }
      return null;
    }
    
    function restoreCaretPosition(element, savedPosition) {
      if (!savedPosition) return;
      const range = document.createRange();
      range.setStart(savedPosition.startContainer, savedPosition.startOffset);
      range.setEnd(savedPosition.endContainer, savedPosition.endOffset);
      const selection = window.getSelection();
      selection.removeAllRanges();
      selection.addRange(range);
    }
  • Insert non-breaking space after non-editable elements to maintain position.
  • Use block-level elements instead of inline elements for contenteditable regions.
  • Avoid DOM manipulation during active input, defer to next frame.

Playground for this case

Use the reported environment as a reference and record what happens in your environment while interacting with the editable area.

Reported environment
OS: Any Any
Device: Desktop or Laptop Any
Browser: Chrome Latest
Keyboard: US
Your environment
Sample HTML:
Event log
Use this log together with the case description when filing or updating an issue.
0 events
Interact with the editable area to see events here.