题目:计算下图所示输入框中的选中文本相对于页面的偏移位置:
首先思考如何获取页面上选中文本相对页面的偏移位置。
很多人可能知道 Element.getBoundingClientRect(),但这个方法是获取已知元素相对页面的偏移位置,所以在这里不能使用这个方法。
其实 Range 也有一个 Range.getBoundingClientRect() 方法,它是获取 Range 相对于页面的偏移位置,又可以根据 Selection 得到 Range,所以问题似乎迎刃而解了。
对于 IE 来说,问题是解决了,因为 IE 有一个叫做 createTextRange 方法,它对于输入框中的选中文本也是有效的。但 Webkit 中却没有这个方法,似乎没有什么完美的方法能解决这个问题。今天要讨论的就是如何在 Chrome ( Webkit ) 中解决这个问题。
解决的思路如下:
- 使用 div 克隆输入框,即使 div 看上去和输入框如出一辙:边框、字体、颜色、内容等。
- 根据输入框的 selectionStart 和 selectionEnd 值,选中 div 中对应的内容。
- 获取 Selection 得到 Range,再使用 Range.getBoundingClientRect() 方法获取偏移值。
- 删除 div 元素,根据之前保存的输入框的 selectionStart 和 selectionEnd 值,选中输入框之前选中的文本。
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | $( function () { 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 ; }); |
说明
- 代码默认使用 jQuery 库。
- 上面的代码并没完美地解决问题,主要是使用 div 模拟 textarea (或者input)时会遇到一些问题,比如 textarea 中的换行,div 中需要转换成 <br> 标签,这期间会丢失精度(当然也可以多写些代码解决这个精度问题,但这里不再讨论)。
- 其他的都是一些细节问题,也有可能没有测试到,有遗漏也在所难免。若有问题,欢迎给我留言,非常感谢。
- 这段代码已经用在了我开发的 Chrome 扩展中,欢迎使用:饥猪阅读,github地址:https://github.com/huntbao/piggyreader
参考资料
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步