Отобразить DIV в позиции курсора в Textarea

Для моего проекта я хотел бы обеспечить автоматическое завершение для определенного текстового поля. Подобно тому, как работает intellisense/omnicomplete. Для этого, однако, я должен найти абсолютную позицию курсора, чтобы я знал, где должен выглядеть DIV.

Оказывается: этого (я надеюсь) невозможно достичь. У кого-нибудь есть какие-то опрятные идеи, как решить эту проблему?

Ответ 1

Версия 2 моего Hacky Experiment

Эта новая версия работает с любым шрифтом, который можно настроить по требованию, и любым размером текстового поля.

Заметив, что некоторые из вас все еще пытаются заставить это работать, я решил попробовать новый подход. На этот раз мои результаты FAR лучше - по крайней мере, на google chrome на linux. У меня больше нет компьютера Windows, поэтому я могу только проверить на chrome/firefox на Ubuntu. Мои результаты работают 100% последовательно в Chrome, и скажем, где-то около 70 - 80% на Firefox, но я не думаю, что было бы невероятно сложно найти несоответствия.

Эта новая версия основана на объекте Canvas. В моем пример, я на самом деле показываю это очень полотно - просто чтобы вы могли видеть его в действии, но его можно было бы легко сделать с помощью скрытого canvas.

Это, безусловно, взломать, и я заранее извиняюсь за свой довольно сложенный код. По крайней мере, в google chrome он работает последовательно, независимо от того, какой шрифт я ему устанавливаю, или размером текстового поля. Я использовал пример Сэма Шаффрона, чтобы показать координаты курсора (серо-фоновый div). Я также добавил ссылку "Рандомизировать", чтобы вы могли видеть, как она работает в разных размерах и стилях font/texarea и следит за обновлением положения курсора на лету. Я рекомендую посмотреть полноэкранную демонстрацию, чтобы вы могли лучше видеть холст холста.

Я расскажу, как это работает...

Основная идея заключается в том, что мы пытаемся перерисовать текстовую область на холсте, насколько это возможно. Поскольку браузер использует один и тот же механизм шрифтов для обеих сторон и тексарию, мы можем использовать функцию измерения шрифта холста, чтобы выяснить, где это происходит. Оттуда мы можем использовать доступные методы холста, чтобы выяснить наши координаты.

Прежде всего, мы корректируем наш холст в соответствии с размерами текстового поля. Это целиком для визуальных целей, так как размер холста на самом деле не влияет на наш результат. Поскольку Canvas на самом деле не предоставляет средства обертывания слов, мне приходилось колдовать (украсть/заимствовать/объединить вместе), чтобы разбить линии на максимально возможное соответствие текстовой области. Здесь вы, вероятно, обнаружите, что вам нужно сделать самую кросс-браузерную настройку.

После переноса слов все остальное является базовой математикой. Мы разделяем строки на массив, чтобы имитировать перенос слов, и теперь мы хотим прокрутить эти строки и дойти до тех пор, пока не закончится наш текущий выбор. Чтобы сделать это, мы просто подсчитываем символы, и как только мы превосходим selection.end, мы знаем, что мы спустились достаточно далеко. Умножьте подсчет линии до этой точки с высотой линии, и у вас есть координата y.

Координата x очень похожа, за исключением того, что мы используем context.measureText. Пока мы печатаем нужное количество символов, это даст нам ширину линии, которая тянется к Canvas, которая заканчивается после последнего выписанного символа, который является символом перед currentl selection.end положение.

При попытке отладить это для других браузеров, нужно искать, где линии не сломаются должным образом. В некоторых местах вы увидите, что последнее слово в строке в холсте может быть завернуто в текстовое поле или наоборот. Это связано с тем, как браузер обрабатывает переносы слов. Пока вы получаете обертывание в холсте в соответствии с текстовым полем, ваш курсор должен быть правильным.

Я вставлю источник ниже. Вы должны иметь возможность копировать и вставлять его, но если вы это сделаете, я прошу вас загрузить свою собственную копию jquery-fieldselection вместо того, чтобы нажимать на тот, что на моем сервере.

Я также поднял новую демонстрацию, а также скрипка.

Удачи!

<!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>

Ошибка

Там одна ошибка, которую я помню. Если вы поместите курсор перед первой буквой в строке, он отобразит "позицию" в качестве последней буквы на предыдущей строке. Это связано с тем, как работает функция selection.end. Я не думаю, что это должно быть слишком сложно найти для этого случая и исправить его соответствующим образом.


