web-dev-qa-db-fra.com

Afficher DIV à la position du curseur dans la zone de texte

Pour un de mes projets, j'adorerais fournir la complétion automatique pour une zone de texte spécifique. Similaire au fonctionnement d'intellisense/omnicomplete. Pour cela cependant, je dois trouver la position absolue du curseur afin de savoir où le DIV doit apparaître.

Il s'avère que c'est (presque j'espère) impossible à réaliser. Quelqu'un a-t-il des idées intéressantes sur la façon de résoudre ce problème?

58
Armin Ronacher

Version 2 de My Hacky Experiment

Cette nouvelle version fonctionne avec n'importe quelle police, qui peut être ajustée à la demande, et n'importe quelle taille de zone de texte.

Après avoir remarqué que certains d'entre vous essayaient toujours de faire fonctionner cela, j'ai décidé d'essayer une nouvelle approche. Mes résultats sont bien meilleurs cette fois-ci - au moins sur google chrome sur linux. Je n'ai plus de PC Windows à ma disposition, donc je ne peux que tester sur chrome/firefox sur Ubuntu. Mes résultats fonctionnent à 100% de manière cohérente sur Chrome, et disons quelque part entre 70 et 80% sur Firefox, mais je n'imagine pas qu'il serait incroyablement difficile de trouver les incohérences.

Cette nouvelle version repose sur un objet Canvas. Dans mon exemple , je montre en fait ce très canevas - juste pour que vous puissiez le voir en action, mais cela pourrait très facilement être fait avec un objet canevas caché.

C'est très certainement un hack, et je m'excuse à l'avance pour mon code plutôt jeté ensemble. À tout le moins, dans Google Chrome, cela fonctionne de manière cohérente, quelle que soit la police dans laquelle je le définis ou la taille de la zone de texte. J'ai utilisé l'exemple de Sam Saffron pour afficher les coordonnées du curseur (une div sur fond gris). J'ai également ajouté un lien "Randomize", afin que vous puissiez le voir fonctionner dans différentes tailles et styles de police/texarea et regarder la mise à jour de la position du curseur à la volée. Je recommande de regarder la démo de la page complète afin que vous puissiez mieux voir la toile de compagnon jouer.

Je vais résumer comment ça marche ...

L'idée sous-jacente est que nous essayons de redessiner la zone de texte sur un canevas, aussi près que possible. Étant donné que le navigateur utilise le même moteur de police pour les deux et pour texarea, nous pouvons utiliser la fonctionnalité de mesure des polices de canvas pour déterminer où se trouvent les choses. De là, nous pouvons utiliser les méthodes de canevas disponibles pour déterminer nos coordonnées.

Tout d'abord, nous ajustons notre toile pour qu'elle corresponde aux dimensions de la zone de texte. Ceci est entièrement à des fins visuelles car la taille de la toile ne fait pas vraiment de différence dans notre résultat. Étant donné que Canvas ne fournit pas réellement un moyen de retour à la ligne, j'ai dû conjurer (voler/emprunter/munger ensemble) un moyen de rompre les lignes pour correspondre le mieux possible à la zone de texte. C'est là que vous trouverez probablement que vous devez effectuer les ajustements les plus inter-navigateurs.

Après l'habillage Word, tout le reste est un calcul de base. Nous avons divisé les lignes en un tableau pour imiter l'habillage Word, et maintenant nous voulons parcourir ces lignes et descendre jusqu'au point où se termine notre sélection actuelle. Pour ce faire, nous comptons simplement les caractères et une fois que nous avons dépassé selection.end, nous savons que nous sommes descendus assez loin. Multipliez le nombre de lignes jusqu'à ce point par la hauteur de ligne et vous obtenez une coordonnée y.

La coordonnée x est très similaire, sauf que nous utilisons context.measureText. Tant que nous imprimons le bon nombre de caractères, cela nous donnera la largeur de la ligne en cours de dessin sur Canvas, qui se termine après le dernier caractère écrit, qui est le caractère avant le _ selection.end position.

Lorsque vous essayez de déboguer cela pour d'autres navigateurs, la chose à rechercher est où les lignes ne se cassent pas correctement. Vous verrez à certains endroits que le dernier mot d'une ligne dans le canevas peut s'être enroulé sur la zone de texte ou vice-versa. Cela a à voir avec la façon dont le navigateur gère les renvois Word. Tant que vous obtenez l'habillage dans le canevas pour correspondre à la zone de texte, votre curseur doit être correct.

Je vais coller la source ci-dessous. Vous devriez pouvoir le copier et le coller, mais si vous le faites, je vous demande de télécharger votre propre copie de jquery-fieldselection au lieu de frapper celle sur mon serveur.

J'ai également augmenté ne nouvelle démo ainsi que n violon .

Bonne chance!

<!DOCTYPE html>
<html lang="en-US">
    <head>
        <meta charset="utf-8" />
        <title>Tooltip 2</title>
        <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
        <script type="text/javascript" src="http://enobrev.info/cursor/js/jquery-fieldselection.js"></script>
        <style type="text/css">
            form {
                float: left;
                margin: 20px;
            }

            #textariffic {
                height: 400px;
                width: 300px;
                font-size: 12px;
                font-family: 'Arial';
                line-height: 12px;
            }

            #tip {
                width:5px;
                height:30px;
                background-color: #777;
                position: absolute;
                z-index:10000
            }

            #mock-text {
                float: left;
                margin: 20px;
                border: 1px inset #ccc;
            }

            /* way the hell off screen */
            .scrollbar-measure {
                width: 100px;
                height: 100px;
                overflow: scroll;
                position: absolute;
                top: -9999px;
            }

            #randomize {
                float: left;
                display: block;
            }
        </style>
        <script type="text/javascript">
            var oCanvas;
            var oTextArea;
            var $oTextArea;
            var iScrollWidth;

            $(function() {
                iScrollWidth = scrollMeasure();
                oCanvas      = document.getElementById('mock-text');
                oTextArea    = document.getElementById('textariffic');
                $oTextArea   = $(oTextArea);

                $oTextArea
                        .keyup(update)
                        .mouseup(update)
                        .scroll(update);

                $('#randomize').bind('click', randomize);

                update();
            });

            function randomize() {
                var aFonts      = ['Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Impact', 'Times New Roman', 'Verdana', 'Webdings'];
                var iFont       = Math.floor(Math.random() * aFonts.length);
                var iWidth      = Math.floor(Math.random() * 500) + 300;
                var iHeight     = Math.floor(Math.random() * 500) + 300;
                var iFontSize   = Math.floor(Math.random() * 18)  + 10;
                var iLineHeight = Math.floor(Math.random() * 18)  + 10;

                var oCSS = {
                    'font-family':  aFonts[iFont],
                    width:          iWidth + 'px',
                    height:         iHeight + 'px',
                    'font-size':    iFontSize + 'px',
                    'line-height':  iLineHeight + 'px'
                };

                console.log(oCSS);

                $oTextArea.css(oCSS);

                update();
                return false;
            }

            function showTip(x, y) {
                $('#tip').css({
                      left: x + 'px',
                      top: y + 'px'
                  });
            }

            // https://stackoverflow.com/a/11124580/14651
            // https://stackoverflow.com/a/3960916/14651

            function wordWrap(oContext, text, maxWidth) {
                var aSplit = text.split(' ');
                var aLines = [];
                var sLine  = "";

                // Split words by newlines
                var aWords = [];
                for (var i in aSplit) {
                    var aWord = aSplit[i].split('\n');
                    if (aWord.length > 1) {
                        for (var j in aWord) {
                            aWords.Push(aWord[j]);
                            aWords.Push("\n");
                        }

                        aWords.pop();
                    } else {
                        aWords.Push(aSplit[i]);
                    }
                }

                while (aWords.length > 0) {
                    var sWord = aWords[0];
                    if (sWord == "\n") {
                        aLines.Push(sLine);
                        aWords.shift();
                        sLine = "";
                    } else {
                        // Break up work longer than max width
                        var iItemWidth = oContext.measureText(sWord).width;
                        if (iItemWidth > maxWidth) {
                            var sContinuous = '';
                            var iWidth = 0;
                            while (iWidth <= maxWidth) {
                                var sNextLetter = sWord.substring(0, 1);
                                var iNextWidth  = oContext.measureText(sContinuous + sNextLetter).width;
                                if (iNextWidth <= maxWidth) {
                                    sContinuous += sNextLetter;
                                    sWord = sWord.substring(1);
                                }
                                iWidth = iNextWidth;
                            }
                            aWords.unshift(sContinuous);
                        }

                        // Extra space after Word for mozilla and ie
                        var sWithSpace = (jQuery.browser.mozilla || jQuery.browser.msie) ? ' ' : '';
                        var iNewLineWidth = oContext.measureText(sLine + sWord + sWithSpace).width;
                        if (iNewLineWidth <= maxWidth) {  // Word fits on current line to add it and carry on
                            sLine += aWords.shift() + " ";
                        } else {
                            aLines.Push(sLine);
                            sLine = "";
                        }

                        if (aWords.length === 0) {
                            aLines.Push(sLine);
                        }
                    }
                }
                return aLines;
            }

            // http://davidwalsh.name/detect-scrollbar-width
            function scrollMeasure() {
                // Create the measurement node
                var scrollDiv = document.createElement("div");
                scrollDiv.className = "scrollbar-measure";
                document.body.appendChild(scrollDiv);

                // Get the scrollbar width
                var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;

                // Delete the DIV
                document.body.removeChild(scrollDiv);

                return scrollbarWidth;
            }

            function update() {
                var oPosition  = $oTextArea.position();
                var sContent   = $oTextArea.val();
                var oSelection = $oTextArea.getSelection();

                oCanvas.width  = $oTextArea.width();
                oCanvas.height = $oTextArea.height();

                var oContext    = oCanvas.getContext("2d");
                var sFontSize   = $oTextArea.css('font-size');
                var sLineHeight = $oTextArea.css('line-height');
                var fontSize    = parseFloat(sFontSize.replace(/[^0-9.]/g, ''));
                var lineHeight  = parseFloat(sLineHeight.replace(/[^0-9.]/g, ''));
                var sFont       = [$oTextArea.css('font-weight'), sFontSize + '/' + sLineHeight, $oTextArea.css('font-family')].join(' ');

                var iSubtractScrollWidth = oTextArea.clientHeight < oTextArea.scrollHeight ? iScrollWidth : 0;

                oContext.save();
                oContext.clearRect(0, 0, oCanvas.width, oCanvas.height);
                oContext.font = sFont;
                var aLines = wordWrap(oContext, sContent, oCanvas.width - iSubtractScrollWidth);

                var x = 0;
                var y = 0;
                var iGoal = oSelection.end;
                aLines.forEach(function(sLine, i) {
                    if (iGoal > 0) {
                        oContext.fillText(sLine.substring(0, iGoal), 0, (i + 1) * lineHeight);

                        x = oContext.measureText(sLine.substring(0, iGoal + 1)).width;
                        y = i * lineHeight - oTextArea.scrollTop;

                        var iLineLength = sLine.length;
                        if (iLineLength == 0) {
                            iLineLength = 1;
                        }

                        iGoal -= iLineLength;
                    } else {
                        // after
                    }
                });
                oContext.restore();

                showTip(oPosition.left + x, oPosition.top + y);
            }

        </script>
    </head>
    <body>

        <a href="#" id="randomize">Randomize</a>

        <form id="tipper">
            <textarea id="textariffic">Aliquam urna. Nullam augue dolor, tincidunt condimentum, malesuada quis, ultrices at, arcu. Aliquam nunc pede, convallis auctor, sodales eget, aliquam eget, ligula. Proin nisi lacus, scelerisque nec, aliquam vel, dictum mattis, eros. Curabitur et neque. Fusce sollicitudin. Quisque at risus. Suspendisse potenti. Mauris nisi. Sed sed enim nec dui viverra congue. Phasellus velit sapien, porttitor vitae, blandit volutpat, interdum vel, enim. Cras sagittis bibendum neque. Proin eu est. Fusce arcu. Aliquam elit nisi, malesuada eget, dignissim sed, ultricies vel, purus. Maecenas accumsan diam id nisi.

