☚ GZOOL Quality Without A Name

获取输入框中选中文本相对于页面的偏移

题目:计算下图所示输入框中的选中文本相对于页面的偏移位置:

o_textarea_selected_text_offset

首先思考如何获取页面上选中文本相对页面的偏移位置。

很多人可能知道 Element.getBoundingClientRect(),但这个方法是获取已知元素相对页面的偏移位置,所以在这里不能使用这个方法。

其实 Range 也有一个 Range.getBoundingClientRect() 方法,它是获取 Range 相对于页面的偏移位置,又可以根据 Selection 得到 Range,所以问题似乎迎刃而解了。

对于 IE 来说,问题是解决了,因为 IE 有一个叫做 createTextRange 方法,它对于输入框中的选中文本也是有效的。但 Webkit 中却没有这个方法,似乎没有什么完美的方法能解决这个问题。今天要讨论的就是如何在 Chrome ( Webkit ) 中解决这个问题。

解决的思路如下:

代码实现如下:

var getSelectedTextBounding = function (input, start, end) {
    var inputBoudRect = input.getBoundingClientRect()
    var div = $('<div>').html(input.value.replace(/\n/g, '<br />')).appendTo(document.body)
    div[0].style.cssText = document.defaultView.getComputedStyle(input, null).cssText
    div.css({
        position: 'absolute',
        left: inputBoudRect.left,
        top: inputBoudRect.top,
        margin: 0,
        overflow: 'hidden'
    })
    div[0].scrollLeft = input.scrollLeft
    div[0].scrollTop = input.scrollTop
    var range = setSelectionRange(div[0], start, end)
    var textBounding = range.getBoundingClientRect()
    div.remove()
    return textBounding

    function getTextNodesIn(node) {
        var textNodes = []
        if (node.nodeType == 3) {
            textNodes.push(node)
        } else {
            var children = node.childNodes
            for (var i = 0, len = children.length i < len ++i) {
                textNodes.push.apply(textNodes, getTextNodesIn(children[i]))
            }
        }
        return textNodes
    }

    function setSelectionRange(el, start, end) {
        var range = document.createRange()
        range.selectNodeContents(el)
        var textNodes = getTextNodesIn(el)
        var foundStart = false
        var charCount = 0, endCharCount

        for (var i = 0, textNode; textNode = textNodes[i++];) {
            endCharCount = charCount + textNode.length
            if (!foundStart && start >= charCount && (start < endCharCount || (start == endCharCount && i < textNodes.length))) {
                range.setStart(textNode, start - charCount)
                foundStart = true
            }
            if (foundStart && end <= endCharCount) {
                range.setEnd(textNode, end - charCount)
                break
            }
            charCount = endCharCount
        }

        var sel = window.getSelection()
        sel.removeAllRanges()
        sel.addRange(range)
        return range
    }
}


var mouseDownedInput = null

$(document).on('mousedown', 'input, textarea', function () {
    mouseDownedInput = this
})

$(document).mouseup(function (e) {
    var sel = window.getSelection()
    var selectedText = sel.toString().trim()
    var boundingClientRect
    if (selectedText) {
        var r = sel.getRangeAt(0)
        if (r.collapsed) {
            if (mouseDownedInput) {
                var start = mouseDownedInput.selectionStart
                var end = mouseDownedInput.selectionEnd
                boundingClientRect = getSelectedTextBounding(mouseDownedInput, start, end)
                mouseDownedInput.setSelectionRange(start, end)
            }
        } else {
            boundingClientRect = r.getBoundingClientRect()
        }
    }
    console.log(boundingClientRect)
    mouseDownedInput = null
})
    

说明

参考资料

本文最初发表于博客园

comments powered by Disqus