Версия 1

Оставляя это здесь, чтобы вы могли видеть прогресс без необходимости прокручивать историю изменений.

Это не идеально, и это наиболее определенно рушится, но я получил его, чтобы он работал очень хорошо в WinXP IE, FF, Safari, Chrome и Opera.

Насколько я могу судить, нет никакого способа напрямую узнать x/y курсора в любом браузере. метод IE, упоминается Adam Bellaire интересен, но, к сожалению, не кросс-браузер. Я подумал, что лучше всего использовать символы в виде сетки.

К сожалению, нет никакой информации о метрике шрифта, встроенной в любой из браузеров, что означает, что моноширинный шрифт является единственным типом шрифта, который будет иметь согласованное измерение. Кроме того, нет надежных средств определения ширины шрифта от высоты шрифта. Сначала я пробовал использовать процент высоты, который отлично работал. Затем я изменил размер шрифта, и все ушло в ад.

Я попробовал один метод для определения ширины символов, который должен был создать временную текстовую область и продолжать добавлять символы до тех пор, пока не изменится scrollHeight (или scrollWidth). Это кажется правдоподобным, но примерно наполовину вниз по этой дороге, я понял, что могу просто использовать атрибут cols на текстовом поле, и подумал, что в этом испытании достаточно хаков, чтобы добавить еще один. Это означает, что вы не можете установить ширину текстовой области через css. Вы должны использовать cols для этого.

Следующая проблема, с которой я столкнулся, заключается в том, что даже когда вы устанавливаете шрифт через css, браузеры сообщают о шрифте по-другому. Когда вы не устанавливаете шрифт, mozilla по умолчанию использует monospace, IE использует Courier New, Opera "Courier New" (с кавычками), Safari, 'Lucida Grand' (с одинарными кавычками). Когда вы установите шрифт на monospace, mozilla и т.д. Возьмите то, что вы им дадите, Safari выйдет как -webkit-monospace, а Opera останется с "Courier New".

Итак, теперь мы инициализируем некоторые вары. Обязательно установите высоту линии в css. Firefox сообщает правильную высоту строки, но IE сообщал "нормальный", и я не беспокоился о других браузерах. Я просто установил высоту строки в моем css и решил разницу. Я не тестировал с использованием ems вместо пикселей. Char высота - только размер шрифта. Вероятно, предварительно установите это в свой CSS.

Кроме того, еще одна предварительная настройка, прежде чем мы начнем размещать персонажей, что действительно заставило меня почесывать голову. Для то есть и mozilla, символы texarea < cols, все остальное - <= chars. Таким образом, Chrome может поместиться 50 символов в поперечнике, но mozilla и т.д. Сломает последнее слово с линии.

Теперь мы создадим массив позиций первого символа для каждой строки. Мы просматриваем каждый Char в текстовом поле. Если это новая строка, мы добавляем новую позицию в наш линейный массив. Если это пробел, мы пытаемся выяснить, будет ли текущее "слово" вписываться в строку, на которой мы находимся, или если она будет перенесена на следующую строку. Пунктуация считается частью "слова". Я не тестировал с вкладками, но там есть строка для добавления 4 символов для вкладки char.

Как только у нас будет массив позиций линии, мы прокручиваемся и пытаемся найти, в какой строке находится курсор. Мы используем hte "End" выбора в качестве нашего курсора.

x = (позиция курсора - позиция первого символа строки курсора) * ширина символа

y = ((строка курсора + 1) * высота строки) - положение прокрутки

Я использую jquery 1.2.6, jquery- fieldselection и jquery-dimensions

Демо: http://enobrev.info/cursor/

И код:

<?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>

Ответ 2

Я разместил тему, связанную с этой проблемой, на русском сайте JavaScript.

Если вы не понимаете, что русская попытка переводится по версии Google: http://translate.google.ru/translate?js=y&prev=_t&hl=ru&ie=UTF-8&layout=1&eotf=1&u=http://javascript.ru/forum/events/7771-poluchit-koordinaty-kursora-v-tekstovom-pole-v-pikselyakh.html&sl=ru&tl=en

Thre - это некоторые проблемы с разметкой в ​​примерах кода в переведенной версии, поэтому вы можете прочитать код в исходном сообщении в России.

