I've got a contentEditable
div in an app I'm working on. I'd like the line that the cursor is currently on to be vertically centered in the window.
When the user scrolls up or down the cursor should always stay centered, with the contentEditable
div moving up or down to adjust.
How do I go about doing this?
I've gotten some of the way there with this
Demo: http://jsfiddle.net/n1dyezmg/
$(function () {
// fire our function on keyup
$(window).bind('keyup', function(event) {
if (!event.ctrlKey && !event.shiftKey)
getCaretXY();
});
// ... and also on page load
getCaretXY();
});
function getCaretXY(){
// this has got to be a performance drag, but...
// paste a special span at the cursor pos
pasteHtmlAtCaret("<span id='XY'></span>")
// get the XY offset of that element
var XYoffset = $("#XY").offset();
// remove our special element
$("#XY").remove();
// do something with the values
$("#caretXY").html(XYoffset.top + "," + XYoffset.left);
}
function pasteHtmlAtCaret(html) {
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (document.selection && document.selection.type != "Control") {
// IE < 9
document.selection.createRange().pasteHTML(html);
}
}
I got the paste at caret function from here
The x,y values of the cursor are recorded on keydown, and it's actually not as much of a performance hog as I expected, but unfortunately it has some issues.
It's inaccurate. If moving the cursor down the left extremity, the span
used to get the X,Y coords sometimes gets pushed to the previous line.
If I shift highlight any of the text, and press the arrow keys the text is deleted. This is obviously a major no-no for an editor! I could possible fix this by having a shadow div of the same dimensions hidden somewhere on the page. getCaretXY could then paste the text into this div and use that to determine the height. The inaccuracy issue remains though.
I also didn't have much luck using this info to adjust the position of the editor, although that's just a case of fiddling with the CSS for long enough
I had an epiphany earlier while driving and listening to Kate Bush's Running up that hill. There's a much, much easier way to do what I want to do.
Create a div
with the content. Create a second, floating div
one line high. Use the scrollTop
height from the second div
to set the height of the first. It works beautifully.
demo: http://jsfiddle.net/aheydvjk/1/
HTML
<div class="editorbox" id="editor" contenteditable="true"></div>
<div class="editorbox" id="overlay" contenteditable="true"></div>
CSS
html, body{
overflow: hidden;
}
.editorbox {
overflow: hidden;
position: absolute;
left: 50%;
top: 200px;
margin-left: -200px;
margin-top: -20px;
width: 400px;
min-height: 20px;
line-height: 20px;
padding: 10px;
box-shadow: 0 0 0 1px #ddd;
height: 100%;
color: #777;
}
#overlay {
height: 20px;
background: #fff;
color: #222;
box-shadow: none;
}
Javascript
$(function(){
$("#overlay").on("scroll", function(){
$("#editor").css("top", 200 -$("#overlay").scrollTop() + "px");
});
$("#overlay").on("keyup", function(){
$("#editor").html($("#overlay").html());
});
});
There are a couple of little things I need to sort, like clicking somewhere on the text or selecting more than one line, but this is a good place to start working from. Thanks for all the help!