Phasellus et nunc. Vivamus sem felis, dignissim non, lacinia id, accumsan quis, ligula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed scelerisque nulla sit amet mi. Nulla consequat, elit vitae tempus vulputate, sem libero rhoncus leo, vulputate viverra nulla purus nec turpis. Nam turpis sem, tincidunt non, congue lobortis, fermentum a, ipsum. Nulla facilisi. Aenean facilisis. Maecenas a quam eu nibh lacinia ultricies. Morbi malesuada orci quis tellus.

Sed eu leo. Donec in turpis. Donec non neque nec ante tincidunt posuere. Pellentesque blandit. Ut vehicula vestibulum risus. Maecenas commodo placerat est. Integer massa nunc, luctus at, accumsan non, pulvinar sed, odio. Pellentesque eget libero iaculis dui iaculis vehicula. Curabitur quis nulla vel felis ullamcorper varius. Sed suscipit pulvinar lectus.</textarea>

        </form>

        <div id="tip"></div>

        <canvas id="mock-text"></canvas>
    </body>
</html>

Bug

Il y a un bug dont je me souviens. Si vous placez le curseur avant la première lettre d'une ligne, il indique la "position" comme dernière lettre de la ligne précédente. Cela a à voir avec le fonctionnement de selection.end. Je ne pense pas qu'il devrait être trop difficile de rechercher ce cas et de le réparer en conséquence.


