1

ユーザー選択に含まれるすべての要素を取得したい (DOM 2 範囲/MS TextRanges のように)。

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

  var elements = [];

  // get elements in the user selection somehow

  return elements;

}

同様の質問に対するTim Downの優れた解決策、およびいくつかのMozとMSのドキュメント、およびいくつかのPPKのものに従って、これを実行しようとしました。

アプローチは基本的に次のとおりです。


  • SelectionLikeObject を DOM 選択または IE 選択として定義します。

  • RangeLikeObject を DOM Range または IE TextRange として定義します。

  • containerNodeノードにしましょう。

  • containerElement要素にしましょう。

  • NodeListcontainedElementsにします。

  • RangeLikeObjectelementRangeにします。

  • RangeLikeObjectselectedRangeにします。

  • selectedElements要素の配列としましょう。

  • element要素にしましょう。

  • SelectionLikeObjectselectionにします。

  • selectionユーザーの選択から設定します。

  • selectedElements新しい配列に設定します。

  • のそれぞれselectedRangeについてselection:

    • containerNodeの共通の祖先コンテナに設定しselectedRangeます。

    • containerElementに最も近い Element の祖先に設定しcontainerNodeます。

    • containedElementsの子孫のリストに設定しcontainerElementます。

    • のそれぞれelementについてcontainedElements:

      • から設定elementRangeelementます。

      • の境界が の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;

};

ここで、解決したい問題は次のとおりです。要素内のテキストの一部のみが選択されている場合、部分的にしか選択されていないにもかかわらず、返された配列に表示されます。

完全に選択された要素のみを含めるように動作を変更するパラメーターを追加したいと思います。答えはcompareBoundaryPointsにあると感じていますが、まだ理解できるほどよく理解していません。

また、IE コードは今のところテストされていませんが、それ (または DOM ブランチ) に問題があるように見える場合はお知らせください。

4

1 に答える 1

1

少し寝てcompareBoundaryPointsをもう一度読んだ後、私は答えを持っていると思います.

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

trueこれは、ユーザーの選択範囲内に完全に収まる要素に対してのみ評価されるようです。

于 2012-04-18T19:37:01.093 に答える