it-swarm.com.de

Setzen Sie den Cursor auf contentEditable <div>

Ich bin nach einer endgültigen, browserübergreifenden Lösung zum Setzen der Cursor-/Caret-Position auf die letzte bekannte Position, wenn contentEditable = 'on' <div> wieder den Fokus erhält. Es sieht so aus, als ob die Standardfunktionalität eines inhaltsbearbeitbaren Divs darin besteht, das Caret/den Cursor bei jedem Klicken an den Anfang des Textes im Div zu bewegen, was unerwünscht ist.

Ich glaube, ich müsste die aktuelle Cursorposition in einer Variablen speichern, wenn sie den Fokus des Divs verlassen, und diese dann zurücksetzen, wenn sie wieder den Fokus im Div haben, aber ich konnte sie nicht zusammensetzen oder eine Arbeit finden Codebeispiel noch.

Wenn jemand irgendwelche Gedanken, Arbeitscode-Schnipsel oder Beispiele hat, würde ich mich freuen, sie zu sehen.

Ich habe noch keinen Code, aber hier ist, was ich habe:

<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. Ich habe diese Ressource ausprobiert, aber es scheint, dass sie für ein <div> nicht funktioniert. Möglicherweise nur für Textbereiche ( Bewegen des Cursors zum Ende der inhaltsbearbeitbaren Entität )

139
GONeale

Dies ist mit standardbasierten Browsern kompatibel, schlägt jedoch wahrscheinlich im Internet Explorer fehl. Ich biete es als Ausgangspunkt. IE unterstützt den DOM-Bereich nicht.

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

Diese Lösung funktioniert in allen gängigen Browsern:

saveSelection() wird an die Ereignisse onmouseup und onkeyup des div angehängt und speichert die Auswahl in der Variablen savedRange.

restoreSelection() wird an das Ereignis onfocus des div angehängt und wählt die in savedRange gespeicherte Auswahl erneut aus.

Dies funktioniert einwandfrei, es sei denn, Sie möchten, dass die Auswahl wiederhergestellt wird, wenn der Benutzer auch auf das Div klickt (was etwas unintuitiv ist, da Sie normalerweise erwarten, dass der Cursor an die Stelle fährt, auf die Sie klicken, der Code jedoch der Vollständigkeit halber enthalten ist).

Um dies zu erreichen, werden die Ereignisse onclick und onmousedown durch die Funktion cancelEvent() abgebrochen, die eine browserübergreifende Funktion zum Abbrechen des Ereignisses ist. Die Funktion cancelEvent() führt auch die Funktion restoreSelection() aus, da das div beim Abbrechen des Klickereignisses nicht den Fokus erhält und daher überhaupt nichts ausgewählt wird, es sei denn, diese Funktion wird ausgeführt.

Die Variable isInFocus speichert, ob sie fokussiert ist, und wird in "false" onblur und "true" onfocus geändert. Auf diese Weise können Klickereignisse nur abgebrochen werden, wenn das Div nicht im Fokus steht (andernfalls können Sie die Auswahl überhaupt nicht ändern).

