Obtenir la position de décalage du curseur dans une zone de texte en pixels [dupliquer]
Cette question a déjà une réponse ici:
- Comment puis-je obtenir les coordonnées de pixels (x, y) du curseur dans les zones de texte? 3 réponses
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 impossible de trouver une solution qui fonctionne dans IE8+, Chrome et Firefox. Il semble que Tim Down travaille sur ce .
Certains autres les liens que j'ai trouvés ont beaucoup de 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 parce que je veux montrer une boîte à suggestions auto-complète à l'intérieur du textarea
en le 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
.
5 réponses
Voici une approche à l'aide de rangyinputs, longiligne et jQuery.
Il copie essentiellement le texte entier de l'intérieur du textarea
dans un div
de la même taille. J'ai mis du CSS pour m'assurer que dans chaque navigateur, le textarea
et le div
enveloppent leur contenu exactement de la même manière.
Lorsque le textarea
est cliqué, je lis à quel index de caractères le caret est positionné, puis j'insère un caret span
au même index à l'intérieur du div
. En seulement en faisant cela, j'ai fini par avoir un problème avec le caret span
en revenant à la ligne précédente si l'Utilisateur a cliqué au début d'une ligne. Pour corriger que je vérifie si le caractère précédent est un space
(ce qui permettrait une écharpe pour se produire), si c'est true
, je l'envelopper dans un span
, et j'enveloppe le mot suivant (l'une directement après la position du curseur) dans un span
. Maintenant, je compare les valeurs supérieures entre ces deux span
, si elles diffèrent, il y avait un emballage en cours, donc je suppose que le top
et le left
valeur du #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 d'implémenter un correctif pour tous car je n'ai pas le temps de le faire pour le moment, un certain nombre de choses que vous]}
-
Il ne gère pas encore les retours durs insérés avec Enter(fixe) -
le positionnement se casse lors de la saisie de plusieurs espaces dans une rangée(fixe) - je pense que les tirets permettraient également un enveloppement de contenu..
Actuellement, il fonctionne exactement de la même manière à travers les navigateurs ici sur Windows 8 avec les dernières versions de Chrome, Firefox, IE et Safari. Mes tests n'ont pas été très rigoureux.
Voici un jsFiddle.
J'espère qu'il vous aidera, à tout le moins il peut vous donner quelques idées pour construire sur.
Certaines Caractéristiques:
J'ai inclus un
ul
pour vous qui est positionné au bon endroit, et corrigé un problème Firefox où la sélectiontextarea
n'était pas remise à son endroit d'origine après les manipulations DOM.J'ai ajouté le support IE7-IE9 et corrigé le problème de sélection de mots multiples souligné dans les commentaires.
J'ai ajouté le support pour les retours durs insérés avec Entrer et plusieurs espaces dans une rangée.
J'ai corrigé un problème avec le comportement par défaut pour la ctrl+maj+flèche vers la gauche texte de la méthode de sélection.
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 avec du contenu textarea du début à la position du curseur. Textarea et le" clone " doivent avoir des CSS correspondants (propriétés de police, padding / margin / border et width). Ensuite, empilez 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/
Violon mis à jour (avec IE8 fix)
HTML:
<textarea id="input"></textarea>
<div id="output"><span></span></div>
<div id="xy"></div>
Textarea est auto-explicatif. La sortie est un élément caché auquel nous allons passer le contenu du texte et faire des mesures. Ce qui est important, c'est que nous utilisions un élément en ligne. le div" xy " est juste 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 textarea, en caractères depuis le début
[2] https://developer.mozilla.org/en/docs/DOM/element.getClientRects
Edit: cet exemple ne fonctionne que pour une largeur fixe textarea. Pour le faire fonctionner avec textarea redimensionnable par l'utilisateur, vous devez ajouter un écouteur d'événement à l'événement redimensionner et définir les dimensions # output pour correspondre aux nouvelles dimensions # input .
Il existe une solution beaucoup plus simple pour obtenir la position du curseur en pixels, que ce qui a été présenté dans les autres réponses.
Notez que cette question est un doublon de 2008, et j'ai répondu ici. Je maintiendrai seulement la réponse à ce lien, puisque cette question aurait dû être fermée en double il y a des années.
Copie de la réponse
J'ai cherché un plugin textarea caret coordinates pour meteor-autocomplete , donc j'ai évalué tous les 8 plugins sur GitHub. Le gagnant est, de loin, textarea-caret-position à partir de Composant.
Caractéristiques
- précision des pixels
- aucune dépendance
- compatibilité du navigateur: Chrome, Safari, Firefox (malgré deux bugs {[8] } Il a), IE9+; peut fonctionner mais pas testé dans Opera, IE8 ou plus
- prend en charge toute famille de polices et la taille, ainsi que le texte-transforme
- la zone de texte peut avoir rembourrage ou bordures
- pas confondu par des barres de défilement horizontales ou verticales dans la zone de texte
- prend en charge les retours durs, les tabulations (sauf sur IE) et les espaces consécutifs dans le texte
- position correcte sur les lignes plus longues que les colonnes de la zone de texte
- non "fantôme" de la position dans l'espace vide à la fin d'une ligne lors de l'emballage des mots longs
Voici une démo - http://jsfiddle.net/dandv/aFPA7/
Comment ça travaux
Un miroir {[0] } 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 du texte de la durée est définie à la suite du texte dans le textarea, afin de reproduire fidèlement l'emballage dans le faux-div.
C'est la seule méthode garantie pour gérer tous les cas de bord relatifs à l'encapsulation de longues lignes. Il est également utilisé par GitHub pour déterminer la position de son @ liste déroulante utilisateur.
Jsfiddle de l'exemple de travail: http://jsfiddle.net/42zHC/2/
Fondamentalement, nous déterminons combien de colonnes correspondent à la largeur (car ce sera monospace). Nous devons forcer les barres de défilement à toujours être là sinon le calcul est désactivé. Ensuite, nous divisons le nombre de colonnes qui correspondent à la largeur, et nous obtenons le décalage en x par caractère. Ensuite, nous définissons la hauteur de la ligne sur le textarea. Puisque nous savons combien de caractères sont dans une rangée, nous pouvons diviser cela avec 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 soustrayons cela, de sorte qu'une fois qu'il commence à utiliser la barre de défilement, il apparaît toujours dans 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;
}
Je n'ai pas pu obtenir quelque chose de similaire au travail, donc ma solution était de localiser la position du caractère du curseur dans la zone de texte, de découper le paragraphe actuel et de l'afficher à côté de la zone de texte.
En utilisant le décalage, j'ai placé un faux curseur (div
, display:inline
, 1px large, border-left: 1px solid black
) dans cette vue du texte modifiable.
De cette façon, vous pouvez créer une zone de rétroaction visuelle où vous pouvez afficher le résultat des effets (tout comme le fait stackoverflow lorsque vous écrivez une réponse).