Version 1

Laissant ceci ici afin que vous puissiez voir la progression sans avoir à fouiller dans l'historique des modifications.

Ce n'est pas parfait et c'est certainement un hack, mais je l'ai fait fonctionner assez bien sur WinXP IE, FF, Safari, Chrome et Opera.

Pour autant que je sache, il n'y a aucun moyen de trouver directement le x/y d'un curseur sur n'importe quel navigateur. La méthode IE , mentionnée par Adam Bellaire est intéressante, mais malheureusement pas multi-navigateur. J'ai pensé que la meilleure chose à faire serait d'utiliser les personnages comme grille.

Malheureusement, aucune information de mesure de police n'est intégrée dans aucun des navigateurs, ce qui signifie qu'une police à espacement fixe est le seul type de police qui aura une mesure cohérente. En outre, il n'existe aucun moyen fiable de déterminer une largeur de police à partir de la hauteur de police. Au début, j'avais essayé d'utiliser un pourcentage de la hauteur, ce qui fonctionnait très bien. Ensuite, j'ai changé la taille de la police et tout est allé en enfer.

J'ai essayé une méthode pour déterminer la largeur des caractères, qui était de créer une zone de texte temporaire et de continuer à ajouter des caractères jusqu'à ce que scrollHeight (ou scrollWidth) change. Cela semble plausible, mais à mi-chemin sur cette route, j'ai réalisé que je pouvais simplement utiliser l'attribut cols sur la zone de texte et j'ai pensé qu'il y avait suffisamment de hacks dans cette épreuve pour en ajouter un autre. Cela signifie que vous ne pouvez pas définir la largeur de la zone de texte via css. Vous DEVEZ utiliser les cols pour que cela fonctionne.

