javascript - Hur man får skärmkoordinaterna för den markerade texten i en textarea


Hej allihopa, jag försöker verkligen lista ut hur man får X/Y för det markerade ordet i ett textarea-fält.

Detta är min nuvarande kod:

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<script type="text/javascript">
var X = 0;
var Y = 0;

function selectHTML() {
    try {
        if (window.ActiveXObject) {
            var c = document.selection.createRange();
            return c.htmlText;
        }

        X = getSelection().getRangeAt(0).endOffset;
        Y = getSelection().getRangeAt(0).startOffset;
        w.surroundContents(nNd);

        return nNd.innerHTML;
    } catch (e) {
        if (window.ActiveXObject) {
            return document.selection.createRange();
        } else {
            return getSelection();
        }
    }
}

function FindTextInsideField() {
    var str = document.getElementById("FindText").value;
    var supported = false;
    var found = false;

    if (window.find) {
        supported = true;
        found = window.find(str);

        let pos = document.getElementById("FindText").value.indexOf(str);

        if (pos >= 0) {
            document.getElementById("FindText").setRangeText(str, pos, pos + 4, "select");
            document.getElementById("FindText").focus();
        }

        $('#FindText').val("");
    } else {
        if (document.selection && document.selection.createRange) {
            var textRange = document.selection.createRange();

            if (textRange.findText) {
                supported = true;

                if (textRange.text.length > 0) {
                    textRange.collapse(true);
                    textRange.move("character", 1);
                }

                found = textRange.findText(str);

                if (found) {
                    textRange.select();
                }
            }
        }

        $('#FindText').val("");
    }

    if (supported) {
        if (!found) {
            alert("The following text was not found:\n" + str);
            $('#FindText').val("");
        }
    } else {
        alert("Your browser does not support this example!");
        $('#FindText').val("");
    }
}

function findXY() {
    var mytext = selectHTML();
    $('#_findXY').val("X = " + X + "  Y = " + Y);
}
</script>
</head>
<body>
 <textarea id = "paragraph_text" name = "paragraph_text" cols = "100" rows = "30" > Lorem ipsum dolor sit amet, eam iusto regione at.Mei id clita legendos.His ipsum neglegentur id, elit oblique no eos.Eum at clita eruditi.Vix quem hinc ex, meliore deserunt vix id, ei error ludus impetus ius.At evertitur elaboraret mel, sonet dolorum repudiandae mea at.
An iusto menandri repudiare mel, eu iisque definiebas pri, semper convenire eam ne.In ius percipit consequat.Ut sumo offendit quo.In duo epicuri nostrum eligendi, essent regione sed no.
In exerci doming splendide sit, mel omnes delicatissimi ei, at virtute vulputate efficiantur his.Quo possim civibus eu, hinc soluta ius ex.Ea quem dolor veniam mel.Sea ex paulo labores laboramus, te illud ludus mel.
Quo vidit nostrum postulant no, paulo doctus diceret vim et, sumo nullam reprehendunt in mei.Eu vis amet commune delicatissimi.Falli impedit in sea.Soluta appareat phaedrum ea sea.Sea facete postulant necessitatibus at, sea veri probo no.

---------------------------------------------------------------------------------------------- -

 </textarea >
 <br/>
 <input type = "text" id = "FindText" value = "percipit" size = "20" / >
 <button onclick = "FindTextInsideField();" > Find! </button>
 <input type = "text" style = "margin-left: 30px;" id = "_findXY" value = "" size = "20" readonly />
 <button onclick = "findXY();" > Get X / Y cords </button>
</body>
</html>

Att markera ordet fungerar alldeles utmärkt. Du skriver in vilket ord som helst som du vill söka efter i textrutan. Men när du trycker på Get X/Y cords visas X=5 Y=5 vilket är fel eftersom det borde vara i X=~250px by Y=~80px området enligt codePen-kodexemplet.

Någon som vet hur man löser detta eller är det ens lösligt?

Svar #1

Första problemet: startOffset och endOffset relaterar inte till positionen för ett urval på skärmen. Istället relaterar de till tecken- eller nodpositionen inom startContainer eller endContainer där ett urval börjar och slutar. Detta gör att du kan bestämma start- och slutindex för tecken eller nod i ett urval som sträcker sig över flera element (tänk dig ett urval som börjar i ett fetstilt område i ett stycke och slutar utanför det, startOffset skulle vara indexet inom endast den fetstilta texten, och endOffset skulle vara indexet inom Text-intervallet som kommer efter det fetstilta elementet).

God nyhet: Det finns en experimentell funktion på Range-objektet som heter getBoundingClientRect som hämtar skärmkoordinater för en begränsningsruta som omger det aktuella urvalet.

Problemet: Tyvärr, åtminstone i Google Chrome, returnerar getBoundingClientRect inte exakta koordinater när en markering finns inom en textarea eller input. Så om du inte kan undvika att använda textarea, till exempel genom att använda en content editable div (se: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content), kanske den här lösningen inte fungerar för dig.

Galet alternativ: Canvas-objektet har funktioner för att mäta textdimensioner, så om du verkligen är ihärdig kan du teoretiskt mäta dimensionerna på texten som finns i textarea, inklusive att efterlikna ordbrytning, för att självständigt beräkna var den markerade texten befinner sig inom textarea (glöm inte att ta hänsyn till rullningsförskjutning).

Här är ett utdrag som visuellt visar tillståndet för Range-objektet för markeringen när det förändras.

var outline = document.getElementById('selection_outline');
var start_container = document.getElementById('start_container');
var end_container = document.getElementById('end_container');
var start_offset = document.getElementById('start_offset');
var end_offset = document.getElementById('end_offset');

document.addEventListener('selectionchange', function() {
  var selection = document.getSelection();
  if (selection) {
    var range = selection.getRangeAt(0);
    if (range) {
      var bounds = range.getBoundingClientRect();
      outline.style.top = `${bounds.top + window.scrollY}px`;
      outline.style.left = `${bounds.left + window.scrollX}px`;
      outline.style.width = `${bounds.width}px`;
      outline.style.height = `${bounds.height}px`;
      
      start_container.value = range.startContainer.tagName || range.startContainer.parentNode.tagName;
      end_container.value = range.endContainer.tagName || range.endContainer.parentNode.tagName;
      
      start_offset.value = range.startOffset;
      end_offset.value = range.endOffset;
    }
  }
})
#selection_outline {
  position: absolute;
  border: 2px solid red;
  pointer-events: none;
}

.flow-wrap-row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}

.flow-wrap-row > * {
  white-space: nowrap
}

.flow-wrap-row > li {
  margin-right: 1.5em;
}

ul {
 padding-left: 1.5em;
}

input {
 width: 80px;
}
<p>This is <b>a complex element</b> with lots of selectable text <i>and nested, <b> and double nested</b> elements</i>!!!</p>
<textarea cols="60" rows="5">Here is a text area with some simple content.</textarea>
<div contenteditable="true">Here is a contenteditable div with some simple content.</div>
<ul class="flow-wrap-row">
  <li><label>startContainer: <input id="start_container" type="text" /></label></li>
  <li><label>endContainer: <input id="end_container" type="text" /></label></li>
  <li><label>startOffset: <input id="start_offset" type="text" /></label></li>
  <li><label>endOffset: <input id="end_offset" type="text" /></label></li>
</ul>
<div id="selection_outline"></div>