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

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

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

Было бы лучше подумать о том, чтобы отказаться от textarea и вместо этого использовать contenteditable div? Просто выбросил это там.

Cade Roux 26.04.2012 22:27
Баунти, the jsfiddle you are supposed to fix is: jsfiddle.net/eMwKd/1
Sam Saffron 24.10.2012 11:07

Вы смотрели решения для редактирования js-кода (я не знаю, есть ли в них автозаполнение)?

undefined 26.10.2012 01:34

приближаемся на хроме: jsbin.com/egadoj/1/edit

Sam Saffron 28.10.2012 03:00

@xyu, да, но я боюсь, что здесь их считают тяжеловесными.

Sam Saffron 28.10.2012 03:01

Опубликовал потенциальное решение, которое работает намного лучше (комментируя, чтобы уведомить людей, которые смотрят эту ветку)

enobrev 28.10.2012 13:07

@SamSaffron: Я имел в виду посмотреть, как они отображают всплывающее окно автозаполнения.

undefined 28.10.2012 16:25

Редакторы на основе @xyu dom (codemirror / ace) могут использовать методы вставки dom для определения местоположения, это довольно просто.

Sam Saffron 30.10.2012 17:05
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
59
8
27 753
10

Ответы 10

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

The DOM in IE does not provide information regarding relative position in terms of characters; however, it does provide bounding and offset values for browser-rendered controls. Thus, I used these values to determine the relative bounds of a character. Then, using the JavaScript TextRange, I created a mechanism for working with such measures to calculate the Line and Column position for fixed-width fonts within a given TextArea.

First, the relative bounds for the TextArea must be calculated based upon the size of the fixed-width font used. To do this, the original value of the TextArea must be stored in a local JavaScript variable and clear the value. Then, a TextRange is created to determine the Top and Left bounds of the TextArea.

Я разместил тему, связанную с этой проблемой, на русском 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- текстовом-полюс-в-пикселях.html & sl = ru & tl = en

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

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

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

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

Это общий алгоритм, но есть разные нюансы, когда дело касается совместимость с браузером. Команда Component.io собрала простой кроссбраузерный плагин, который работает во всех крайних случаях и не требует jQuery.

Dan Dascalescu 17.03.2014 16:24

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

это именно то, что вам нужно, он использует структуру prototype.js и выполняет свою работу. код написан для события щелкнуть, поэтому вы можете добавить к нему событие onkeyup, и готово. Я также тестировал это в IE, Chrome, Firefox, Safari, Opera, и он работает абсолютно. Надеюсь, вы найдете это полезным.

Mahyar 30.10.2012 16:47

проект на github - хорошее усилие, но далеко не пуленепробиваемое @enobrev имеет гораздо более жесткую реализацию. В частности, код на github не вводит слово-разрыв для переноса слов, он не выполняет замену вставки пробелов должным образом (несколько пробелов отключают его).

Sam Saffron 30.10.2012 17:21

Недавно я назначил автора этого плагина осуждать это в пользу textarea-caret-position. @SamSaffron: Я не видел любые репозитории такого рода на GitHub enobrev?

Dan Dascalescu 17.03.2014 16:33

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

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

// 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.info(range.getBoundingClientRect());

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

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

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

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

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

К счастью, на Github есть скрипт для вычисления позиции курсора относительно контейнера, но для этого требуется jQuery. Страница GitHub здесь: jquery-каретка-позиция-геттер, Спасибо Бевису, Чжао.

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

Скрипт Бевиса - глючит и больше не обслуживается. Я знаю это, потому что оценил все восемь плагинов для получения координат текстового поля на GitHub. На сегодняшний день лучший плагин - textarea-caret-position в компоненте.io. Намного проще, кроссбраузерно и не требует jQuery.

Dan Dascalescu 17.03.2014 16:30

исправил здесь: http://jsfiddle.net/eMwKd/4/

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

Я еще раз посмотрю на это.

обновление: хм, перенос слов не точен, если строки слишком длинные ..

это самое близкое, что я могу получить: jsbin.com/egadoj/1/edit волосы от решенного

Sam Saffron 28.10.2012 03:00

приближается jsbin.com/egadoj/4

Sam Saffron 28.10.2012 04:28

это довольно хорошо, вам нужно только проверить, находитесь ли вы в конце строки (в этом случае предварительный просмотр не работает)

lrsjng 28.10.2012 04:36

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

lrsjng 28.10.2012 04:38

Это действительно непростая проблема, но команда Component.io собрала плагин textarea-caret-position, который в значительной степени идеален (без зависимостей, кроссбраузерность, только 80 строк кода, обрабатывает полосы прокрутки, перенос, любую комбинацию шрифтов и т. д.)

Dan Dascalescu 17.03.2014 16:36

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

   <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

это просто местоположение курсора, довольно просто решить кроссбраузерность и очень хорошо документировано

Sam Saffron 30.10.2012 17:04

Вот описание одной хитрости для смещения каретки: Координаты каретки Textarea X / Y - плагин jQuery

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

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

// http://stackoverflow.com/questions/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;
    });
});

Взаимодействие с другими людьми ОБНОВИТЬ: Я вижу, что есть ошибка, если первая строка не содержит жестких разрывов строк, но если это так, похоже, работает хорошо.

Мне понравилась информация о нахождении ширины полос прокрутки. Есть обычный ошибка, однако, что большинство библиотек координат каретки имеют. component.io плагин textarea-caret-position решает эту проблему, к тому же не требует jQuery, работает в Chrome, FF и IE и занимает всего 80 строк кода.

Dan Dascalescu 17.03.2014 16:47

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

Копия ответа

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

Функции

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

Вот демо - http://jsfiddle.net/dandv/aFPA7/

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

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

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

скопируйте-вставьте эту строку «iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii‌ iiiiiiiiiiiiiiiiiii» и наслаждайтесь результатом. Он полностью не работает в Firefox, а также есть проблема в Chrome.

daliusd 03.01.2019 10:35

Другие вопросы по теме