Le problème suivant que j'ai rencontré est que, même lorsque vous définissez la police via css, les navigateurs signalent la police différemment. Lorsque vous ne définissez pas de police, mozilla utilise monospace par défaut, IE utilise Courier New, Opera "Courier New" (avec guillemets), Safari, 'Lucida Grand' (avec guillemets simples). Lorsque vous définissez la police sur monospace, mozilla et que vous prenez ce que vous leur donnez, Safari apparaît sous la forme -webkit-monospace et Opera reste avec "Courier New".

Alors maintenant, nous initialisons quelques vars. Assurez-vous également de définir la hauteur de votre ligne dans le CSS. Firefox rapporte la hauteur de ligne correcte, mais IE rapportait "normal" et je n'ai pas pris la peine avec les autres navigateurs. Je viens de définir la hauteur de ligne dans mon CSS et cela a résolu la différence. Je n'ont pas été testés en utilisant ems au lieu de pixels. La hauteur des caractères n'est que la taille de la police. Devrait probablement être prédéfinie dans votre css également.

En outre, un autre préréglage avant de commencer à placer des personnages - ce qui m'a vraiment fait me gratter la tête. Pour ie et mozilla, les caractères texarea sont <cols, tout le reste est <= chars. Donc Chrome peut contenir 50 caractères, mais mozilla et ie casseraient le dernier mot de la ligne.

