0

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?

Update

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.

  1. 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.

  2. 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

Community
  • 1
  • 1
roryok
  • 9,325
  • 17
  • 71
  • 138

3 Answers3

0

I came up with a solution that does the trick.

HTML

<div contenteditable></div>

CSS

* {
    margin: 0;
    padding: 0;
}
div[contenteditable] {
    position: absolute;
    left: 50%;
    top: 50%;
    margin-left: -160px;
    margin-top: -20px;
    width: 300px;
    min-height: 20px;
    line-height: 20px;
    padding: 10px;
    box-shadow: 0 0 0 1px #ddd;
}

JavaScript

var $div = $('div[contenteditable]');
var divPadding = parseInt($div.css('padding-top'));
var divLineHeight = parseInt($div.css('line-height'));

var centerDiv = function () {
    var windowHeight = window.innerHeight;
    var divHeight = $div.height();
    var divLines = divHeight / divLineHeight;

    var divTop = (windowHeight / 2) - ((divLines - 1) * divLineHeight) - divPadding;

    $div.css('top', divTop);
}

setInterval(function () {
    centerDiv();
}, 50);

What we're doing is repeatedly calling centerDiv(), which checks the height of the editable <div>, checks the number of lines that it contains, and then calculates and updates the top property to vertically center the cursor.

Here's a live demo: http://jsfiddle.net/galengidman/u6d8krLm/

Galen Gidman
  • 552
  • 3
  • 9
  • It's a great start, but with this the *last* line is always centered, not the *current* line. if I move back to the top the cursor position is no longer centered. – roryok Aug 27 '14 at 18:24
0

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!

roryok
  • 9,325
  • 17
  • 71
  • 138
-1

Without a code sample to look at, I can't say for sure, but maybe a text-align:center might be your solution?

chargarg
  • 37
  • 7