可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Scenario
I have a contenteditable <div>
area, and within this area I may have some <span contenteditable="false"></span>
containing some text. The idea is, these span elements will represent styled text that can not be edited, but may be deleted from the <div contenteditable="true"></div>
area by pressing the backspace key.
Issue
The cursor placement is the big issue here. if you delete one of these <span>
elements, the cursor jumps to the end of the <div>
. More interesting, if you type some text while the cursor is "at the end," the text placement is just fine... Then, if you delete the newly typed text, the cursor jumps back!
I have prepared a fiddle which will demonstrate this. I need this to work only in Chrome, and other browsers are either of non-concern for now or have workarounds in place. (Also note the prepared Fiddle is crafted to demonstrate this in Chrome only).
Fiddle
Fiddle Instruction: Chrome Version 39.0.2171.95 m (64-bit) reproduced in 32-bit as well
- Click into
<div>
area
- Type "123"
- Backspace "3" Backspace "2" Backspace "1"
Related Details
Researching this extensively, I have come across various SO question that are similar, but borrowing the associated solutions has not proved to be the silver bullet I am after. I have also found issues for the Chrome project which seem to target (perhaps not in the exact manner) the issue described above, and can be viewed below.
- Issue 384357: Caret position inside contenteditable region with uneditable nodes
- Issue 385003: Insert caret style is wrong when reaching the end of a line in a contenteditable element
- Issue 71598: Caret in wrong position after non-editable element at the end of contentEditable
The closest SO solution I have found can be here. The idea in this solution is to place ‌
characters after the <span>
elements, but if I want to now delete my <span>
, I instead delete the ‌
... forcing my cursor to jump to the end, offering a weird UI experience by not deleting my <span>
on my "initial delete key stroke."
Question
Has anyone experienced this issue and found a work around? I welcome any possible solution, even as JS hacky as they come. I've come to learn that leveraging contenteditable
comes with a laundry list of struggle, but this seems to be the last remaining difficulty I currently have.
回答1:
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:
- Add
display: inline-block
to div.main
- add a
<br/>
at the end of div.main
JSFiddle Link
回答2:
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.
回答3:
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;
}
回答4:
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();
}
})
});
回答5:
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>'