获取文本选择中的元素

时间:2012-04-18 03:33:57

标签: javascript dom range

我希望获得用户选择中包含的所有元素(如DOM 2范围/ MS TextRanges中所示)。

/** @return {Array.<Element>} */
function getSelectedElements() {

  var elements = [];

  // get elements in the user selection somehow

  return elements;

}

我试过这样做是通过跟随Tim Down的优秀solution to a similar question,以及一些Moz和MS文档,以及一些PPK的东西。

方法基本上是:


  • 将SelectionLikeObject定义为DOM选择或IE选择。

  • 将RangeLikeObject定义为DOM范围或IE TextRange。

  • containerNode成为节点。

  • containerElement成为元素。

  • containedElements成为NodeList。

  • elementRange成为RangeLikeObject。

  • selectedRange成为RangeLikeObject。

  • selectedElements成为元素数组。

  • element成为元素。

  • selection成为SelectionLikeObject。

  • 从用户的选择中设置selection

  • selectedElements设置为新数组。

  • 对于selectedRange中的每个selection

    • containerNode设置为selectedRange的共同祖先容器。

    • containerElement设置为距containerNode最近的元素祖先。

    • containedElements设置为containerElement的后代列表。

    • 对于element中的每个containedElements

      • elementRange设置element

      • 如果elementRange的边界属于。{ selectedRange的边界:

        • element推送到selectedElements

DOM分支如下所示:

/** 
    @param {Document} doc
    @return {Array.<Element>} 
*/
getSelectedElements.fromDOM = function (doc) {

  /** @type {Range} */
  var selectedRange;

  /** @type {Array.<Element>} */
  var selectedElements = [];

  /** @type {Node} */
  var containerNode;

  /** @type {Element} */
  var containerElement;

  /** @type {NodeList} */
  var containedElements;

  /** @type {Range} */
  var elementRange;

  /** @type {Element} */
  var element;

  /** @type {Selection} */
  var selection = doc.defaultView.getSelection();

  /** @type {number} */
  var rangeCount = selection.rangeCount;

  /** @type {number} */
  var elementCount;

  /** @type {number} */
  var i;

  // hack for browsers without getRangeAt
  // see http://www.quirksmode.org/dom/range_intro.html

  if (!selection.getRangeAt) {

    selection.getRangeAt = function (i) {
      /** @type {Range} */
      var range = doc.createRange();
      if (i || !selection.anchorNode) {
        return range;
      }
      range.setStart(selection.anchorNode, selection.anchorOffset);
      range.setEnd(selection.focusNode, selection.focusOffset);
      return range;

    };

    selection.rangeCount = 1;

  }

  elementRange = doc.createRange();

  for (i = 0; i < rangeCount; ++i) {

    selectedRange = selection.getRangeAt(i);

    containerNode = selectedRange.commonAncestorContainer;

    while (containerNode && containerNode.nodeType != 1) {

      containerNode = containerNode.parentNode;

    }

    if (!containerNode) {

      return selectedElements; // something went wrong...

    }

    containerElement = /** @type {Element} */ containerNode;

    containedElements = containerElement.getElementsByTagName('*');

    elementCount = containedElements.length;

    for (var i = 0; i < elementCount; ++i) {

      element = containedElements[i];

      elementRange.selectNodeContents(element);

      if (elementRange.compareBoundaryPoints(selectedRange.END_TO_START, selectedRange) < 1 &&
          elementRange.compareBoundaryPoints(selectedRange.START_TO_END, selectedRange) > -1) {

        selectedElements.push(element);

      }
    }
  }

  elementRange.detach();

  return selectedElements;

};

IE分支看起来像这样:

/** 
    @param {Document} doc
    @return {Array.<Element>} 
*/
getSelectedElements.fromIE = function (doc) {

  // Selection - http://msdn.microsoft.com/en-us/library/ie/dd347133(v=vs.85).aspx
  // TextRange - http://msdn.microsoft.com/en-us/library/dd347140(v=vs.85).aspx
  // ControlRange - http://msdn.microsoft.com/en-us/library/ie/ms537447(v=vs.85).aspx

  /** @type {TextRange|ControlRange} */
  var ieRange = doc.selection && doc.selection.createRange();

  /** @type {Array.<Element>} */
  var selectedElements = [];

  /** @type {TextRange} */
  var selectedRange;

  /** @type {Element} */
  var containerElement;

  /** @type {NodeList} */
  var containedElements;

  /** @type {TextRange} */
  var elementRange;

  /** @type {Element} */
  var element;

  /** @type {Selection} */
  var selection;

  /** @type {number} */
  var i = -1;


  if (ieRange.text === void 0) {

    return []; // FIXME: It's a ControlRange, give up.

  }

  selectedRange = /** @type {TextRange} */ ieRange;

  containerElement = selectedRange.parentElement();

  containedElements = containerElement.getElementsByTagName('*');

  elementRange = doc.body.createTextRange();

  while ((element = containedElements[++i])) {

      elementRange.moveToElementText(element);

      if (elementRange.compareEndPoints("StartToEnd", selectedRange) > -1 && 
          elementRange.compareEndPoints("EndToStart", selectedRange) < 1) {

        selectedElements.push(element);

      } 
  }

  return /** @type {Array.<Element>} */ selectedElements;

};

现在,我要解决的问题是:如果只选择了元素中的部分文本,它将出现在返回的数组中,即使它只是partly selected

我想添加一个更改行为的参数,使其仅包含完全选定的元素。我有一种感觉,答案在于compareBoundaryPoints,我只是不太了解它还没有弄明白。

此外,IE代码到目前为止尚未经过测试,但如果有任何问题(或DOM分支),请告诉我。

1 个答案:

答案 0 :(得分:1)

在睡了一觉并再次阅读compareBoundaryPoints后,我想我有答案。

if (elementRange.compareBoundaryPoints(Range.START_TO_START, selectedRange) > -1 &&
    elementRange.compareBoundaryPoints(Range.END_TO_END, selectedRange) < 1) {

对于完全在用户选择范围内的元素,这似乎只评估true