Firefox showing caret in wrong position?
Seems that adding this css rule to "container" solves the problem in most cases:
white-space: pre-wrap;
contentEditable cursor position/style in FireFox
I waited and used the content editable only in Firefox 4 and higher. I filed this and a few other bugs which the Mozilla team has fixed for FF 4.
Why is the caret invisible in a contenteditable with position:relative?
Don't know the exact cause of the issue but you can fix it in multiple ways.
My previous explanation (maybe wrong) :
This isn't a bug, you put the
background color
on a<span>
tag (inposition:relative
) inside a contenteditable div element and so the
span is on top of the contenteditable div.
I still think is related to the z-index
since we can see on the image below the red background on top of the Chrome focus border:
Remove position:relative
Removing position:relative
of the <span>
fix the issue:
.no-bug {
background-color: red;
}
<div contenteditable>
This has caret
<span class="no-bug">This has caret !</span>
this has caret
</div>
Firefox contenteditable cursor issue
Here's a fiddle that you might like: http://jsfiddle.net/e5gwc1gq/2/
I believe that the problem is because the :before
psuedo element doesn't allow access to the underlying parent-div.
The reason I think so is because if you were to replace before
with after
, the problem persists, but is flipped, ie, now if you click in the left, it won't work properly.
Note: You might want to add word-wrap: break-word;
css rule for .content-editable
, since firefox doesn't add this by default, but chrome does.
How to set the caret (cursor) position in a contenteditable element (div)?
In most browsers, you need the Range
and Selection
objects. You specify each of the selection boundaries as a node and an offset within that node. For example, to set the caret to the fifth character of the second line of text, you'd do the following:
function setCaret() {
var el = document.getElementById("editable")
var range = document.createRange()
var sel = window.getSelection()
range.setStart(el.childNodes[2], 5)
range.collapse(true)
sel.removeAllRanges()
sel.addRange(range)
}
<div id="editable" contenteditable="true">
text text text<br>text text text<br>text text text<br>
</div>
<button id="button" onclick="setCaret()">focus</button>
setting the caret/cursor position to the end of a contenteditable div
No idea if this will help anyone else out. This does not work cross-browser, but since my issue was specific to FireFox and how it handles range/selection with BR tags, this seemed to solve my issue.
I can basically just set the end of the range before the BR
range.setEndBefore($(el).find('br')[0]);
so in my function I am doing this only for firefox:
function placeCaretAtEnd(el) {
el.focus();
if (window.getSelection){
if (typeof window.getSelection != "undefined"
&& typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
if ($('#browser-version-check').val() == 'firefox') {
range.setEndBefore($(el).find('br')[0]);
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}
}
And here is an updated jsfiddle http://jsfiddle.net/mstefanko/fG5kJ/2/, did not include browser check so this demo will only work correctly in FireFox
Set cursor position on contentEditable div
This is compatible with the standards-based browsers, but will probably fail in IE. I'm providing it as a starting point. IE doesn't support DOM Range.
var editable = document.getElementById('editable'),
selection, range;
// Populates selection and range variables
var captureSelection = function(e) {
// Don't capture selection outside editable region
var isOrContainsAnchor = false,
isOrContainsFocus = false,
sel = window.getSelection(),
parentAnchor = sel.anchorNode,
parentFocus = sel.focusNode;
while(parentAnchor && parentAnchor != document.documentElement) {
if(parentAnchor == editable) {
isOrContainsAnchor = true;
}
parentAnchor = parentAnchor.parentNode;
}
while(parentFocus && parentFocus != document.documentElement) {
if(parentFocus == editable) {
isOrContainsFocus = true;
}
parentFocus = parentFocus.parentNode;
}
if(!isOrContainsAnchor || !isOrContainsFocus) {
return;
}
selection = window.getSelection();
// Get range (standards)
if(selection.getRangeAt !== undefined) {
range = selection.getRangeAt(0);
// Get range (Safari 2)
} else if(
document.createRange &&
selection.anchorNode &&
selection.anchorOffset &&
selection.focusNode &&
selection.focusOffset
) {
range = document.createRange();
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
} else {
// Failure here, not handled by the rest of the script.
// Probably IE or some older browser
}
};
// Recalculate selection while typing
editable.onkeyup = captureSelection;
// Recalculate selection after clicking/drag-selecting
editable.onmousedown = function(e) {
editable.className = editable.className + ' selecting';
};
document.onmouseup = function(e) {
if(editable.className.match(/\sselecting(\s|$)/)) {
editable.className = editable.className.replace(/ selecting(\s|$)/, '');
captureSelection();
}
};
editable.onblur = function(e) {
var cursorStart = document.createElement('span'),
collapsed = !!range.collapsed;
cursorStart.id = 'cursorStart';
cursorStart.appendChild(document.createTextNode('—'));
// Insert beginning cursor marker
range.insertNode(cursorStart);
// Insert end cursor marker if any text is selected
if(!collapsed) {
var cursorEnd = document.createElement('span');
cursorEnd.id = 'cursorEnd';
range.collapse();
range.insertNode(cursorEnd);
}
};
// Add callbacks to afterFocus to be called after cursor is replaced
// if you like, this would be useful for styling buttons and so on
var afterFocus = [];
editable.onfocus = function(e) {
// Slight delay will avoid the initial selection
// (at start or of contents depending on browser) being mistaken
setTimeout(function() {
var cursorStart = document.getElementById('cursorStart'),
cursorEnd = document.getElementById('cursorEnd');
// Don't do anything if user is creating a new selection
if(editable.className.match(/\sselecting(\s|$)/)) {
if(cursorStart) {
cursorStart.parentNode.removeChild(cursorStart);
}
if(cursorEnd) {
cursorEnd.parentNode.removeChild(cursorEnd);
}
} else if(cursorStart) {
captureSelection();
var range = document.createRange();
if(cursorEnd) {
range.setStartAfter(cursorStart);
range.setEndBefore(cursorEnd);
// Delete cursor markers
cursorStart.parentNode.removeChild(cursorStart);
cursorEnd.parentNode.removeChild(cursorEnd);
// Select range
selection.removeAllRanges();
selection.addRange(range);
} else {
range.selectNode(cursorStart);
// Select range
selection.removeAllRanges();
selection.addRange(range);
// Delete cursor marker
document.execCommand('delete', false, null);
}
}
// Call callbacks here
for(var i = 0; i < afterFocus.length; i++) {
afterFocus[i]();
}
afterFocus = [];
// Register selection again
captureSelection();
}, 10);
};
Caret disappears in Firefox when saving its position with Rangy
Apparently rangy's Selection Save and Restore module can't be used to keep track of the current selection while the user interacts with a contenteditable
, like you want.
I digged into it a bit, and the problem is that rangy inserts hidden <span>
s as markers, and updates the selection to be after the marker, instead of keeping it inside the #text
node the user's editing:
<div contenteditable>
#text [This is something I typed <!-- selection is moved from here -->]
<span class="rangySelectionBoundary"/>
<!-- to here -->
</div>
Firefox has trouble displaying the caret in this scenario (I haven't found a bug about this specific issue, but here's a similar one where the caret is not displayed when the selection is between two <span>
s).
Commenting this code out seems to fix the issue with the disappearing caret. It's unclear to me why that code is needed -- it was added before 1.0 in a large commit with its message saying: "Fixes for save/restore problems with control ranges and multiple range selections. Added demos for save/restore and CSS class applier modules." -- so I'm not comfortable to suggest fixing this in rangy (and since it's unmaintained for a few years, I don't have much hope in getting its author's input on this).
So I tried to figure out why you needed this in the first place, to suggest other solutions not involving rangy.saveSelection
(for example, rangy's Text Range module provides getSelection().saveCharacterRanges(containerNode)
that works without modifying the DOM.
It appears that you have a <div contenteditable>
and some "buttons" (<span>
s), clicking on which would insert some HTML at the caret position. The problem you were trying to solve was that when the "buttons" were clicked, the selection moved from the contenteditable
into the button, and you were unable to detect the insert position.
Instead of storing and restoring the selection, you can instead make the buttons user-select: none
- this will keep the caret in the contenteditable.
To test this, I commented out all references to rangy.saveSelection
and rangy.restoreSelection
and changed the this.$refs.divInput.focus();
call in the "button"'s onclick
handler to run only when the contenteditable
wasn't already focused by wrapping it in an if (!this.$refs.divInput.contains(document.activeElement))
. See how this works in this updated fiddle:
https://jsfiddle.net/fjxsgvm2/
Related Topics
How to Remove Space (Margin) Above HTML Header
Apply CSS Style to Child Elements
Ml-Auto Is Not Pushing Navbar Links to the Right
What Is Caret Symbol ^ Used for in CSS When Selecting Elements
How to Use CSS Position Sticky to Keep a Sidebar Visible with Bootstrap 4
Flexbox VS Tables, Why Do We Need Flexbox
Small Margin/Gap at the Top of Document
Google Webfonts Render Choppy in Chrome on Windows
CSS Vertical Scrollbar Padding Left/Right in Ul Possible
Align Two Inline-Blocks Left and Right on Same Line
How to Center Multiple Inline-Block Elements with CSS
Implement a CSS-Only Slideshow/Carousel with Next and Previous Buttons
Why Doesn't Position: Sticky Work in Chrome
Firefox Sets Wrong Caret Position Contenteditable with :Before