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

После окончательного кроссбраузерного решения я устанавливаю позицию курсора/каретки в последнюю известную позицию, когда contentEditable = 'on' <div> восстанавливает фокус. Кажется, что функциональность по умолчанию для редактируемого содержимого 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. Я пробовал этот ресурс, но кажется, что он не работает для <div> . Возможно, только для textarea (Как переместить курсор в конец контентного объекта)

Ответ 1

Это совместимо со стандартными браузерами, но, вероятно, сбой в IE. Я предоставляю его в качестве отправной точки. IE не поддерживает 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);
};

Ответ 2

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

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

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

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

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

Переменная isInFocus сохраняет ли она в фокусе и изменяется на "false" onblur и "true" onfocus. Это позволяет отменять события click, только если div не находится в фокусе (иначе вы вообще не сможете изменить выбор).

Если вы хотите, чтобы выбор был изменен, когда div сфокусирован щелчком, а не восстанавливает выделение onclick (и только тогда, когда фокус задается элементу программно с помощью document.getElementById("area").focus(); или аналогичного, просто удалите onclick и onmousedown. События onblur и функции onDivBlur() и cancelEvent() также могут быть удалены в этих обстоятельствах.

Этот код должен работать, если вы упадете непосредственно в тело html-страницы, если вы хотите быстро протестировать его:

<div id="area" style="width:300px;height:300px;" onblur="onDivBlur();" onmousedown="return cancelEvent(event);" onclick="return cancelEvent(event);" contentEditable="true" onmouseup="saveSelection();" onkeyup="saveSelection();" onfocus="restoreSelection();"></div>
<script type="text/javascript">
var savedRange,isInFocus;
function saveSelection()
{
    if(window.getSelection)//non IE Browsers
    {
        savedRange = window.getSelection().getRangeAt(0);
    }
    else if(document.selection)//IE
    { 
        savedRange = document.selection.createRange();  
    } 
}

function restoreSelection()
{
    isInFocus = true;
    document.getElementById("area").focus();
    if (savedRange != null) {
        if (window.getSelection)//non IE and there is already a selection
        {
            var s = window.getSelection();
            if (s.rangeCount > 0) 
                s.removeAllRanges();
            s.addRange(savedRange);
        }
        else if (document.createRange)//non IE and no selection
        {
            window.getSelection().addRange(savedRange);
        }
        else if (document.selection)//IE
        {
            savedRange.select();
        }
    }
}
//this part onwards is only needed if you want to restore selection onclick
var isInFocus = false;
function onDivBlur()
{
    isInFocus = false;
}

function cancelEvent(e)
{
    if (isInFocus == false && savedRange != null) {
        if (e && e.preventDefault) {
            //alert("FF");
            e.stopPropagation(); // DOM style (return false doesn't always work in FF)
            e.preventDefault();
        }
        else {
            window.event.cancelBubble = true;//IE stopPropagation
        }
        restoreSelection();
        return false; // false = IE style
    }
}
</script>

Ответ 3

Обновление

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

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

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

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

EDIT:

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

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

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

Я скоро напишу это в какой-то момент.

Ответ 4

У меня была связанная с этим ситуация, когда мне особенно нужно было установить позицию курсора в конец END контента, пригодного для контента. Я не хотел использовать полноценную библиотеку, такую ​​как 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;
}

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

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

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

Что это!

Ответ 5

Я взял Nico Burns ответ и сделал это с помощью jQuery:

  • Общий: для каждого 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>

Ответ 6

После того, как я поиграл, я ответил на измененный 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);
    });
};

Ответ 7

Вы можете использовать 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();

Ответ 8

В Firefox у вас может быть текст div в дочернем node (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);