Dans mon projet, j'essaie d'obtenir la position de décalage du curseur dans un textarea
en pixels. Cela peut-il être fait?
Avant de demander ici, j'ai parcouru de nombreux liens , en particulier ceux de Tim Down, mais je n'ai pas trouvé de solution qui fonctionne dans IE8 +, Chrome et Firefox. Il semble Tim Down y travaille .
Certains autresliens que j'ai trouvés ont de nombreux problèmes comme ne pas trouver le décalage supérieur de la position du curseur.
J'essaie d'obtenir la position de décalage du curseur car je veux afficher une boîte de suggestion de saisie semi-automatique à l'intérieur du textarea
en la positionnant en fonction de la position de décalage du curseur.
PS: Je ne peux pas utiliser un contenteditable div
parce que j'ai écrit beaucoup de code lié à un textarea
.
Voici une approche utilisant rangyinputs , rangy et jQuery .
Il copie essentiellement le texte entier de l'intérieur du textarea
dans un div
de la même taille. J'ai défini du CSS pour m'assurer que dans chaque navigateur, le textarea
et le div
encapsulent leur contenu exactement de la même manière.
Lorsque l'on clique sur textarea
, je lis à quel index de caractères le curseur est positionné, puis j'insère un curseur span
au même index à l'intérieur du div
. Ce faisant, j'ai fini par avoir un problème avec le signe d'insertion span
pour revenir à la ligne précédente si l'utilisateur cliquait au début d'une ligne. Pour corriger cela, je vérifie si le caractère précédent est un space
(ce qui permettrait un habillage), si c'est true
, je l'enveloppe dans un span
, et J'encapsule le prochain mot (celui directement après la position du curseur) dans un span
. Maintenant, je compare les valeurs les plus élevées entre ces deux span
, si elles diffèrent, il y avait un habillage en cours, donc je suppose que la valeur top
et la valeur left
de les #nextword
span
sont équivalents à la position du curseur.
Cette approche peut encore être améliorée, je suis sûr que je n'ai pas pensé à tout ce qui pourrait mal tourner, et même si je l'ai fait, alors je n'ai pas pris la peine de mettre en œuvre un correctif pour tous, car je n'ai pas le temps de le faire pour le moment, un certain nombre de choses que vous devriez examiner:
Actuellement, il fonctionne exactement de la même manière sur tous les navigateurs ici sur Windows 8 avec les dernières versions de Chrome, Firefox, IE et Safari. Mes tests n'ont cependant pas été très rigoureux.
Voici un jsFiddle.
J'espère que cela vous aidera, à tout le moins, cela pourrait vous donner quelques idées sur lesquelles s'appuyer.
J'ai inclus un ul
pour vous qui est positionné au bon endroit, et corrigé un problème Firefox où la sélection textarea
n'était pas réinitialisée à son emplacement d'origine après les manipulations DOM.
J'ai ajouté le support IE7 - IE9 et corrigé le problème de sélection de plusieurs mots signalé dans les commentaires.
J'ai ajouté la prise en charge des retours durs insérés avec Enter et plusieurs espaces d'affilée.
J'ai résolu un problème avec le comportement par défaut du ctrl+shift+left arrow méthode de sélection de texte.
JavaScript
function getTextAreaXandY() {
// Don't do anything if key pressed is left arrow
if (e.which == 37) return;
// Save selection start
var selection = $(this).getSelection();
var index = selection.start;
// Copy text to div
$(this).blur();
$("div").text($(this).val());
// Get current character
$(this).setSelection(index, index + 1);
currentcharacter = $(this).getSelection().text;
// Get previous character
$(this).setSelection(index - 1, index)
previouscharacter = $(this).getSelection().text;
var start, endchar;
var end = 0;
var range = rangy.createRange();
// If current or previous character is a space or a line break, find the next Word and wrap it in a span
var linebreak = previouscharacter.match(/(\r\n|\n|\r)/gm) == undefined ? false : true;
if (previouscharacter == ' ' || currentcharacter == ' ' || linebreak) {
i = index + 1; // Start at the end of the current space
while (endchar != ' ' && end < $(this).val().length) {
i++;
$(this).setSelection(i, i + 1)
var sel = $(this).getSelection();
endchar = sel.text;
end = sel.start;
}
range.setStart($("div")[0].childNodes[0], index);
range.setEnd($("div")[0].childNodes[0], end);
var nextword = range.toHtml();
range.deleteContents();
var position = $("<span id='nextword'>" + nextword + "</span>")[0];
range.insertNode(position);
var nextwordtop = $("#nextword").position().top;
}
// Insert `#caret` at the position of the caret
range.setStart($("div")[0].childNodes[0], index);
var caret = $("<span id='caret'></span>")[0];
range.insertNode(caret);
var carettop = $("#caret").position().top;
// If preceding character is a space, wrap it in a span
if (previouscharacter == ' ') {
range.setStart($("div")[0].childNodes[0], index - 1);
range.setEnd($("div")[0].childNodes[0], index);
var prevchar = $("<span id='prevchar'></span>")[0];
range.insertNode(prevchar);
var prevchartop = $("#prevchar").position().top;
}
// Set textarea selection back to selection start
$(this).focus();
$(this).setSelection(index, selection.end);
// If the top value of the previous character span is not equal to the top value of the next Word,
// there must have been some wrapping going on, the previous character was a space, so the wrapping
// would have occured after this space, its safe to assume that the left and top value of `#nextword`
// indicate the caret position
if (prevchartop != undefined && prevchartop != nextwordtop) {
$("label").text('X: ' + $("#nextword").position().left + 'px, Y: ' + $("#nextword").position().top);
$('ul').css('left', ($("#nextword").position().left) + 'px');
$('ul').css('top', ($("#nextword").position().top + 13) + 'px');
}
// if not, then there was no wrapping, we can take the left and the top value from `#caret`
else {
$("label").text('X: ' + $("#caret").position().left + 'px, Y: ' + $("#caret").position().top);
$('ul').css('left', ($("#caret").position().left) + 'px');
$('ul').css('top', ($("#caret").position().top + 14) + 'px');
}
$('ul').css('display', 'block');
}
$("textarea").click(getTextAreaXandY);
$("textarea").keyup(getTextAreaXandY);
[~ # ~] html [~ # ~]
<div></div>
<textarea>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</textarea>
<label></label>
<ul>
<li>Why don't you type this..</li>
</ul>
[~ # ~] css [~ # ~]
body {
font-family: Verdana;
font-size: 12px;
line-height: 14px;
}
textarea, div {
font-family: Verdana;
font-size: 12px;
line-height: 14px;
width: 300px;
display: block;
overflow: hidden;
border: 1px solid black;
padding: 0;
margin: 0;
resize: none;
min-height: 300px;
position: absolute;
-moz-box-sizing: border-box;
white-space: pre-wrap;
}
span {
display: inline-block;
height: 14px;
position: relative;
}
span#caret {
display: inline;
}
label {
display: block;
margin-left: 320px;
}
ul {
padding: 0px;
margin: 9px;
position: absolute;
z-index: 999;
border: 1px solid #000;
background-color: #FFF;
list-style-type:none;
display: none;
}
@media screen and (-webkit-min-device-pixel-ratio:0) {
span {
white-space: pre-wrap;
}
}
div {
/* Firefox wrapping fix */
-moz-padding-end: 1.5px;
-moz-padding-start: 1.5px;
/* IE8/IE9 wrapping fix */
padding-right: 5px\0/;
width: 295px\0/;
}
span#caret
{
display: inline-block\0/;
}
Vous pouvez créer un élément séparé (invisible) et le remplir de contenu de zone de texte du début à la position du curseur. La zone de texte et le "clone" doivent avoir un CSS correspondant (propriétés de police, remplissage/marge/bordure et largeur). Empilez ensuite ces éléments les uns sur les autres.
Permettez-moi de commencer par un exemple de travail, puis parcourez le code: http://jsfiddle.net/g7rBk/
HTML:
<textarea id="input"></textarea>
<div id="output"><span></span></div>
<div id="xy"></div>
Textarea est explicite. La sortie est un élément caché auquel nous transmettrons le contenu texte et effectuerons des mesures. Ce qui est important, c'est que nous utiliserons un élément en ligne. le div "xy" n'est qu'un indicateur à des fins de test.
CSS:
/* identical styling to match the dimensions and position of textarea and its "clone"
*/
#input, #output {
position:absolute;
top:0;
left:0;
font:14px/1 monospace;
padding:5px;
border:1px solid #999;
white-space:pre;
margin:0;
background:transparent;
width:300px;
max-width:300px;
}
/* make sure the textarea isn't obscured by clone */
#input {
z-index:2;
min-height:200px;
}
#output {
border-color:transparent;
}
/* hide the span visually using opacity (not display:none), so it's still measurable; make it break long words inside like textarea does. */
#output span {
opacity:0;
Word-wrap: break-Word;
overflow-wrap: break-Word;
}
/* the cursor position indicator */
#xy {
position:absolute;
width:4px;
height:4px;
background:#f00;
}
JavaScript:
/* get references to DOM nodes we'll use */
var input = document.getElementById('input'),
output = document.getElementById('output').firstChild,
position = document.getElementById('position'),
/* And finally, here it goes: */
update = function(){
/* Fill the clone with textarea content from start to the position of the caret. You may need to expand here to support older IE [1]. The replace /\n$/ is necessary to get position when cursor is at the beginning of empty new line.
*/
output.innerHTML = input.value.substr( 0, input.selectionStart ).replace(/\n$/,"\n\001");
/* the fun part!
We use an inline element, so getClientRects[2] will return a collection of rectangles wrapping each line of text.
We only need the position of the last rectangle.
*/
var rects = output.getClientRects(),
lastRect = rects[ rects.length - 1 ],
top = lastRect.top - input.scrollTop,
left = lastRect.left+lastRect.width;
/* position the little div and see if it matches caret position :) */
xy.style.cssText = "top: "+top+"px;left: "+left+"px";
}
[1] Position du curseur dans la zone de texte, en caractères depuis le début
[2] https://developer.mozilla.org/en/docs/DOM/element.getClientRects
Modifier: cet exemple ne fonctionne que pour les zones de texte à largeur fixe. Pour le faire fonctionner avec une zone de texte redimensionnable par l'utilisateur, vous devez ajouter un écouteur d'événement à l'événement de redimensionnement et définir les dimensions #output pour qu'elles correspondent aux nouvelles dimensions #input.
Il existe une solution beaucoup plus simple pour obtenir la position du curseur en pixels que celle présentée dans les autres réponses.
Notez que cette question est une copie de celle de 2008, et j'y ai répondu ici. Je ne maintiendrai la réponse qu'à ce lien, car cette question aurait dû être fermée en double il y a des années.
J'ai cherché un plugin de coordonnées de zone de texte pour meteor-autocomplete , j'ai donc évalué les 8 plugins sur GitHub. Le gagnant est, de loin, textarea-caret-position from Component .
Un miroir <div>
est créé hors écran et stylisé exactement comme le <textarea>
. Ensuite, le texte de la zone de texte jusqu'au curseur est copié dans le div et un <span>
est inséré juste après. Ensuite, le contenu texte de la plage est défini sur le reste du texte dans la zone de texte, afin de reproduire fidèlement l'habillage dans le faux div.
Il s'agit de la seule méthode garantie pour gérer tous les cas Edge relatifs à l'habillage de longues lignes. Il est également utilisé par GitHub pour déterminer la position de son @ menu déroulant utilisateur.
JsFiddle de l'exemple de travail: http://jsfiddle.net/42zHC/2/
Fondamentalement, nous déterminons combien de colonnes tiennent dans la largeur (car ce sera monospace). Nous devons forcer les barres de défilement à toujours être présentes sinon le calcul est désactivé. Ensuite, nous divisons le nombre de colonnes qui correspondent à la largeur et nous obtenons le décalage x par caractère. Ensuite, nous définissons la hauteur de ligne sur la zone de texte. Puisque nous savons combien de caractères sont dans une ligne, nous pouvons diviser cela par le nombre de caractères et nous obtenons le numéro de ligne. Avec la hauteur de ligne, nous avons maintenant le décalage y. Ensuite, nous obtenons le scrollTop de la zone de texte et le soustrayons, de sorte qu'une fois qu'il commence à utiliser la barre de défilement, il s'affiche toujours à la bonne position.
Javascript:
$(document).ready(function () {
var cols = document.getElementById('t').cols;
var width = document.getElementById('t').clientWidth;
var height = $('textarea').css('line-height');
var pos = $('textarea').position();
$('#t').on('keyup', function () {
el = document.getElementById("t");
if (el.selectionStart) {
selection = el.selectionStart;
} else if (document.selection) {
el.focus();
var r = document.selection.createRange();
if (r == null) {
selection = 0;
}
var re = el.createTextRange(),
rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
selection = rc.text.length;
} else { selection = 0 }
var row = Math.floor((selection-1) / cols);
var col = (selection - (row * cols));
var x = Math.floor((col*(width/cols)));
var y = (parseInt(height)*row);
$('span').html("row: " + row + "<br>columns" + col + "<br>width: " + width + "<br>x: " + x +"px<br>y: " + y +"px<br>Scrolltop: "+$(this).scrollTop()).css('top',pos.top+y-$(this).scrollTop()).css('left',pos.left+x+10);
});
});
HTML:
<textarea id="t"></textarea>
<br>
<span id="tooltip" style="background:yellow"></span>
CSS:
textarea {
height: 80px;
line-height: 12px;
overflow-y:scroll;
}
span {
position: absolute;
}