Подтвердить что ты не робот

Получить положение смещения каретки в текстовом поле в пикселях

В моем проекте я пытаюсь получить позицию смещения каретки в textarea в пикселях. Это можно сделать?

Прежде чем просить здесь, я просмотрел многие ссылки, особенно Tim Down, но я не смог найти решение, которое работает в IE8 +, Chrome и Firefox. Кажется, Tim Down работает над этим.

Некоторые другие ссылки, которые я нашел, имеют много проблем, таких как отсутствие верхнего смещения каретки положение.

Я пытаюсь получить позицию смещения каретки, потому что я хочу показать окно запроса автозаполнения внутри textarea, расположив его на основе позиции смещения каретки.

PS: Я не могу использовать contenteditable div, потому что написал много кода, относящегося к textarea.

4b9b3361

Ответ 1

Здесь используется подход rangyinputs, rangy и jQuery.

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

Когда нажата кнопка textarea, я читаю, в каком символе указывается каретка, затем вставляем каретку span с тем же индексом внутри div. Только делая это, у меня возникла проблема с каретой span, возвращающейся к предыдущей строке, если пользователь нажал в начале строки. Чтобы исправить это, я проверяю, является ли предыдущий символ space (что позволит сделать обертку), если это true, я завершаю его в span, и я завершаю следующее слово (прямое после позиции каретки) в span. Теперь я сравниваю верхние значения между этими двумя span 's, если они отличаются друг от друга, происходит некоторая обертка, поэтому я предполагаю, что значения top и left #nextword span эквивалентны в положение каретки.

Этот подход все еще может быть улучшен, я уверен, что я не думал обо всем, что могло бы пойти не так, и даже если бы у меня было, то я не потрудился внедрить исправление для всех, поскольку я У меня есть время, чтобы сделать это в данный момент, несколько вещей, которые вам нужно будет посмотреть:

  • он еще не обрабатывает жесткие возвраты, вставленные с помощью Enter (исправлено)
  • позиционирование прерывается при вводе нескольких пробелов в строке (исправлено)
  • Я думаю, что дефисы позволят обертывать содержимое, а также.

В настоящее время он работает точно так же в браузерах здесь, в Windows 8, с последними версиями Chrome, Firefox, IE и Safari. Однако мои тесты не были очень строгими.

Здесь jsFiddle.

Я надеюсь, что это поможет вам, по крайней мере, это может дать вам некоторые идеи, чтобы опираться.

Некоторые функции:

  • Я включил ul для вас, который позиционируется в нужном месте, и исправил проблему с Firefox, когда выбор textarea не возвращался обратно в исходное место после манипуляций с DOM.

  • Я добавил поддержку IE7 - IE9 и исправил проблему выбора нескольких слов, указанную в комментариях.

  • Я добавил поддержку жестких возвратов, вставленных с помощью Enter и нескольких пробелов в строке.

  • Я исправил проблему с поведением по умолчанию для метода выбора текста ctrl + shift + left arrow.

JavaScript

