14

选择一块文本(可能跨越许多DOM节点)时,是否可以使用JavaScript提取所选文本和节点?

想象一下这段 HTML 代码:

<h1>Hello World</h1><p>Hi <b>there!</b></p>

如果用户从“World...”开始启动 mouseDown 事件,然后在“there!”之后启动 mouseUp,我希望它会返回:

Text : { selectedText: "WorldHi there!" },
Nodes: [ 
  { node: "h1", offset: 6, length: 5 }, 
  { node: "p", offset: 0, length: 16 }, 
  { node: "p > b", offset: 0, length: 6 } 
]

我尝试将 HTML 放入 textarea 中,但这只会让我获得 selectedText。我没有尝试过该<canvas>元素,但这可能是另一种选择。

如果不是 JavaScript,有没有办法使用 Firefox 扩展来实现?

4

6 回答 6

15

您将经历一段颠簸的旅程,但这很有可能。主要问题是 IE 和 W3C 为选择提供了完全不同的界面,因此如果您想要跨浏览器功能,那么您基本上必须将整个内容编写两次。此外,两个界面都缺少一些基本功能。

Mozilla 开发者联系有关于W3C 选择的故事。Microsoft在 MSDN 上记录了他们的系统。我建议从 PPK对 range 的介绍开始。

以下是我认为有效的一些基本功能:

// selection objects will differ between browsers
function getSelection () {
  return ( msie ) 
    ? document.selection
    : ( window.getSelection || document.getSelection )();
}

// range objects will differ between browsers
function getRange () {
  return ( msie ) 
      ? getSelection().createRange()
      : getSelection().getRangeAt( 0 )
}

// abstract getting a parent container from a range
function parentContainer ( range ) {
  return ( msie )
      ? range.parentElement()
      : range.commonAncestorContainer;
}
于 2008-12-12T23:19:35.623 回答
8

我的Rangy库将通过统一 IE < 9 和所有其他主要浏览器中的不同 API,并getNodes()在其 Range 对象上提供一个函数,为您提供帮助:

function getSelectedNodes() {
    var selectedNodes = [];
    var sel = rangy.getSelection();
    for (var i = 0; i < sel.rangeCount; ++i) {
        selectedNodes = selectedNodes.concat( sel.getRangeAt(i).getNodes() );
    }
    return selectedNodes;
}

在所有浏览器中获取所选文本都非常容易。在 Rangy 它只是

var selectedText = rangy.getSelection().toString();

没有范围:

function getSelectedText() {
    var sel, text = "";
    if (window.getSelection) {
        text = "" + window.getSelection();
    } else if ( (sel = document.selection) && sel.type == "Text") {
        text = sel.createRange().text;
    }
    return text;
}

至于字符偏移,您可以node对选择中的任何节点执行类似的操作。请注意,这并不一定代表文档中的可见文本,因为它没有考虑折叠空格、通过 CSS 隐藏的文本、通过 CSS 定位在正常文档流之外的文本、由和块元素隐含的换行符<br>以及其他细微之处。

var sel = rangy.getSelection();
var selRange = sel.getRangeAt(0);
var rangePrecedingNode = rangy.createRange();
rangePrecedingNode.setStart(selRange.startContainer, selRange.startOffset);
rangePrecedingNode.setEndBefore(node);
var startIndex = rangePrecedingNode.toString().length;
rangePrecedingNode.setEndAfter(node);
var endIndex = rangePrecedingNode.toString().length;
alert(startIndex + ", " + endIndex);
于 2012-04-09T15:55:32.987 回答
4

据我了解,这将返回选定的节点:当我有

<p> ... </p><p> ... </p><p> ... </p><p> ... </p><p> ... </p>...
<p> ... </p><p> ... </p><p> ... </p><p> ... </p><p> ... </p>

很多节点,我只选择了几个,然后我只希望这些节点出现在列表中。

function getSelectedNodes() {
  // from https://developer.mozilla.org/en-US/docs/Web/API/Selection
  var selection = window.getSelection();
  if (selection.isCollapsed) {
    return [];
  };
  var node1 = selection.anchorNode;
  var node2 = selection.focusNode;
  var selectionAncestor = get_common_ancestor(node1, node2);
  if (selectionAncestor == null) {
    return [];
  }
  return getNodesBetween(selectionAncestor, node1, node2);
}

function get_common_ancestor(a, b)
{
    // from http://stackoverflow.com/questions/3960843/how-to-find-the-nearest-common-ancestors-of-two-or-more-nodes
    $parentsa = $(a).parents();
    $parentsb = $(b).parents();

    var found = null;

    $parentsa.each(function() {
        var thisa = this;

        $parentsb.each(function() {
            if (thisa == this)
            {
                found = this;
                return false;
            }
        });

        if (found) return false;
    });

    return found;
}

function isDescendant(parent, child) {
     // from http://stackoverflow.com/questions/2234979/how-to-check-in-javascript-if-one-element-is-a-child-of-another
     var node = child;
     while (node != null) {
         if (node == parent) {
             return true;
         }
         node = node.parentNode;
     }
     return false;
}

function getNodesBetween(rootNode, node1, node2) {
  var resultNodes = [];
  var isBetweenNodes = false;
  for (var i = 0; i < rootNode.childNodes.length; i+= 1) {
    if (isDescendant(rootNode.childNodes[i], node1) || isDescendant(rootNode.childNodes[i], node2)) {
      if (resultNodes.length == 0) {
        isBetweenNodes = true;
      } else {
        isBetweenNodes = false;
      }
      resultNodes.push(rootNode.childNodes[i]);
    } else if (resultNodes.length == 0) {
    } else if (isBetweenNodes) {
      resultNodes.push(rootNode.childNodes[i]);
    } else {
      return resultNodes;
    }
  };
 if (resultNodes.length == 0) {
    return [rootNode];
  } else if (isDescendant(resultNodes[resultNodes.length - 1], node1) || isDescendant(resultNodes[resultNodes.length - 1], node2)) {
    return resultNodes;
  } else {
    // same child node for both should never happen
    return [resultNodes[0]];
  }
}

代码应位于:https ://github.com/niccokunzmann/spiele-mit-kindern/blob/gh-pages/javascripts/feedback.js

我在这里发布了这个答案,因为我想在这里找到它。

于 2013-12-02T19:20:46.457 回答
0

如果您只想要范围,还有一种更短的方法。

function getRange(){
    return (navigator.appName=="Microsoft Internet Explorer")
        ? document.selection.createRange().parentElement()
        : (getSelection||document.getSelection)().getRangeAt(0).commonAncestorContainer
}
于 2012-01-25T15:09:30.240 回答
0

在 IE11+ 中工作的所有符合标准的代码。

文本字符串

window.getSelection().getRangeAt(0).toString()

起始节点(即使文本被向后选择):

window.getSelection().anchorNode

结束节点(即使文本被向后选择):

window.getSelection().focusNode

你想了解更多吗?选择一些文本并在控制台中运行以下 JavaScript:

console.log(window.getSelection());
console.log(window.getSelection().getRangeAt(0));
于 2018-02-14T13:53:44.853 回答
-1

您可能想从http://javascript.internet.com/page-details/copy-selected-text.html开始

于 2008-12-11T22:26:08.737 回答