Wenn Sie möchten, dass die Auswahl geändert wird, wenn das Div durch Klicken fokussiert wird, und die Auswahl nicht wiederhergestellt wird onclick (und nur, wenn das Element programmgesteuert mit document.getElementById("area").focus(); oder ähnlich fokussiert wird Entfernen Sie dann einfach die Ereignisse onclick und onmousedown. Das Ereignis onblur und die Funktionen onDivBlur() und cancelEvent() können ebenfalls sicher entfernt werden unter diesen Umständen.

Dieser Code sollte funktionieren, wenn er direkt in den Text einer HTML-Seite eingefügt wird, wenn Sie ihn schnell testen möchten:

<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>
95
Nico Burns

Update

Ich habe eine browserübergreifende Bereichs- und Auswahlbibliothek namens Rangy geschrieben, die eine verbesserte Version des Codes enthält, den ich unten gepostet habe. Sie können für diese bestimmte Frage das Modul zum Speichern und Wiederherstellen der Auswahl verwenden, obwohl ich versucht wäre, etwas wie Antwort von @ Nico Burns zu verwenden, wenn Sie nichts anderes tun mit Auswahlen in Ihrem Projekt und brauchen nicht den Großteil einer Bibliothek.

Vorherige Antwort

Sie können IERange ( http://code.google.com/p/ierange/ ) verwenden, um den TextRange des IE in einen DOM-Bereich zu konvertieren und ihn in Verbindung mit dem Ausgangspunkt der Augenlosigkeit zu verwenden. Persönlich würde ich nur die Algorithmen von IERange verwenden, die die Range <-> TextRange-Konvertierungen durchführen, anstatt das Ganze zu verwenden. Das Auswahlobjekt des IE verfügt nicht über die Eigenschaften focusNode und anchorNode, sondern Sie sollten stattdessen nur Range/TextRange aus der Auswahl verwenden können.

Ich könnte etwas zusammenstellen, um dies zu tun, werde hier zurück posten, wenn und wann ich es tue.

BEARBEITEN:

Ich habe eine Demo eines Skripts erstellt, das dies ausführt. Es funktioniert in allem, was ich bisher ausprobiert habe, mit Ausnahme eines Fehlers in Opera 9, auf den ich noch nicht eingegangen bin. Die Browser, in denen es funktioniert, sind IE 5.5, 6 und 7, Chrome 2, Firefox 2, 3 und 3.5 und Safari 4, alle unter Windows.

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

Beachten Sie, dass die Auswahl in Browsern möglicherweise rückwärts erfolgt, sodass sich der Fokusknoten am Anfang der Auswahl befindet. Wenn Sie die rechte oder linke Cursortaste drücken, wird das Einfügemarke relativ zum Anfang der Auswahl verschoben. Ich denke nicht, dass es möglich ist, dies beim Wiederherstellen einer Auswahl zu replizieren, daher befindet sich der Fokusknoten immer am Ende der Auswahl.

Ich werde dies bald vollständig aufschreiben.

19
Tim Down

Ich hatte eine verwandte Situation, in der ich speziell die Cursorposition auf das ENDE eines inhaltsbearbeitbaren Divs setzen musste. Ich wollte keine vollwertige Bibliothek wie Rangy verwenden, und viele Lösungen waren viel zu schwer.

Am Ende habe ich mir diese einfache jQuery-Funktion ausgedacht, um die Karat-Position auf das Ende eines inhaltsbearbeitbaren Div zu setzen:

$.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;
}

Die Theorie ist einfach: Hängen Sie einen Bereich an das Ende der bearbeitbaren Datei an, wählen Sie ihn aus und entfernen Sie den Bereich. Lassen Sie uns einen Cursor am Ende der Div. Sie können diese Lösung anpassen, um die Spanne an einer beliebigen Stelle einzufügen und so den Cursor an einer bestimmten Stelle zu platzieren.

Die Verwendung ist einfach:

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

Das ist es!

15
Zane Claes

Ich habe die Antwort von Nico Burns genommen und mit jQuery gemacht:

  • Generisch: Für jedes div contentEditable="true"
  • Kürzer

Sie benötigen jQuery 1.6 oder höher:

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>
7
Gatsbimantico

Nach dem Herumspielen habe ich die Antwort von eyelidlessness oben geändert und es zu einem jQuery-Plugin gemacht, sodass Sie einfach eine der folgenden Aktionen ausführen können:

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

Entschuldigen Sie die lange Code-Post, aber es kann jemandem helfen:

$.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);
    });
};
4
mkaj

Sie können selectNodeContents verwenden, das von modernen Browsern unterstützt wird.

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();
1
zoonman

In Firefox können Sie den Text des div in einem untergeordneten Knoten haben (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);
0
yoav