Получить курсор или текстовую позицию в пикселях для элемента ввода - PullRequest
17 голосов
/ 03 августа 2011

IE позволяет мне создать текстовый диапазон во входном элементе, после чего я могу вызвать getBoundingClientRect() и получить позицию в пикселях определенного символа или курсора / каретки. Есть ли способ получить положение определенного символа в пикселях в других браузерах?

var input = $("#myInput")[0];
var pixelPosition = null;
if (input.createTextRange)
{
    var range = input.createTextRange();
    range.moveStart("character", 6);
    pixelPosition = range.getBoundingClientRect();
}
else
{
    // Is there any way to create a range on an input's value?
}

Я использую jQuery, но сомневаюсь, что он сможет решить мою ситуацию. Я ожидаю чистого решения JavaScript, если оно есть, но ответы jQuery приветствуются.

Ответы [ 4 ]

16 голосов
/ 31 октября 2011

Демо Я написал функцию, которая ведет себя как ожидалось.Очень подробную демонстрационную панель можно найти здесь: Fiddle: http://jsfiddle.net/56Rep/5/
Интерфейс в демоверсии не требует пояснений.

Функциональность, запрошенная в вопросе, будет реализована вмоя функция заключается в следующем:var pixelPosition = getTextBoundingRect(input, 6)

Зависимости функций Обновлено : функция является чистым JavaScript и не зависит от какого-либо плагина или фреймворка!Функция предполагает, что метод getBoundingClientRect существует.Текстовые диапазоны используются, когда они поддерживаются.В противном случае функциональность достигается с помощью моей функциональной логики.

Функциональная логика Сам код содержит несколько комментариев.Эта часть углубляется в детали.

  1. Один временный <div> контейнер создан.
  2. 1 - 3 <span> элементы созданы.Каждый диапазон содержит часть значения ввода (смещение от 0 до selectionStart, selectionStart до selectionEnd, selectionEnd до конца строки, только второй диапазон имеет значение).
  3. Несколько значимых стилейсвойства из элемента input копируются в эти теги <div> и <span>.Только существенные свойства стиля копируются.Например, color не копируется, потому что это никак не влияет на смещения символа. # 1
  4. <div> располагается в точном положении текстовый узел (значение ввода).Границы и отступы учитываются, чтобы убедиться, что временное <div> правильно позиционировано .
  5. Создана переменная, которая содержит возвращаемое значение div.getBoundingClientRect().
  6. Временное <div> удалено, , если параметр debug не установлен в значение true.
  7. Функция возвращаетClientRect объект.Для получения дополнительной информации об этом объекте см. эту страницу . demo также показывает список свойств: top, left, right, bottom, height и width.

# 1 : getBoundingClientRect() (и некоторые второстепенные свойства) используется для определения положения элемента ввода.Затем добавляются отступы и ширина границы, чтобы получить реальную позицию текстового узла.

Известные проблемы Единственный случай несоответствия произошел, когда getComputedStyle вернул неправильное значение для font-family: когда страница не определила свойство font-family, computedStyle возвращает неправильное значение (даже Firebug испытывает эту проблему; среда:Linux, Firefox 3.6.23, шрифт "Sans Serif").

Как видно из демонстрации, расположение иногда слегка смещено (почти ноль, всегда меньше 1 пикселя).

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

Код