function getTextAreaXandY() {

    // Don't do anything if key pressed is left arrow
    if (e.which == 37) return;     

    // Save selection start
    var selection = $(this).getSelection();
    var index = selection.start;

    // Copy text to div
    $(this).blur();
    $("div").text($(this).val());

    // Get current character
    $(this).setSelection(index, index + 1);
    currentcharacter = $(this).getSelection().text;

    // Get previous character
    $(this).setSelection(index - 1, index)
    previouscharacter = $(this).getSelection().text;

    var start, endchar;
    var end = 0;
    var range = rangy.createRange();

    // If current or previous character is a space or a line break, find the next word and wrap it in a span
    var linebreak = previouscharacter.match(/(\r\n|\n|\r)/gm) == undefined ? false : true;

    if (previouscharacter == ' ' || currentcharacter == ' ' || linebreak) {
        i = index + 1; // Start at the end of the current space        
        while (endchar != ' ' && end < $(this).val().length) {
            i++;
            $(this).setSelection(i, i + 1)
            var sel = $(this).getSelection();
            endchar = sel.text;
            end = sel.start;
        }

        range.setStart($("div")[0].childNodes[0], index);
        range.setEnd($("div")[0].childNodes[0], end);
        var nextword = range.toHtml();
        range.deleteContents();
        var position = $("<span id='nextword'>" + nextword + "</span>")[0];
        range.insertNode(position);
        var nextwordtop = $("#nextword").position().top;
    }

    // Insert `#caret` at the position of the caret
    range.setStart($("div")[0].childNodes[0], index);
    var caret = $("<span id='caret'></span>")[0];
    range.insertNode(caret);
    var carettop = $("#caret").position().top;

    // If preceding character is a space, wrap it in a span
    if (previouscharacter == ' ') {
        range.setStart($("div")[0].childNodes[0], index - 1);
        range.setEnd($("div")[0].childNodes[0], index);
        var prevchar = $("<span id='prevchar'></span>")[0];
        range.insertNode(prevchar);
        var prevchartop = $("#prevchar").position().top;
    }

    // Set textarea selection back to selection start
    $(this).focus();
    $(this).setSelection(index, selection.end);

    // If the top value of the previous character span is not equal to the top value of the next word,
    // there must have been some wrapping going on, the previous character was a space, so the wrapping
    // would have occured after this space, its safe to assume that the left and top value of `#nextword`
    // indicate the caret position
    if (prevchartop != undefined && prevchartop != nextwordtop) {
        $("label").text('X: ' + $("#nextword").position().left + 'px, Y: ' + $("#nextword").position().top);
        $('ul').css('left', ($("#nextword").position().left) + 'px');
        $('ul').css('top', ($("#nextword").position().top + 13) + 'px');
    }
    // if not, then there was no wrapping, we can take the left and the top value from `#caret`    
    else {
        $("label").text('X: ' + $("#caret").position().left + 'px, Y: ' + $("#caret").position().top);
        $('ul').css('left', ($("#caret").position().left) + 'px');
        $('ul').css('top', ($("#caret").position().top + 14) + 'px');
    }

    $('ul').css('display', 'block');
}

$("textarea").click(getTextAreaXandY);
$("textarea").keyup(getTextAreaXandY);

HTML

<div></div>
<textarea>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</textarea>
<label></label>
<ul>
    <li>Why don't you type this..</li>
</ul>

CSS

body {
    font-family: Verdana;
    font-size: 12px;
    line-height: 14px;
}
textarea, div {
    font-family: Verdana;
    font-size: 12px;
    line-height: 14px;
    width: 300px;
    display: block;
    overflow: hidden;
    border: 1px solid black;
    padding: 0;
    margin: 0;
    resize: none;
    min-height: 300px;
    position: absolute;
    -moz-box-sizing: border-box;
    white-space: pre-wrap;
}
span {
    display: inline-block;
    height: 14px;
    position: relative;
}
span#caret {
    display: inline;
}
label {
    display: block;
    margin-left: 320px;
}
ul {
    padding: 0px;
    margin: 9px;
    position: absolute;
    z-index: 999;
    border: 1px solid #000;
    background-color: #FFF;
    list-style-type:none;
    display: none;
}
@media screen and (-webkit-min-device-pixel-ratio:0) {
    span {
        white-space: pre-wrap;
    }
}
div {
    /* Firefox wrapping fix */
    -moz-padding-end: 1.5px;
    -moz-padding-start: 1.5px;
    /* IE8/IE9 wrapping fix */
    padding-right: 5px\0/;
    width: 295px\0/;
}
span#caret
{
    display: inline-block\0/;
}

Ответ 2

Вы можете создать отдельный (невидимый) элемент и заполнить его содержимым textarea от начала до позиции курсора. Textarea и "клон" должны иметь соответствующий CSS (свойства шрифта, отступы/край/граница и ширина). Затем соедините эти элементы друг с другом.

Позвольте мне начать с рабочего примера, затем пройдите через код: http://jsfiddle.net/g7rBk/

Обновлено Fiddle (с исправлением IE8)

HTML:

<textarea id="input"></textarea>
<div id="output"><span></span></div>
<div id="xy"></div>

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

CSS

/* identical styling to match the dimensions and position of textarea and its "clone"
*/
#input, #output {
    position:absolute;
    top:0;
    left:0;
    font:14px/1 monospace;
    padding:5px;
    border:1px solid #999;
    white-space:pre;
    margin:0;
    background:transparent;
    width:300px;
    max-width:300px;
}
/* make sure the textarea isn't obscured by clone */
#input { 
    z-index:2;
    min-height:200px;
}

#output { 
    border-color:transparent; 
}