Nous allons maintenant créer un tableau de positions de premier caractère pour chaque ligne. Nous parcourons tous les caractères de la zone de texte. S'il s'agit d'une nouvelle ligne, nous ajoutons une nouvelle position à notre tableau de lignes. S'il s'agit d'un espace, nous essayons de déterminer si le "mot" actuel s'adaptera à la ligne sur laquelle nous sommes ou s'il va être poussé à la ligne suivante. La ponctuation fait partie du "mot". Je n'ai pas testé avec des onglets, mais il y a une ligne pour ajouter 4 caractères pour un onglet.

Une fois que nous avons un tableau de positions de ligne, nous parcourons et essayons de trouver la ligne sur laquelle se trouve le curseur. Nous utilisons le "Fin" de la sélection comme curseur.

x = (position du curseur - position du premier caractère de la ligne du curseur) * largeur des caractères

y = ((ligne du curseur + 1) * hauteur de la ligne) - position de défilement

J'utilise jquery 1.2.6 , jquery-fieldselection , et jquery-dimensions

La démo: http://enobrev.info/cursor/

Et le code:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Tooltip</title>
        <script type="text/javascript" src="js/jquery-1.2.6.js"></script>
        <script type="text/javascript" src="js/jquery-fieldselection.js"></script>
        <script type="text/javascript" src="js/jquery.dimensions.js"></script>
        <style type="text/css">
            form {
                margin: 20px auto;
                width: 500px;
            }

            #textariffic {
                height: 400px;
                font-size: 12px;
                font-family: monospace;
                line-height: 15px;
            }

            #tip {
                position: absolute;
                z-index: 2;
                padding: 20px;
                border: 1px solid #000;
                background-color: #FFF;
            }
        </style>
        <script type="text/javascript">
            $(function() {
                $('textarea')
                    .keyup(update)
                    .mouseup(update)
                    .scroll(update);
            });

            function showTip(x, y) {                
                y = y + $('#tip').height();

                $('#tip').css({
                    left: x + 'px',
                    top: y + 'px'
                });
            }

            function update() {
                var oPosition = $(this).position();
                var sContent = $(this).val();

                var bGTE = jQuery.browser.mozilla || jQuery.browser.msie;

                if ($(this).css('font-family') == 'monospace'           // mozilla
                ||  $(this).css('font-family') == '-webkit-monospace'   // Safari
                ||  $(this).css('font-family') == '"Courier New"') {    // Opera
                    var lineHeight   = $(this).css('line-height').replace(/[^0-9]/g, '');
                        lineHeight   = parseFloat(lineHeight);
                    var charsPerLine = this.cols;
                    var charWidth    = parseFloat($(this).innerWidth() / charsPerLine);


                    var iChar = 0;
                    var iLines = 1;
                    var sWord = '';

                    var oSelection = $(this).getSelection();
                    var aLetters = sContent.split("");
                    var aLines = [];

                    for (var w in aLetters) {
                        if (aLetters[w] == "\n") {
                            iChar = 0;
                            aLines.Push(w);
                            sWord = '';
                        } else if (aLetters[w] == " ") {    
                            var wordLength = parseInt(sWord.length);


                            if ((bGTE && iChar + wordLength >= charsPerLine)
                            || (!bGTE && iChar + wordLength > charsPerLine)) {
                                iChar = wordLength + 1;
                                aLines.Push(w - wordLength);
                            } else {                
                                iChar += wordLength + 1; // 1 more char for the space
                            }

                            sWord = '';
                        } else if (aLetters[w] == "\t") {
                            iChar += 4;
                        } else {
                            sWord += aLetters[w];     
                        }
                    }

                    var iLine = 1;
                    for(var i in aLines) {
                        if (oSelection.end < aLines[i]) {
                            iLine = parseInt(i) - 1;
                            break;
                        }
                    }

                    if (iLine > -1) {
                        var x = parseInt(oSelection.end - aLines[iLine]) * charWidth;
                    } else {
                        var x = parseInt(oSelection.end) * charWidth;
                    }
                    var y = (iLine + 1) * lineHeight - this.scrollTop; // below line

                    showTip(oPosition.left + x, oPosition.top + y);
                }
            }

        </script>
    </head>
    <body>
        <form id="tipper">
            <textarea id="textariffic" cols="50">
