it-swarm.com.de

Wie setze ich die Cursorposition in einem inhaltsbearbeitbaren Element (div)?

Ich habe dieses einfache HTML als Beispiel:

<div id="editable" contenteditable="true">
  text text text<br>
  text text text<br>
  text text text<br>
</div>
<button id="button">focus</button>

Ich möchte eine einfache Sache: Wenn ich auf die Schaltfläche klicke, möchte ich das Einfügemarke (den Cursor) an einer bestimmten Stelle im bearbeitbaren Div platzieren. Nach der Suche über das Web habe ich dieses JS an einen Button-Klick angehängt, aber es funktioniert nicht (FF, Chrome):

var range = document.createRange();
var myDiv = document.getElementById("editable");
range.setStart(myDiv, 5);
range.setEnd(myDiv, 5);

Ist es möglich, die Caret-Position manuell so einzustellen?

159
Frodik

In den meisten Browsern benötigen Sie die Objekte Range und Selection . Sie geben jede Auswahlgrenze als Knoten und einen Versatz innerhalb dieses Knotens an. Um das Caret beispielsweise auf das fünfte Zeichen der zweiten Textzeile festzulegen, gehen Sie folgendermaßen vor:

var el = document.getElementById("editable");
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el.childNodes[2], 5);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);

IE <9 funktioniert völlig anders. Wenn Sie diese Browser unterstützen müssen, benötigen Sie anderen Code.

jsFiddle-Beispiel: http://jsfiddle.net/timdown/vXnCM/

222
Tim Down

Die meisten Antworten auf inhaltsbearbeitbare Cursorpositionierungen sind insofern ziemlich einfach, als sie nur Eingaben mit einfachem Vanille-Text ermöglichen. Sobald Sie HTML-Elemente innerhalb des Containers verwenden, wird der eingegebene Text in Knoten aufgeteilt und großzügig über eine Baumstruktur verteilt.

Um die Cursorposition festzulegen, habe ich diese Funktion, die alle untergeordneten Textknoten innerhalb des angegebenen Knotens umkreist und einen Bereich vom Anfang des Anfangsknotens bis zum Zeichen chars.count festlegt:

function createRange(node, chars, range) {
    if (!range) {
        range = document.createRange()
        range.selectNode(node);
        range.setStart(node, 0);
    }

    if (chars.count === 0) {
        range.setEnd(node, chars.count);
    } else if (node && chars.count >0) {
        if (node.nodeType === Node.TEXT_NODE) {
            if (node.textContent.length < chars.count) {
                chars.count -= node.textContent.length;
            } else {
                range.setEnd(node, chars.count);
                chars.count = 0;
            }
        } else {
           for (var lp = 0; lp < node.childNodes.length; lp++) {
                range = createRange(node.childNodes[lp], chars, range);

                if (chars.count === 0) {
                    break;
                }
            }
        }
    } 

    return range;
};

Ich rufe dann die Routine mit dieser Funktion auf:

function setCurrentCursorPosition(chars) {
    if (chars >= 0) {
        var selection = window.getSelection();

        range = createRange(document.getElementById("test").parentNode, { count: chars });

        if (range) {
            range.collapse(false);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }
};

Der range.collapse (false) setzt den Cursor an das Ende des Bereichs. Ich habe es mit den neuesten Versionen von Chrome, IE, Mozilla und Opera getestet und sie funktionieren alle einwandfrei.

PS. Wenn jemand interessiert ist, erhalte ich die aktuelle Cursorposition mit diesem Code:

function isChildOf(node, parentId) {
    while (node !== null) {
        if (node.id === parentId) {
            return true;
        }
        node = node.parentNode;
    }

    return false;
};

function getCurrentCursorPosition(parentId) {
    var selection = window.getSelection(),
        charCount = -1,
        node;

    if (selection.focusNode) {
        if (isChildOf(selection.focusNode, parentId)) {
            node = selection.focusNode; 
            charCount = selection.focusOffset;

            while (node) {
                if (node.id === parentId) {
                    break;
                }

                if (node.previousSibling) {
                    node = node.previousSibling;
                    charCount += node.textContent.length;
                } else {
                     node = node.parentNode;
                     if (node === null) {
                         break
                     }
                }
           }
      }
   }

    return charCount;
};

Der Code macht das Gegenteil der Set-Funktion - er erhält das aktuelle window.getSelection (). FocusNode und focusOffset und zählt alle gefundenen Textzeichen rückwärts, bis er auf einen übergeordneten Knoten mit der ID containerId stößt. Die Funktion isChildOf prüft vor der Ausführung, ob der gelieferte Knoten tatsächlich ein untergeordnetes Element des angegebenen parentId ist.

Der Code sollte ohne Änderung funktionieren, aber ich habe ihn gerade von einem von mir entwickelten jQuery-Plugin übernommen, also habe ich ein paar dies ist herausgehackt - lass es mich wissen, wenn etwas nicht funktioniert!

48
Liam

Es ist sehr schwer, das Caret in die richtige Position zu bringen, wenn Sie ein fortgeschrittenes Element wie (p) (span) usw. haben. Das Ziel ist es, (Objekttext) zu erhalten:

<div id="editable" contenteditable="true">dddddddddddddddddddddddddddd<p>dd</p>psss<p>dd</p>
    <p>dd</p>
    <p>text text text</p>
</div>
<p id='we'></p>
<button onclick="set_mouse()">focus</button>
<script>

    function set_mouse() {
        var as = document.getElementById("editable");
        el = as.childNodes[1].childNodes[0];//goal is to get ('we') id to write (object Text) because it work only in object text
        var range = document.createRange();
        var sel = window.getSelection();
        range.setStart(el, 1);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);

        document.getElementById("we").innerHTML = el;// see out put of we id
    }
