Получить курсор или текстовую позицию в пикселях для элемента ввода
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 value?
}
Я использую jQuery, но я сомневаюсь, что он сможет решить мою ситуацию. Я ожидаю, что чистое решение для JavaScript, если оно есть, но ответы jQuery приветствуются.
Ответы
Ответ 1
В итоге я создал скрытый макетный ввод из диапазона, который был полностью и точно подобран для ввода. Я установил текст этого диапазона в значение ввода до символа, чью позицию я хочу найти. Я вставляю диапазон перед входом и получаю его смещение:
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:
#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;
}
Ответ 2
Демо
Я написал функцию, которая ведет себя так, как ожидалось. Подробную демонстрационную панель можно найти здесь: Fiddle: http://jsfiddle.net/56Rep/5/
Интерфейс в демонстрации не требует пояснений.
Функциональность, запрошенная в вопросе, будет реализована в моей функции следующим образом:
var pixelPosition = getTextBoundingRect(input, 6)
Зависимости функций
Обновлено: функция является чистым JavaScript и не зависит от какого-либо плагина или фреймворка!
Функция предполагает, что существует метод getBoundingClientRect
. Текстовые диапазоны используются, когда они поддерживаются. В противном случае функциональность будет достигнута с использованием моей функциональной логики.
Функциональная логика
Сам код содержит несколько комментариев. Эта часть идет более подробно.
- Создается контейнер временный
<div>
.
Создаются элементы - 1 - 3
<span>
. Каждый диапазон содержит часть входного значения (смещения 0 до selectionStart
, selectionStart
до selectionEnd
, selectionEnd
до конца строки, только второй диапазон является средним).
- Несколько важных свойств стиля из входного элемента копируются в теги
<div>
и <span>
. Скопированы только значительные свойства стиля. Например, color
не копируется, поскольку он никак не влияет на смещения символа. # 1
-
<div>
позиционируется в точном месте текста node (входное значение). Учитываются границы и прокладки, чтобы обеспечить правильное расположение временного <div>
.
- Создается переменная, которая содержит возвращаемое значение
div.getBoundingClientRect()
.
- Временная
<div>
удаляется, если для параметра debug
не установлено значение true.
- Функция возвращает объект
ClientRect
. Для получения дополнительной информации об этом объекте см. эту страницу. демонстрация также показывает список свойств: top
, left
, right
, bottom
, height
и width
.
# 1: getBoundingClientRect()
(и некоторые второстепенные свойства) используется для определения положения входного элемента. Затем добавляются добавка и ширина границы, чтобы получить реальное положение текста node.
Известные проблемы
Единственный случай несогласованности встречался, когда getComputedStyle
возвращал неправильное значение для font-family
: когда страница не определила свойство font-family
, computedStyle возвращает неверное значение (даже Firebug испытывает эту проблему; среда: Linux, Firefox 3.6.23, шрифт "Без засечек" ).
Как видно в демонстрации, позиционирование иногда слегка отключается (почти нулевое, всегда меньше 1 пикселя).
Технические ограничения не позволяют script получать точное смещение текстового фрагмента при перемещении содержимого, например. когда первый видимый символ в поле ввода не равен первому значению значения.
код
// @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
Обновление от мая 2014 года: Невероятно легкая и надежная textarea-caret-position Компонентная библиотека теперь поддерживает <input type="text">
, делая все остальные ответы устаревшими.
Демо доступно на http://jsfiddle.net/dandv/aFPA7/
Благодаря Rob W для вдохновения для поддержки RTL.
Ответ 4
Обновление 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>
могут быть вставлены заранее или динамически.