Идея проста. Не существует простого, универсального и кросс-браузерного метода для получения положения курсора в пикселях. Честно говоря, есть, но только для Internet Explorer.

В других браузерах, если вам действительно нужно вычислить его, вам нужно...

  • создать невидимый DIV
  • скопировать все стили и содержимое текстового поля в DIV
  • затем вставьте элемент HTML в точно такой же позиции в тексте, где каретка находится в текстовом поле
  • получить координаты этого элемента HTML

Ответ 3

Я больше не буду объяснять проблемы, связанные с этим материалом, потому что они хорошо объясняются в других сообщениях. Просто укажем на возможное решение, у него есть ошибка, но это отправная точка.

К счастью, в Github есть сценарий для вычисления позиции каретки относительно контейнера, но для этого требуется jQuery. Страница GitHub здесь: jquery-caret-position-getter, Thanxs для Bevis.Zhao.

На основе этого я внедрил следующий код: проверить его в действии здесь, в 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

Обратите внимание, что этот вопрос является дубликатом вопроса, заданного месяцем ранее, и я ответил на него здесь. Я буду только поддерживать ответ по этой ссылке, так как этот вопрос должен был быть закрыт как повторяющийся несколько лет назад.

Копия ответа

Я искал плагин для координат textarea caret для meteor-autocomplete, поэтому я оценил все 8 плагинов на GitHub. Победитель, безусловно, textarea-caret-position от Компонент.

Функции

  • точность пикселей
  • никаких зависимостей
  • совместимость браузера: Chrome, Safari, Firefox (несмотря на два ошибки), IE9 +; может работать, но не тестироваться в Opera, IE8 или старше
  • поддерживает любое семейство и размер шрифта, а также текстовые преобразования
  • текстовая область может иметь произвольное заполнение или границы
  • не путать горизонтальные или вертикальные полосы прокрутки в текстовом поле
  • поддерживает жесткие возвраты, вкладки (кроме IE) и последовательные пробелы в тексте
  • правильное положение в строках длиннее столбцов в текстовой области
  • no позиция "призрака" в пустом пространстве в конце строки при обертке длинных слов

Здесь демо - http://jsfiddle.net/dandv/aFPA7/

enter image description here

Как это работает

Зеркало <div> создается за пределами экрана и создается точно так же, как <textarea>. Затем текст текстовой области до каретки копируется в div и a <span> вставляется сразу после нее. Затем текстовое содержимое диапазона устанавливается в остальную часть текста в текстовом поле, чтобы точно воспроизвести обертку в faux div.

Это единственный метод, который гарантированно обрабатывает все граничные случаи, связанные с переносом длинных строк. Он также используется GitHub для определения местоположения своего раскрывающегося списка @.

Ответ 5

Этот blog, похоже, слишком близко отвечает на вопрос. Я не пробовал это сам, но автор говорит, что его тестировали с помощью FF3, Chrome, IE, Opera, Safari. Код находится на GitHub

Ответ 6

зафиксировал его здесь: http://jsfiddle.net/eMwKd/4/

только недостатком является то, что уже предоставленная функция getCaret() разрешает неправильную позицию при нажатии клавиши. поэтому красный курсор, кажется, находится за реальным курсором, если вы не отпустите клавишу.

У меня будет еще один взгляд на него.

update: hm, word-wrapping неточно, если строки слишком длинные..

Ответ 7

Это сообщение в блоге, похоже, касается вашего вопроса, но, к сожалению, автор признает, что он тестировал его только в IE 6.

DOM в IE не предоставляет информацию относительно относительного положения в терминах символов; однако он предоставляет ограничивающие и смещенные значения для элементов управления, отображаемых в браузере. Таким образом, я использовал эти значения для определения относительных границ персонажа. Затем, используя JavaScript TextRange, я создал механизм для работы с такими мерами для вычисления позиции Line и Column для шрифтов фиксированной ширины в заданной TextArea.

Во-первых, относительные оценки для TextArea должны быть рассчитаны на основе размера шрифта фиксированной ширины. Для этого исходное значение TextArea должно быть сохранено в локальной переменной JavaScript и очистить значение. Затем создается TextRange для определения верхних и левых границ TextArea.

Ответ 8

Я не знаю решения для textarea, но он работает для div с contenteditable.

Вы можете использовать API Range. Например: (да, вам действительно нужны только эти 3 строки кода)

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

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