/* hide the span visually using opacity (not display:none), so it still measurable; make it break long words inside like textarea does. */
#output span {
    opacity:0;
    word-wrap: break-word;
    overflow-wrap: break-word;
}
/* the cursor position indicator */
#xy { 
    position:absolute; 
    width:4px;
    height:4px;
    background:#f00;
}

JavaScript:

/* get references to DOM nodes we'll use */
var input = document.getElementById('input'),
    output = document.getElementById('output').firstChild,
    position = document.getElementById('position'),

/* And finally, here it goes: */
    update = function(){
         /* Fill the clone with textarea content from start to the position of the caret. You may need to expand here to support older IE [1]. The replace /\n$/ is necessary to get position when cursor is at the beginning of empty new line.
          */
         output.innerHTML = input.value.substr( 0, input.selectionStart ).replace(/\n$/,"\n\001");

        /* the fun part! 
           We use an inline element, so getClientRects[2] will return a collection of rectangles wrapping each line of text.
           We only need the position of the last rectangle.
         */
        var rects = output.getClientRects(),
            lastRect = rects[ rects.length - 1 ],
            top = lastRect.top - input.scrollTop,
            left = lastRect.left+lastRect.width;
        /* position the little div and see if it matches caret position :) */
        xy.style.cssText = "top: "+top+"px;left: "+left+"px";
    }

[1] Позиция Caret в текстовом поле, в символах с самого начала

[2] https://developer.mozilla.org/en/docs/DOM/element.getClientRects

Изменить: этот пример работает только для текстовой области с фиксированной шириной. Чтобы заставить его работать с изменяемой пользователем текстовой областью, вам нужно добавить прослушиватель событий к событию изменения размера и установить размеры #output в соответствии с новыми размерами #input.

Ответ 3

Там гораздо более простое решение для получения позиции каретки в пикселях, чем то, что было представлено в других ответах.

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

Копия ответа

Я искал плагин для координат 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 для определения местоположения своего раскрывающегося списка @.

Ответ 4

JsFiddle рабочего примера: http://jsfiddle.net/42zHC/2/

В принципе, мы выясняем, сколько столбцов соответствует ширине (поскольку это будет моноширинность). Мы вынуждены вынуждать полосы прокрутки всегда быть там, иначе расчет будет выключен. Затем мы делим количество столбцов, которые соответствуют ширине, и получаем смещение x на символ. Затем мы устанавливаем высоту строки в текстовом поле. Поскольку мы знаем, сколько символов в строке, мы можем разделить это число с количеством символов, и мы получим номер строки. С высотой линии мы теперь имеем смещение y. Затем мы получаем scrollTop текстового поля и вычитаем это, так что, как только он начнет использовать полосу прокрутки, он все еще отображается в правильной позиции.

Javascript:

$(document).ready(function () {
  var cols = document.getElementById('t').cols;
  var width = document.getElementById('t').clientWidth;
  var height = $('textarea').css('line-height');
  var pos = $('textarea').position();
  $('#t').on('keyup', function () {
    el = document.getElementById("t");
    if (el.selectionStart) { 
        selection = el.selectionStart; 
      } else if (document.selection) { 
        el.focus(); 
        var r = document.selection.createRange(); 
        if (r == null) { 
           selection = 0; 
        } 
        var re = el.createTextRange(), 
        rc = re.duplicate(); 
        re.moveToBookmark(r.getBookmark()); 
        rc.setEndPoint('EndToStart', re); 
        selection = rc.text.length; 
      } else { selection = 0 }
    var row = Math.floor((selection-1) / cols);
    var col = (selection - (row * cols));
    var x = Math.floor((col*(width/cols)));
    var y = (parseInt(height)*row);
    $('span').html("row: " + row + "<br>columns" + col + "<br>width: " + width + "<br>x: " + x +"px<br>y: " + y +"px<br>Scrolltop: "+$(this).scrollTop()).css('top',pos.top+y-$(this).scrollTop()).css('left',pos.left+x+10);
  });
});

HTML:

<textarea id="t"></textarea>
<br>
<span id="tooltip" style="background:yellow"></span>

CSS

textarea {
  height: 80px;
  line-height: 12px;
  overflow-y:scroll;
}
span {
  position: absolute;
}

Ответ 5

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

Используя смещение, я разместил в этом представлении редактируемого текста фальшивый курсор (div, display:inline, 1px wide, border-left: 1px solid black).

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