</script>
3
Jalaluddin Rumi
  const el = document.getElementById("editable");
  el.focus()
  let char = 1, sel; // character at which to place caret

  if (document.selection) {
    sel = document.selection.createRange();
    sel.moveStart('character', char);
    sel.select();
  }
  else {
    sel = window.getSelection();
    sel.collapse(el.lastChild, char);
  }
2
Sagar M

Wenn Sie jQuery nicht verwenden möchten, können Sie diesen Ansatz ausprobieren:

public setCaretPosition() {
    const editableDiv = document.getElementById('contenteditablediv');
    const lastLine = this.input.nativeElement.innerHTML.replace(/.*?(<br>)/g, '');
    const selection = window.getSelection();
    selection.collapse(editableDiv.childNodes[editableDiv.childNodes.length - 1], lastLine.length);
}

editableDiv Vergessen Sie nicht, ein id für das bearbeitbare Element festzulegen. Dann müssen Sie Ihr innerHTML aus dem Element holen und alle Bremsleitungen abschneiden. Und setzen Sie mit den nächsten Argumenten den Einsturz.

2
Volodymyr Khmil

Ich schreibe einen Syntax-Textmarker (und einen einfachen Code-Editor) und musste wissen, wie man ein einfaches Anführungszeichen automatisch eingibt und das Caret zurücksetzt (wie heutzutage viele Code-Editoren).

Hier ist ein Auszug meiner Lösung, dank der großen Hilfe von diesem Thread, den MDN-Dokumenten und viel Moz-Konsolenbeobachtung.

//onKeyPress event

if (evt.key === "\"") {
    let sel = window.getSelection();
    let offset = sel.focusOffset;
    let focus = sel.focusNode;

    focus.textContent += "\""; //setting div's innerText directly creates new
    //nodes, which invalidate our selections, so we modify the focusNode directly

    let range = document.createRange();
    range.selectNode(focus);
    range.setStart(focus, offset);

    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}

//end onKeyPress event

Dies ist ein inhaltlich editierbares div-Element

Ich lasse das hier als Dankeschön, da mir klar ist, dass es bereits eine akzeptierte Antwort gibt.

1

Ich denke, es ist nicht einfach, caret in einem inhaltsbearbeitbaren Element auf eine bestimmte Position zu setzen. Ich habe dafür meinen eigenen Code geschrieben. Es umgeht den Knotenbaum und berechnet, wie viele Zeichen übrig sind, und setzt das Einfügemarke in das benötigte Element. Ich habe diesen Code nicht viel getestet.

//Set offset in current contenteditable field (for start by default or for with forEnd=true)
function setCurSelectionOffset(offset, forEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return;

    const firstRange = sel.getRangeAt(0);

    if (offset > 0) {
        bypassChildNodes(document.activeElement, offset);
    }else{
        if (forEnd)
            firstRange.setEnd(document.activeElement, 0);
        else
            firstRange.setStart(document.activeElement, 0);
    }



    //Bypass in depth
    function bypassChildNodes(el, leftOffset) {
        const childNodes = el.childNodes;

        for (let i = 0; i < childNodes.length && leftOffset; i++) {
            const childNode = childNodes[i];

            if (childNode.nodeType === 3) {
                const curLen = childNode.textContent.length;

                if (curLen >= leftOffset) {
                    if (forEnd)
                        firstRange.setEnd(childNode, leftOffset);
                    else
                        firstRange.setStart(childNode, leftOffset);
                    return 0;
                }else{
                    leftOffset -= curLen;
                }
            }else
            if (childNode.nodeType === 1) {
                leftOffset = bypassChildNodes(childNode, leftOffset);
            }
        }

        return leftOffset;
    }
}

Ich habe auch Code geschrieben, um die aktuelle Caret-Position zu erhalten (habe nicht getestet):

//Get offset in current contenteditable field (start offset by default or end offset with calcEnd=true)
function getCurSelectionOffset(calcEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return 0;

    const firstRange     = sel.getRangeAt(0),
          startContainer = calcEnd ? firstRange.endContainer : firstRange.startContainer,
          startOffset    = calcEnd ? firstRange.endOffset    : firstRange.startOffset;
    let needStop = false;

    return bypassChildNodes(document.activeElement);



    //Bypass in depth
    function bypassChildNodes(el) {
        const childNodes = el.childNodes;
        let ans = 0;

        if (el === startContainer) {
            if (startContainer.nodeType === 3) {
                ans = startOffset;
            }else
            if (startContainer.nodeType === 1) {
                for (let i = 0; i < startOffset; i++) {
                    const childNode = childNodes[i];

                    ans += childNode.nodeType === 3 ? childNode.textContent.length :
                           childNode.nodeType === 1 ? childNode.innerText.length :
                           0;
                }
            }

            needStop = true;
        }else{
            for (let i = 0; i < childNodes.length && !needStop; i++) {
                const childNode = childNodes[i];
                ans += bypassChildNodes(childNode);
            }
        }

        return ans;
    }
}

Beachten Sie außerdem, dass range.startOffset und range.endOffset Zeichenversatz für Textknoten (nodeType === 3) und untergeordneter Knotenversatz für Elementknoten (nodeType === 1) enthalten. range.startContainer und range.endContainer können sich auf jeden Elementknoten einer beliebigen Ebene in der Struktur beziehen (natürlich können sie sich auch auf Textknoten beziehen).

0
vitaliydev