skip to Main Content

I’m trying to find a solution for this problem for a long time now, so I’m going to ask here now.

Start with a normal contenteditable div element, like this one:

<div contenteditable>Edit this text</div>

If you click on the text, you will be able to edit it with the cursor at the position you clicked at.

Now, I don’t want the text to be editable on the first click, and I don’t want to be able to interact with the text at all while the element is not focused. I could achieve this by disabling the div, turning of contenteditable, with CSS or an overlaying element.

I now want the element to be editable again on doubleclick with the exact same behaviour you would expect from clicking the element normally. Let’s use JavaScript and a EventListener for onmousedown or ondblclick for that.

Example 1

<div id="wrapper">
    <div id="overlay"></div>
    <div id="editable" contenteditable>Edit this text</div>
</div>
overlay.ondblclick = () => {
    overlay.style.display = 'none';
    editable.focus();
};

// use onblur to restore the overlay 

This works, BUT the cursor will be positioned at the start of the element. If you don’t use focus(), you have to click a third time to focus the element.

Example 2

<div id="editable" contenteditable="false">Edit this text</div>
editable.ondblclick = () => {
    editable.setAttribute('contenteditable', true);
};

// use onblur to set contenteditable to false again
// additional CSS will prevent being able to interact with the text if not focused

This works, BUT you will select the word at which you are clicking at instead of putting the text cursor there.

This is for a private project, accessability, compatibility, etc don’t matter.

2

Answers


  1. Chosen as BEST ANSWER

    After some more hours of research, I came to this solution:

    const editable = document.getElementById('editable');
    
    editable.ondblclick = (event) => {
      if (document.activeElement === event.target) return;
    
      editable.setAttribute('contenteditable', true);
      selectRange(getMouseEventCaretRange(event));
    }
    
    function getMouseEventCaretRange(event) {
        if (event.rangeOffset !== undefined) {
            // The new fancy Firefox-only way that doesn't even have documentation
            let range = document.createRange();
    
            range.setStart(event.rangeParent, event.rangeOffset);
            range.collapse(true);
    
            return range;
        } else if (document.caretPositionFromPoint) {
            // The 'official' way (only used by Firefox) that doesn't even position the cursor correctly
            // VSC keeps telling me the method doesn't exist
            let pos = document.caretPositionFromPoint(event.clientX, event.clientY);
            let range = document.createRange();
    
            range.setStart(pos.offsetNode, pos.offsetX);
            range.collapse(true);
    
            return range;
        } else if (document.caretRangeFromPoint) {
            // The 'deprecated WebKit-proprietary fallback method' (used by every mf browser out there)
            return document.caretRangeFromPoint(event.clientX, event.clientY);
        }
    }
    
    function selectRange(range) {
        const sel = window.getSelection();
    
        sel.removeAllRanges();
        sel.addRange(range);
    }
    * {
      font-family: monospace;
    }
    
    #editable {
      font-size: 1.5rem;
     }
    
    #editable[contenteditable="false"] {
      user-select: none;
      cursor: pointer;
    }
    
    #editable[contenteditable="false"]::selection {
      background-color: transparent;
     }
    <div id="editable" contenteditable="false" onblur="this.setAttribute('contenteditable', false);">Double click to edit this text</div>
    <ul>
      <li>Caret is placed where clicked, not at the start</li>
      <li>Double clicked word is not highlighted</li>
      <li>Text can not be interacted with if the element isn't focused</li>
    </ul>

    It is long, complicated, but it works. The API for this is completely messed up in my opinion. I hope this can help someone in the future.


  2. There’s no need for anything complicated, just prevent the double-click action when adding contenteditable while making sure it’s in focus.

    document.addEventListener('mousedown', (event) => {
      if (event.detail > 1) { // double-clicked
        const e = event.target;
        if (e.id === "content" && !e.contenteditable) {
          e.contentEditable = true;
          event.preventDefault();
          e.focus();
        }
      }
    }, false);
    <div id="content">Edit this text</div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search