Установить положение курсора на contentEditable


Я после окончательного кросс-браузерного решения для установки позиции курсора / курсора в последнюю известную позицию, когда contentEditable='on'

восстанавливает фокус. Оказывается, по умолчанию функциональность редактируемого div контента заключается в перемещении курсора/курсора в начало текста в div каждый раз, когда вы нажимаете на него, что нежелательно.

Я считаю, что мне придется хранить в переменной текущую позицию курсора, когда они покидают фокус div, а затем повторно установить это когда они снова фокусируются внутри, но я еще не смог собрать или найти рабочий пример кода.

Если у кого-то есть какие-то мысли, рабочие фрагменты кода или образцы, я был бы рад их увидеть.

У меня еще нет кода, но вот что у меня есть:

<script type="text/javascript">
// jQuery
$(document).ready(function() {
   $('#area').focus(function() { .. }  // focus I would imagine I need.
}
</script>
<div id="area" contentEditable="true"></div>

PS. Я пробовал этот ресурс, но, похоже, он не работает для

8 136

8 ответов:

это совместимо со стандартными браузерами, но, вероятно, не удастся в IE. Я предоставляю его в качестве отправной точки. IE не поддерживает диапазон DOM.

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);
};

это решение работает во всех основных браузерах:

saveSelection() присоединен к onmouseup и onkeyup события div и сохраняет выбор в переменной savedRange.

restoreSelection() присоединен к onfocus событие div и повторно выбирает выбор, сохраненный в savedRange.

это работает отлично, если вы не хотите, чтобы выбор был восстановлен, когда пользователь нажимает div aswell (что немного неинтуитивно, как обычно ожидайте, что курсор будет идти туда, где вы нажимаете, но код включен для полноты)

для этого onclick и onmousedown мероприятия отменены с помощью функции cancelEvent() который является функцией кросс-браузера, чтобы отменить событие. Элемент

обновление

Я написал кросс-браузерный диапазон и библиотеку выбора под названием Rangy это включает в себя улучшенную версию кода, который я опубликовал ниже. Вы можете использовать выбор сохранить и восстановить модуль для этого конкретного вопроса, хотя я был бы соблазн использовать что-то вроде @ ответ Нико Бернса если вы больше ничего не делаете с выборками в своем проекте и не нуждаетесь в основной части библиотека.

предыдущий ответ