Aliquam urna. Nullam augue dolor, tincidunt condimentum, malesuada quis, ultrices at, arcu. Aliquam nunc pede, convallis auctor, sodales eget, aliquam eget, ligula. Proin nisi lacus, scelerisque nec, aliquam vel, dictum mattis, eros. Curabitur et neque. Fusce sollicitudin. Quisque at risus. Suspendisse potenti. Mauris nisi. Sed sed enim nec dui viverra congue. Phasellus velit sapien, porttitor vitae, blandit volutpat, interdum vel, enim. Cras sagittis bibendum neque. Proin eu est. Fusce arcu. Aliquam elit nisi, malesuada eget, dignissim sed, ultricies vel, purus. Maecenas accumsan diam id nisi.

Phasellus et nunc. Vivamus sem felis, dignissim non, lacinia id, accumsan quis, ligula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed scelerisque nulla sit amet mi. Nulla consequat, elit vitae tempus vulputate, sem libero rhoncus leo, vulputate viverra nulla purus nec turpis. Nam turpis sem, tincidunt non, congue lobortis, fermentum a, ipsum. Nulla facilisi. Aenean facilisis. Maecenas a quam eu nibh lacinia ultricies. Morbi malesuada orci quis tellus.

Sed eu leo. Donec in turpis. Donec non neque nec ante tincidunt posuere. Pellentesque blandit. Ut vehicula vestibulum risus. Maecenas commodo placerat est. Integer massa nunc, luctus at, accumsan non, pulvinar sed, odio. Pellentesque eget libero iaculis dui iaculis vehicula. Curabitur quis nulla vel felis ullamcorper varius. Sed suscipit pulvinar lectus. 
            </textarea>

        </form>

        <p id="tip">Here I Am!!</p>
    </body>
</html>
35
enobrev

J'ai publié un sujet lié à ce problème sur un site JavaScript russe.

Si vous ne comprenez pas le russe, essayez de traduire par la version Google: http://translate.google.ru/translate?js=y&prev=_t&hl=ru&ie=UTF-8&layout=1&eotf=1&u=http://javascript .ru/forum/événements/7771-poluchit-koordinaty-kursora-v-tekstovom-pole-v-pikselyakh.html & sl = ru & tl = en

Il y a quelques problèmes de balisage dans les exemples de code dans la version traduite afin que vous puissiez lire le code dans le message russe d'origine .

L'idée est simple. Il n'y a pas de méthode simple, universelle et multi-navigateur pour obtenir la position du curseur en pixels. Franchement, il existe, mais uniquement pour Internet Explorer.

Dans d'autres navigateurs, si vous avez vraiment besoin de le calculer, vous devez ...

  • créer un DIV invisible
  • copier tous les styles et le contenu de la zone de texte dans cette DIV
  • puis insérez l'élément HTML exactement à la même position dans le texte où le curseur est dans la zone de texte
  • obtenir les coordonnées de cet élément HTML
4
prike

Je ne vais pas expliquer les problèmes liés à ce genre de choses car ils sont bien expliqués dans d'autres articles. Je vais juste indiquer une solution possible, il y a un bug mais c'est un point de départ.

Heureusement, il existe un script sur Github pour calculer la position du curseur par rapport à son conteneur, mais il nécessite jQuery. Page GitHub ici: jquery-caret-position-getter, Merci à Bevis.Zhao.

Sur cette base, j'ai implémenté le code suivant: vérifiez-le en action ici sur jsFiddle.net