Я не уверен в совместимости с браузером, но нашел, что он работает в последних браузерах Chrome, Firefox и даже IE7 (я думаю, что я тестировал 7, иначе это было 9).

Вы можете даже делать "сумасшедшие" вещи вроде этого: если вы печатаете "#hash", а курсор находится на последнем h, вы можете посмотреть в текущем диапазоне для символа #, переместить диапазон вернитесь к символам n и получите ограниченный прямоугольник этого диапазона, это заставит popup-div, похоже, "придерживаться" слова.

Одним из незначительных недостатков является то, что contenteditable иногда может быть немного ошибкой. Курсор любит ходить в невозможные места, и теперь вам приходится иметь дело с вводом HTML. Но я уверен, что поставщики браузеров будут решать эти проблемы, поскольку больше сайтов начинают их использовать.

Еще один совет, который я могу дать: посмотрите на библиотеку rangy. Он пытается быть полнофункциональной кросс-совместимой библиотекой диапазонов. Вам это не нужно, но если вы имеете дело со старыми браузерами, это может стоить вам.

Ответ 9

Возможно, вам это понравится, он будет указывать позицию выбора и положение курсора, поэтому постарайтесь проверить таймер, чтобы получить автоматическую позицию или снять отметку, чтобы получить позицию, нажав кнопку "Получить выделение"

   <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>

Посмотрите демо здесь: jsbin.com

Ответ 10

Существует описание одного взлома для смещения каретки: Координаты каретки Textarea X/Y - плагин jQuery

Также лучше использовать элемент div с атрибутом contenteditable, если вы можете использовать функции html5.

Ответ 11

Как насчет добавления элемента span в клонирование div и установки фальшивого курсора на основе этих смещений диапазона? Я обновил вашу скрипку здесь. Также здесь только бит JS

// http://stackoverflow.com/info/263743/how-to-get-caret-position-in-textarea
var map = [];
var pan = '<span>|</span>'

//found @ http://davidwalsh.name/detect-scrollbar-width

function getScrollbarWidth() {
    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 getCaret(el) {
    if (el.selectionStart) {
        return el.selectionStart;
    } else if (document.selection) {
        el.focus();

        var r = document.selection.createRange();
        if (r == null) {
            return 0;
        }

        var re = el.createTextRange(),
            rc = re.duplicate();
        re.moveToBookmark(r.getBookmark());
        rc.setEndPoint('EndToStart', re);

        return rc.text.length;
    }
    return 0;
}


$(function() {
    var span = $('#pos span');
    var textarea = $('textarea');

    var note = $('#note');

    css = getComputedStyle(document.getElementById('textarea'));
    try {
        for (i in css) note.css(css[i]) && (css[i] != 'width' && css[i] != 'height') && note.css(css[i], css.getPropertyValue(css[i]));
    } catch (e) {}

    note.css('max-width', '300px');
    document.getElementById('note').style.visibility = 'hidden';
    var height = note.height();
    var fakeCursor, hidePrompt;

    textarea.on('keyup click', function(e) {
        if (document.getElementById('textarea').scrollHeight > 100) {
            note.css('max-width', 300 - getScrollbarWidth());
        }

        var pos = getCaret(textarea[0]);

        note.text(textarea.val().substring(0, pos));
        $(pan).appendTo(note);
        span.text(pos);

        if (hidePrompt) {
            hidePrompt.remove();
        }
        if (fakeCursor) {
            fakeCursor.remove();
        }

        fakeCursor = $("<div style='width:5px;height:30px;background-color: #777;position: absolute;z-index:10000'>&nbsp;</div>");

        fakeCursor.css('opacity', 0.5);
        fakeCursor.css('left', $('#note span').offset().left + 'px');
        fakeCursor.css('top', textarea.offset().top + note.height() - (30 + textarea.scrollTop()) + 'px');

        hidePrompt = fakeCursor.clone();
        hidePrompt.css({
            'width': '2px',
            'background-color': 'white',
            'z-index': '1000',
            'opacity': '1'
        });

        hidePrompt.appendTo(textarea.parent());
        fakeCursor.appendTo(textarea.parent());



        return true;
    });
});

ОБНОВЛЕНИЕ. Я вижу, что есть ошибка, если первая строка не содержит жестких разрывов строк, но если она работает, она работает хорошо.