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 の回答も歓迎します。
ベストアンサー1
デモ
期待通りに動作する関数を作成しました。非常に詳細なデモ パネルは、こちらにあります。フィドル:http://jsfiddle.net/56Rep/5/
デモのインターフェースは一目瞭然です。
質問で要求されている機能は、次のように私の関数に実装されます。
var pixelPosition = getTextBoundingRect(input, 6)
関数の依存関係
更新しました: この関数は純粋な JavaScript であり、プラグインやフレームワークに依存しません。
この関数はgetBoundingClientRect
メソッドが存在することを前提としています。テキスト範囲はサポートされている場合に使用されます。それ以外の場合は、私の関数ロジックを使用して機能が実現されます。
関数ロジック
コード自体にはいくつかのコメントが含まれています。この部分ではさらに詳しく説明します。
- 1つ一時的
<div>
コンテナが作成されます。 - 1 - 3 個の
<span>
要素が作成されます。各スパンは入力値の一部を保持します (オフセット 0 からselectionStart
、selectionStart
からselectionEnd
、selectionEnd
文字列の末尾まで、2 番目のスパンのみが意味を持ちます)。 - 入力要素のいくつかの重要なスタイル プロパティが、これらのタグ
<div>
と<span>
タグにコピーされます。重要なスタイル プロパティのみがコピーされます。たとえば、color
は文字のオフセットにまったく影響しないため、コピーされません。#1 - は
<div>
、テキストノード(入力値)境界線とパディングが考慮され、一時的<div>
な正しく配置されました。 - 戻り値を保持する変数が作成されます。
div.getBoundingClientRect()
。 - 一時的なもの
<div>
は取り除かれ、ない限りパラメータdebug
が true に設定されています。 - この関数はオブジェクトを返します
ClientRect
。このオブジェクトの詳細については、このページ。デモプロパティのリストも表示されます: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;
}
}