<html><head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>- jsFiddle demo by mjerez</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.2.js"></script>
    <link rel="stylesheet" type="text/css" href="http://jsfiddle.net/css/normalize.css">
    <link rel="stylesheet" type="text/css" href="http://jsfiddle.net/css/result-light.css">   
    <script type="text/javascript" src="https://raw.github.com/beviz/jquery-caret-position-getter/master/jquery.caretposition.js"></script>     
    <style type="text/css">
        body{position:relative;font:normal 100% Verdana, Geneva, sans-serif;padding:10px;}
        .aux{background:#ccc;opacity: 0.5;width:50%;padding:5px;border:solid 1px #aaa;}
        .hidden{display:none}
        .show{display:block; position:absolute; top:0px; left:0px;}
    </style>
    <script type="text/javascript">//<![CDATA[ 
    $(document).keypress(function(e) {
        if ($(e.target).is('input, textarea')) {
            var key = String.fromCharCode(e.which);
            var ctrl = e.ctrlKey;
            if (ctrl) {
                var display = $("#autocomplete");
                var editArea = $('#editArea');            
                var pos = editArea.getCaretPosition();
                var offset = editArea.offset();
                // now you can use left, top(they are relative position)
                display.css({
                    left: offset.left + pos.left,
                    top:  offset.top + pos.top,
                    color : "#449"
                })
                display.toggleClass("show");
                return false;
            }
        }

    });
    window.onload = (function() {
        $("#editArea").blur(function() {
            if ($("#autocomplete").hasClass("show")) $("#autocomplete").toggleClass("show");
        })
    });
    //]]>  
    </script>
</head>
<body>
    <p>Click ctrl+space to while you write to diplay the autocmplete pannel.</p>
    </br>
    <textarea id="editArea" rows="4" cols="50"></textarea>
    </br>
    </br>
    </br>
    <div id="autocomplete" class="aux hidden ">
        <ol>
            <li>Option a</li>
            <li>Option b</li>
            <li>Option c</li>
            <li>Option d</li>
        </ol>
    </div>
</body>
4
Ma Jerez

Notez que cette question est un double de celle posée un mois plus tôt, 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.

Copie de la réponse

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 .

Caractéristiques

  • précision des pixels
  • aucune dépendance que ce soit
  • compatibilité du navigateur: Chrome, Safari, Firefox (malgré deuxbugs qu'il a), IE9 +; peut fonctionner mais non testé dans Opera, IE8 ou plus ancien
  • prend en charge n'importe quelle famille et taille de police, ainsi que les transformations de texte
  • la zone de texte peut avoir un remplissage ou des bordures arbitraires
  • pas confondu par les 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 dans la zone de texte
  • non position "fantôme" dans l'espace vide à la fin d'une ligne lors de l'habillage de mots longs

Voici une démo - http://jsfiddle.net/dandv/aFPA7/

enter image description here

Comment ça fonctionne

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.

2
Dan Dascalescu

Ce blog semble être trop proche pour répondre à la question. Je ne l'ai pas essayé moi-même, mais l'auteur dit qu'il a été testé avec FF3, Chrome, IE, Opera, Safari. Le code est activé GitHub

1
snoopy-do

corrigé ici: http://jsfiddle.net/eMwKd/4/

le seul inconvénient est que la fonction déjà fournie getCaret() se résout à la mauvaise position sur la touche enfoncée. à cet effet, le curseur rouge semble être derrière le curseur réel, sauf si vous relâchez la touche.

J'y reviendrai.

mise à jour: hm, le retour à la ligne n'est pas précis si les lignes sont trop longues.

1
lrsjng

peut-être que cela vous plaira, il indiquera la position de sélection et la position du curseur alors essayez de vérifier la minuterie pour obtenir la position automatique ou décochez pour obtenir la position en cliquant sur le bouton Get Selection

   <form>
 <p>
 <input type="button" onclick="evalOnce();" value="Get Selection">
timer:
<input id="eval_switch" type="checkbox" onclick="evalSwitchClicked(this)">
<input id="eval_time" type="text" value="200" size="6">
ms
</p>
<textarea id="code" cols="50" rows="20">01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 Sample text area. Please select above text. </textarea>
<textarea id="out" cols="50" rows="20"></textarea>
</form>
<div id="test"></div>
<script>

function Selection(textareaElement) {
this.element = textareaElement;
}
Selection.prototype.create = function() {
if (document.selection != null && this.element.selectionStart == null) {
return this._ieGetSelection();
} else {
return this._mozillaGetSelection();
}
}
Selection.prototype._mozillaGetSelection = function() {
return {
start: this.element.selectionStart,
end: this.element.selectionEnd
 };
 }
Selection.prototype._ieGetSelection = function() {
this.element.focus();
var range = document.selection.createRange();
var bookmark = range.getBookmark();
var contents = this.element.value;
var originalContents = contents;
var marker = this._createSelectionMarker();
while(contents.indexOf(marker) != -1) {
marker = this._createSelectionMarker();
 }
var parent = range.parentElement();
if (parent == null || parent.type != "textarea") {
return { start: 0, end: 0 };
}
range.text = marker + range.text + marker;
contents = this.element.value;
var result = {};
result.start = contents.indexOf(marker);
contents = contents.replace(marker, "");
result.end = contents.indexOf(marker);
this.element.value = originalContents;
range.moveToBookmark(bookmark);
range.select();
return result;
}
Selection.prototype._createSelectionMarker = function() {
return "##SELECTION_MARKER_" + Math.random() + "##";
}

var timer;
var buffer = "";
function evalSwitchClicked(e) {
if (e.checked) {
evalStart();
} else {
evalStop();
}
}
function evalStart() {
var o = document.getElementById("eval_time");
timer = setTimeout(timerHandler, o.value);
}
function evalStop() {
clearTimeout(timer);
}
function timerHandler() {
clearTimeout(timer);
var sw = document.getElementById("eval_switch");
if (sw.checked) {
evalOnce();
evalStart();
}
}
function evalOnce() {
try {
var selection = new Selection(document.getElementById("code"));
var s = selection.create();
var result = s.start + ":" + s.end;
buffer += result;
flush();
 } catch (ex) {
buffer = ex;
flush();
}
}
function getCode() {
// var s.create()
// return document.getElementById("code").value;
}
function clear() {
var out = document.getElementById("out");
out.value = "";
}
function print(str) {
buffer += str + "\n";
}
function flush() {
var out = document.getElementById("out");
out.value = buffer;
buffer = "";
 } 
</script>

regardez la démo ici: jsbin.com

0
echo_Me

Je ne connais pas de solution pour textarea mais cela fonctionne bien pour un div avec contenteditable.

Vous pouvez utiliser l'API Range. Comme ça: (oui, vous n'avez vraiment besoin que de ces 3 lignes de code)

// get active selection
var selection = window.getSelection();
// get the range (you might want to check selection.rangeCount
// to see if it's popuplated)
var range = selection.getRangeAt(0);

// will give you top, left, width, height
console.log(range.getBoundingClientRect());

Je ne suis pas sûr de la compatibilité du navigateur, mais j'ai trouvé que cela fonctionne dans les derniers Chrome, Firefox et même IE7 (je pense que j'ai testé 7, sinon c'était 9).

Vous pouvez même faire des choses "folles" comme ceci: si vous tapez "#hash" et le curseur est au dernier h, vous pouvez rechercher dans la plage actuelle le # caractère, reculer la plage de n caractères et obtenir le bounding-rect de cette plage, cela fera que la popup-div semblera 'coller' au Word .

Un inconvénient mineur est que contenteditable peut parfois être un peu bogué. Le curseur aime aller dans des endroits impossibles et vous devez maintenant faire face à la saisie HTML. Mais je suis sûr que les fournisseurs de navigateurs aborderont ces problèmes si davantage de sites commencent à les utiliser.

Une autre astuce que je peux donner est: regardez la bibliothèque rangy . Il tente d'être une bibliothèque de gammes de compatibilité croisée complète. Vous n'en avez pas besoin, mais si vous avez affaire à d'anciens navigateurs, cela pourrait valoir la peine.

0
Halcyon

Ce billet de blog semble répondre à votre question, mais malheureusement, l'auteur admet qu'il ne l'a testé qu'en IE 6.

Le DOM dans IE ne fournit pas d'informations sur la position relative en termes de caractères; cependant, il fournit des valeurs de délimitation et de décalage pour les contrôles rendus par le navigateur. Ainsi, j'ai utilisé ces valeurs pour déterminer la relative Ensuite, en utilisant JavaScript TextRange, j'ai créé un mécanisme pour travailler avec de telles mesures pour calculer la position de la ligne et de la colonne pour les polices à largeur fixe dans un TextArea donné.

Tout d'abord, les limites relatives de TextArea doivent être calculées en fonction de la taille de la police à largeur fixe utilisée. Pour ce faire, la valeur d'origine de TextArea doit être stockée dans une variable JavaScript locale et effacer la valeur. Ensuite, un TextRange est créé pour déterminer les limites supérieure et gauche du TextArea.

0
Adam Bellaire

Il y a une description d'un hack pour le décalage du curseur: coordonnées du curseur Textarea X/Y - plugin jQuery

Il sera également préférable d'utiliser l'élément div avec l'attribut contenteditable si vous pouvez utiliser les fonctionnalités html5.

0
Andrey Sbrodov