вы можете использовать IERange (http://code.google.com/p/ierange/), чтобы преобразовать текстовый диапазон IE в нечто вроде диапазона DOM и использовать его в сочетании с чем-то вроде начальной точки eyelidlessness. Лично я бы использовал только алгоритмы из IERange, которые выполняют преобразования Range TextRange, а не используют все это. И выбора объекта, т. е. не имеют focusNode и anchorNode свойства, но вы должны быть в состоянии просто использовать диапазон/TextRange, полученный из выбора вместо этого.

Я мог бы собрать что-то вместе, чтобы сделать это, опубликую здесь, если и когда я это сделаю.

EDIT:

Я создал демо-версию скрипта, который делает это. Он работает во всем, что я пробовал до сих пор, за исключением ошибки в Opera 9, которую я еще не успел изучить. Браузеры, в которых он работает, - это IE 5.5, 6 и 7, Chrome 2, Firefox 2, 3 и 3.5, а также Safari 4, Все на Windows.

http://www.timdown.co.uk/code/selections/

обратите внимание, что выбор может быть сделан в обратном направлении в браузерах, так что узел фокусировки находится в начале выбора и нажатие правой или левой клавиши курсора переместит курсор в положение относительно начала выбора. Я не думаю, что это можно повторить при восстановлении выделения, поэтому узел фокуса всегда находится в конце выделения.

I напишу это полностью в какой-то момент в ближайшее время.

у меня была связанная ситуация, когда мне специально нужно было установить положение курсора в конец contenteditable div. Я не хотел использовать полноценную библиотеку, такую как Rangy, и многие решения были слишком тяжелыми.

в конце концов, я придумал эту простую функцию jQuery, чтобы установить положение карата до конца contenteditable div:

$.fn.focusEnd = function() {
    $(this).focus();
    var tmp = $('<span />').appendTo($(this)),
        node = tmp.get(0),
        range = null,
        sel = null;

    if (document.selection) {
        range = document.body.createTextRange();
        range.moveToElementText(node);
        range.select();
    } else if (window.getSelection) {
        range = document.createRange();
        range.selectNode(node);
        sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }
    tmp.remove();
    return this;
}

теория проста: добавьте пролет в конец редактируемого, выберите его, а затем удалите пролет - оставив нас с курсором в конце разд. Вы можете адаптировать это решение, чтобы вставить пролет везде, где вы хотите, таким образом, поставив курсор в определенном месте.

использование просто:

$('#editable').focusEnd();

вот именно!

я взял ответ Нико Бернса и сделал это с помощью jQuery:

  • Generic: для каждого div contentEditable="true"
  • короче

вам понадобится jQuery 1.6 или выше:

savedRanges = new Object();
$('div[contenteditable="true"]').focus(function(){
    var s = window.getSelection();
    var t = $('div[contenteditable="true"]').index(this);
    if (typeof(savedRanges[t]) === "undefined"){
        savedRanges[t]= new Range();
    } else if(s.rangeCount > 0) {
        s.removeAllRanges();
        s.addRange(savedRanges[t]);
    }
}).bind("mouseup keyup",function(){
    var t = $('div[contenteditable="true"]').index(this);
    savedRanges[t] = window.getSelection().getRangeAt(0);
}).on("mousedown click",function(e){
    if(!$(this).is(":focus")){
        e.stopPropagation();
        e.preventDefault();
        $(this).focus();
    }
});

savedRanges = new Object();
$('div[contenteditable="true"]').focus(function(){
    var s = window.getSelection();
    var t = $('div[contenteditable="true"]').index(this);
    if (typeof(savedRanges[t]) === "undefined"){
        savedRanges[t]= new Range();
    } else if(s.rangeCount > 0) {
        s.removeAllRanges();
        s.addRange(savedRanges[t]);
    }
}).bind("mouseup keyup",function(){
    var t = $('div[contenteditable="true"]').index(this);
    savedRanges[t] = window.getSelection().getRangeAt(0);
}).on("mousedown click",function(e){
    if(!$(this).is(":focus")){
        e.stopPropagation();
        e.preventDefault();
        $(this).focus();
    }
});
div[contenteditable] {
    padding: 1em;
    font-family: Arial;
    outline: 1px solid rgba(0,0,0,0.5);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contentEditable="true"></div>
<div contentEditable="true"></div>
<div contentEditable="true"></div>

после игры я изменил ответ eyelidlessness выше и сделал его плагином jQuery, чтобы вы могли просто сделать один из них:

var html = "The quick brown fox";
$div.html(html);

// Select at the text "quick":
$div.setContentEditableSelection(4, 5);

// Select at the beginning of the contenteditable div:
$div.setContentEditableSelection(0);

// Select at the end of the contenteditable div:
$div.setContentEditableSelection(html.length);

извините за длинный пост код, но это может помочь кому-то:

$.fn.setContentEditableSelection = function(position, length) {
    if (typeof(length) == "undefined") {
        length = 0;
    }

    return this.each(function() {
        var $this = $(this);
        var editable = this;
        var selection;
        var range;

        var html = $this.html();
        html = html.substring(0, position) +
            '<a id="cursorStart"></a>' +
            html.substring(position, position + length) +
            '<a id="cursorEnd"></a>' +
            html.substring(position + length, html.length);
        console.log(html);
        $this.html(html);

        // 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
            }
        };

        // Slight delay will avoid the initial selection
        // (at start or of contents depending on browser) being mistaken
        setTimeout(function() {
            var cursorStart = document.getElementById('cursorStart');
            var 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();
                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);
                }
            }

            // Register selection again
            captureSelection();
        }, 10);
    });
};

можно использовать selectNodeContents, который поддерживается современными браузерами.

var el = document.getElementById('idOfYoursContentEditable');
var selection = window.getSelection();
var range = document.createRange();
selection.removeAllRanges();
range.selectNodeContents(el);
range.collapse(false);
selection.addRange(range);
el.focus();

в Firefox у вас может быть текст div в дочернем узле (o_div.childNodes[0])

var range = document.createRange();

range.setStart(o_div.childNodes[0],last_caret_pos);
range.setEnd(o_div.childNodes[0],last_caret_pos);
range.collapse(false);

var sel = window.getSelection(); 
sel.removeAllRanges();
sel.addRange(range);