// @author Rob W       http://stackoverflow.com/users/938089/rob-w
// @name               getTextBoundingRect
// @param input          Required HTMLElement with `value` attribute
// @param selectionStart Optional number: Start offset. Default 0
// @param selectionEnd   Optional number: End offset. Default selectionStart
// @param debug          Optional boolean. If true, the created test layer
//                         will not be removed.
function getTextBoundingRect(input, selectionStart, selectionEnd, debug) {
    // Basic parameter validation
    if(!input || !('value' in input)) return input;
    if(typeof selectionStart == "string") selectionStart = parseFloat(selectionStart);
    if(typeof selectionStart != "number" || isNaN(selectionStart)) {
        selectionStart = 0;
    }
    if(selectionStart < 0) selectionStart = 0;
    else selectionStart = Math.min(input.value.length, selectionStart);
    if(typeof selectionEnd == "string") selectionEnd = parseFloat(selectionEnd);
    if(typeof selectionEnd != "number" || isNaN(selectionEnd) || selectionEnd < selectionStart) {
        selectionEnd = selectionStart;
    }
    if (selectionEnd < 0) selectionEnd = 0;
    else selectionEnd = Math.min(input.value.length, selectionEnd);

    // If available (thus IE), use the createTextRange method
    if (typeof input.createTextRange == "function") {
        var range = input.createTextRange();
        range.collapse(true);
        range.moveStart('character', selectionStart);
        range.moveEnd('character', selectionEnd - selectionStart);
        return range.getBoundingClientRect();
    }
    // createTextRange is not supported, create a fake text range
    var offset = getInputOffset(),
        topPos = offset.top,
        leftPos = offset.left,
        width = getInputCSS('width', true),
        height = getInputCSS('height', true);

        // Styles to simulate a node in an input field
    var cssDefaultStyles = "white-space:pre;padding:0;margin:0;",
        listOfModifiers = ['direction', 'font-family', 'font-size', 'font-size-adjust', 'font-variant', 'font-weight', 'font-style', 'letter-spacing', 'line-height', 'text-align', 'text-indent', 'text-transform', 'word-wrap', 'word-spacing'];

    topPos += getInputCSS('padding-top', true);
    topPos += getInputCSS('border-top-width', true);
    leftPos += getInputCSS('padding-left', true);
    leftPos += getInputCSS('border-left-width', true);
    leftPos += 1; //Seems to be necessary

    for (var i=0; i<listOfModifiers.length; i++) {
        var property = listOfModifiers[i];
        cssDefaultStyles += property + ':' + getInputCSS(property) +';';
    }
    // End of CSS variable checks

    var text = input.value,
        textLen = text.length,
        fakeClone = document.createElement("div");
    if(selectionStart > 0) appendPart(0, selectionStart);
    var fakeRange = appendPart(selectionStart, selectionEnd);
    if(textLen > selectionEnd) appendPart(selectionEnd, textLen);

    // Styles to inherit the font styles of the element
    fakeClone.style.cssText = cssDefaultStyles;

    // Styles to position the text node at the desired position
    fakeClone.style.position = "absolute";
    fakeClone.style.top = topPos + "px";
    fakeClone.style.left = leftPos + "px";
    fakeClone.style.width = width + "px";
    fakeClone.style.height = height + "px";
    document.body.appendChild(fakeClone);
    var returnValue = fakeRange.getBoundingClientRect(); //Get rect

    if (!debug) fakeClone.parentNode.removeChild(fakeClone); //Remove temp
    return returnValue;

    // Local functions for readability of the previous code
    function appendPart(start, end){
        var span = document.createElement("span");
        span.style.cssText = cssDefaultStyles; //Force styles to prevent unexpected results
        span.textContent = text.substring(start, end);
        fakeClone.appendChild(span);
        return span;
    }
    // Computing offset position
    function getInputOffset(){
        var body = document.body,
            win = document.defaultView,
            docElem = document.documentElement,
            box = document.createElement('div');
        box.style.paddingLeft = box.style.width = "1px";
        body.appendChild(box);
        var isBoxModel = box.offsetWidth == 2;
        body.removeChild(box);
        box = input.getBoundingClientRect();
        var clientTop  = docElem.clientTop  || body.clientTop  || 0,
            clientLeft = docElem.clientLeft || body.clientLeft || 0,
            scrollTop  = win.pageYOffset || isBoxModel && docElem.scrollTop  || body.scrollTop,
            scrollLeft = win.pageXOffset || isBoxModel && docElem.scrollLeft || body.scrollLeft;
        return {
            top : box.top  + scrollTop  - clientTop,
            left: box.left + scrollLeft - clientLeft};
    }
    function getInputCSS(prop, isnumber){
        var val = document.defaultView.getComputedStyle(input, null).getPropertyValue(prop);
        return isnumber ? parseFloat(val) : val;
    }
}
3 голосов
/ 02 мая 2014

Май 2014 обновление: Невероятно легкий и надежный textarea-caret-position Библиотека компонентов теперь поддерживает также <input type="text">, что делает все остальные ответы устаревшими.

Демонстрация доступна на http://jsfiddle.net/dandv/aFPA7/

Спасибо Робу В. за вдохновение в поддержку RTL.

2 голосов
/ 31 октября 2011

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

function getInputTextPosition(input, charOffset)
{
    var pixelPosition = null;
    if (input.createTextRange)
    {
        var range = input.createTextRange();
        range.moveStart("character", charOffset);
        pixelPosition = range.getBoundingClientRect();
    }
    else
    {
        var text = input.value.substr(0, charOffset).replace(/ $/, "\xa0");
        var sizer = $("#sizer").insertBefore(input).text(text);
        pixelPosition = sizer.offset();
        pixelPosition.left += sizer.width();
        if (!text) sizer.text("."); // for computing height. An empty span returns 0
        pixelPosition.bottom = pixelPosition.top + sizer.height();
    }
    return pixelPosition
}

CSS для моего sizer span:

#sizer
{
    position: absolute;
    display: inline-block;
    visibility: hidden;
    margin: 3px; /* simulate padding and border without affecting height and width */
    font-family: "segoe ui", Verdana, Arial, Sans-Serif;
    font-size: 12px;
}
1 голос
/ 16 декабря 2016

2016 обновление: Более современным решением на основе HTML5 было бы использование свойства contenteditable.

<div contenteditable="true">  <!-- behaves as input -->
   Block of regular text, and <span id='interest'>text of interest</span>
</div>

Теперь мы можем найти положение диапазона, используя jquery offset().И, конечно же, теги <span> можно вставлять заранее или динамически.

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