Why Is My Contenteditable caret Jumping to the End in Chrome?

后端 未结 6 1533
攒了一身酷
攒了一身酷 2020-12-10 01:59

Scenario

I have a contenteditable

area, and within this area I may have some
相关标签:
6条回答
  • 2020-12-10 02:12

    The problem is that your contenteditable element is a div, which by default is display: block. This is what causes your caret position problem. This can be fixed by making the outermost div non-editable and adding a new editable div that will be treated as inline-block.

    The new HTML would have a new div just inside the outer one (and the corresponding closing tag at the end):

    <div id="main" class="main"><div id="editable" contenteditable="true">...
    

    And add this to your CSS:

    div#editable {
        display: inline-block;
    }
    

    For the sake of seeing the caret better when it is between span elements, I've also added margin: 2px to the rule for div.main span in the CSS but this is not necessary to prevent the caret jumping issue reported in the question.

    Here is a fiddle.

    As you've started discovering, contenteditable is handled inconsistently across browsers. A few years back, I started working on a project (in-browser XML editor) where I thought contenteditable would make everything easier. Then as I developed the application, I soon found myself taking over the functions that I thought contenteditable would give me for free. Today, the only thing contenteditable give me is that it turns on keyboard events on elements I want to edit. Everything else, including caret movement and caret display, is managed by custom code.

    0 讨论(0)
  • 2020-12-10 02:14

    Just add a new line char (\n) before closing the contenteditable tag. For example in JavaScript: var html = '<div id="main" contenteditable="true">' + content + "\n" + '</div>'

    0 讨论(0)
  • 2020-12-10 02:17

    I have found a hacky way to solve the initial problem of the cursor misbehaving at the end of the <div> with some JQuery. I am basically inserting an empty <i> for the cursor to "latch" on to at the end of the <div> contents. This tag works for me because there is no UI difference to the end user when backspacing these from normal text due to overriding font-style: normal (see fiddle), and I also needed something different than <span> to insert

    I am not particularly thrilled with this workaround, especially choosing an <i> just because, but I have not come across a better alternative, and I still welcome better solutions- if any exist!. I plan to keep working at this to hopefully figure out the arrow keys as well, but luckily I am only bothered by this glitch in the Fiddle and not my code.


    Fiddle (Chrome only)

    <div id="main" contenteditable="true">
        <span contenteditable="false">item</span>
        <span contenteditable="false">item</span>
        <span contenteditable="false">item</span>
    </div>
    

    function hackCursor() {    
        return $('#main :last-child').get(0).tagName === 'SPAN' ? 
            $('#main span:last-child').after('<i style="font-style: normal"></i>') : 
            false;
    }
    
    $(function(){
    
        hackCursor();
    
        $('#main').bind({
            'keyup': function(e) {
                hackCursor();
            }
        })
    });
    
    0 讨论(0)
  • 2020-12-10 02:23

    The problem is evident in the below example and can be remedied by changing the contentetiable wrapper to display:block, but that causes a very annoying Chrome bug - <div><br></div> will be added when ENTER key is pressed

    [contenteditable="true"]{
      border: 1px dotted red;
      box-sizing: border-box;
      width: 100%;
      padding: 5px;
      outline: none;
      display: inline-block; /* prevents Chrome from adding <div><br></div> when pressing ENTER key. highly undesirable. */
    }
    
    [contenteditable="false"]{
      background: lightgreen;
      padding: 3px;
      border-radius: 3px;
    }
    <div contenteditable="true">
        <span contenteditable="false">item</span>
        <span contenteditable="false">item</span>
        <span contenteditable="false">item</span>
    </div>

    Lets try to fix it:

    I can see the problem with the combination of width being wider than the content and also of display: inline-block.

    I also do not want to use javascript if there's any possibility for a CSS fix.

    I would very much like to keep display: inline-block to prevent the mentioned above Chrome bug from happening, so it would seem a thing can be done with the width issue, by setting it to auto when the contenteditable gets focused, and wrapping everything with an element that will allow to fake the same width as before using a pseudo-element inside the contenteditable (only when focused)

    .wrapper{
      width: 100%;
      position: relative;
    }
    
    [contenteditable="true"]{
      border: 1px solid red;
      box-sizing: border-box;
      width: 100%;
      padding: 5px;
      outline: none;
      display: inline-block;
    }
    
    [contenteditable="true"]:focus{
      border-color: transparent;
      width: auto;
    }
    
    [contenteditable="true"]:focus::before{
      content: '';
      border: 1px solid red; /* same as "real" border */
      position: absolute;
      top:0; right:0; bottom:0; left:0;
    }
    
    [contenteditable="false"]{
      background: lightgreen;
      padding: 3px;
      border-radius: 3px;
    }
    <div class='wrapper'>
      <div contenteditable="true">
          <span contenteditable="false">item</span>
          <span contenteditable="false">item</span>
          <span contenteditable="false">item</span>
      </div>
    <div>

    The above solution is good for some situations, but not when the lines wrap and on the last like, the last element is a node:

    .wrapper{
      width: 100%;
      position: relative;
    }
    
    [contenteditable="true"]{
      border: 1px solid red;
      box-sizing: border-box;
      width: 100%;
      padding: 5px;
      outline: none;
      display: inline-block;
    }
    
    [contenteditable="true"]:focus{
      border-color: transparent;
      width: auto;
    }
    
    [contenteditable="true"]:focus::before{
      content: '';
      border: 1px solid red; /* same as "real" border */
      position: absolute;
      top:0; right:0; bottom:0; left:0;
    }
    
    [contenteditable="false"]{
      background: lightgreen;
      padding: 3px;
      border-radius: 3px;
    }
    <div class='wrapper'>
      <div contenteditable="true">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
          <span contenteditable="false">item</span>
          <span contenteditable="false">item</span>
          <span contenteditable="false">item</span>
      </div>
    <div>

    Lets just use javascript and place a br element at the end, if one is not there (it cannot be removed by selecting all and deleting the content):

    var elem = document.body.firstElementChild;
    
    if( !elem.lastChild || elem.lastChild.tagName != 'BR' )
        elem.insertAdjacentHTML('beforeend', '<br>')
    [contenteditable="true"]{
      border: 1px solid red;
      box-sizing: border-box;
      width: 100%;
      padding: 5px;
      outline: none;
      display: inline-block;
    }
    
    [contenteditable="false"]{
      background: lightgreen;
      padding: 3px;
      border-radius: 3px;
    }
    <div contenteditable="true">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
          <span contenteditable="false">item</span>
          <span contenteditable="false">item</span>
          <span contenteditable="false">item</span>
    </div>

    0 讨论(0)
  • 2020-12-10 02:31

    I don't know why this happens, but I had the feeling it has something to do with the sizing of the <div>, so I tried playing with the display property. After setting it to inline-block and playing a little with the text I found that the issue is gone after I make some edits to it, specifically adding a new line.

    I saw that, for some reason, the <br/> tag is kept in div.main after I delete my new line, but the appearance of the <div> and the way it responds to arrow keys is the same as if there is no new line in it.

    So I restarted the fiddle with the CSS change and a <br/> tag in div.main and viola!

    So to conclude:

    1. Add display: inline-block to div.main
    2. add a <br/> at the end of div.main

    JSFiddle Link

    0 讨论(0)
  • 2020-12-10 02:31

    So, I've got a slightly cleaner implementation of your approach, which relies on the input event that is triggered when a contenteditable field is updated. This event is not supported by IE, but it looks like contenteditable is pretty wonky in IE anyways.

    It basically injects elements around every element marked as contenteditable="false". It could probably be done on initial load rather than just on the input event, but I figured you might have ways of injecting more span's to the region, so input should account for this.

    Limitations include some weird behavior surrounding the arrow keys, as you've seen yourself. Mostly what I've seen is that the cursor maintains its proper position when you move backward through the spans, but not when you move forward.

    JSFiddle Link

    Javascript

    $('#main').on('input', function() {
        var first = true;
        $(this).contents().each(function(){
            if ($(this).is('[contenteditable=false]')) {
                $("<i class='wrapper'/>").insertAfter(this);
                if (first) {
                    $("<i class='wrapper'/>").insertBefore(this);
                    first = false   
                }
            }
        });
    }).trigger('input');
    

    CSS

    div.main {
        width: 400px;
        height: 250px;
        border: solid 1px black;
    }
    
    div.main span {
        width:40px;
        background-color: red;
        border-radius: 5px;
        color: white;
        cursor: pointer;
    }
    
    i.wrapper {
        font-style: normal;
    }
    
    0 讨论(0)
提交回复
热议问题