2011-08-03 122 views
17

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的答案是受歡迎的。

+0

可能與您尋找的內容有關:http:// stackoverflow。com/questions/4085312/jquery-get-the-cursor-position-of-text-in-input-without-browser-specific-code – Jesse

+0

我很確定簡短答案是「否」,但我沒有現在有時間進行闡述或研究。 –

+0

@TimDown我已經實現了請求的行爲。既然你有很多經驗範圍(由於你的Rangy項目),你能檢查它嗎? –

回答

2

我結束了創建一個隱藏的模擬輸入了絕對定位的跨度和樣式相似的輸入。我將該跨度的文本設置爲輸入的值,直到我想要查找的位置的字符。我輸入之前插入跨度和得到它的偏移:

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 
{ 
    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

我在這個實現之後創建了一個測試用例:http://jsfiddle.net/WYzfm/。不管給定的起始偏移量如何,返回的偏移量始終相等。另外,這個函數需要一個元素'#sizer'來定義。如果你想繼續開發這個功能,我推薦使用'$('

')',這樣它就不依賴於它所嵌入的文檔。 –

+1

@RobW - 爲什麼'top'或'bottom'在相同輸入中的不同字符偏移上會有所不同?我希望只有'left'改變,它可以:http://jsfiddle.net/gilly3/WYzfm/2/。 – gilly3

15

演示
我寫了一個函數,其行爲如預期。一個非常詳細的演示面板可以在這裏找到:小提琴:http://jsfiddle.net/56Rep/5/
在演示界面是不言自明的。

的要求,在這個問題會在我的函數中實現如下功能:
    var pixelPosition = getTextBoundingRect(input, 6)

功能依賴
更新:該功能是純JavaScript,而不是依賴於任何插件或框架!
該函數假定存在getBoundingClientRect方法。文本範圍在被支持時使用。否則,功能是使用我的功能邏輯實現的。

功能邏輯
本身包含幾個註釋的代碼。這一部分更深入細節。

  1. 一個臨時<div>容器被創建。
  2. 1 - 3 <span>元素已創建。每個量程保存輸入值的一部分(偏移0到selectionStartselectionStartselectionEnd,selectionEnd到字符串結尾,只有第二個量程是平均的)。
  3. 來自輸入元素的幾個重要樣式屬性被複制到這些<div><span>標籤中。只有重要的樣式屬性被複制。例如,color未被複制,因爲它不會以任何方式影響字符的偏移量。 #1
  4. <div>位於文本節點(輸入值)的確切位置。考慮到邊界和填充,以確保臨時<div>正確定位。
  5. 創建了一個變量,該變量保存的返回值爲div.getBoundingClientRect()
  6. 臨時<div>被刪除,除非參數debug設置爲true。
  7. 該函數返回ClientRect對象。有關此對象的更多信息,請參閱this pagedemo也顯示屬性列表:topleftright, bottom,heightwidth

#1getBoundingClientRect()(和一些次要屬性)被用於確定所述輸入元件的位置。然後,添加填充和邊框寬度,以獲取文本節點的實際位置。

已知問題
遇到不一致的情況下,只有當getComputedStyle返回錯誤值font-family:當一個頁面已經沒有定義font-family特性,computedStyle返回不正確的值(甚至螢火蟲遇到此問題;環境: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; 
    } 
} 
+0

+1。這是一個非常強大的解決方案,適合打包爲插件,甚至包含在分佈式庫中。但是,比我需要的方式更多。就我個人而言,我更喜歡做你在CSS中做什麼。它大大降低了複雜性。另外,我很樂意假定調用者會通過很好的論證而不是做所有的驗證。讓來電者承受垃圾傳入的後果。還有一個想法:不是將功能烘焙到獲得開始和結束位置的能力,而是通過簡單地調用函數兩次來簡化函數並獲得兩個位置。 – gilly3

+0

@ gilly3我已經包含了完整性的結束偏移量。左邊緣偏移量可以通過'.left'獲得,右邊緣偏移量可以使用'.left + .width'來計算。出於性能原因,最好調用該函數,並使用'.left + .width',而不是兩次調用該函數。支持'selectionStart'和'selectionEnd'並不是什麼大不了的事情。 **關於CSS **:CSS主要用於複製影響偏移量的屬性。如果對我的「功能邏輯」部分有任何不清楚的地方,請指出,以便我可以改進它。 –

+0

對於我所指的簡化,請參閱我發佈的答案 - 這就是我最終使用的答案。順便說一下,您的代碼在IE中不起作用。爲了解決這個問題,你需要首先摺疊你的範圍,然後通過結束開始的差異來移動結束。看到這個更新:http://jsfiddle.net/gilly3/56Rep/4/ – gilly3

1

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>標籤可以預先插入或動態插入。

相關問題