• web dom api中的Selection和Range


    如果你做过wysiwyg这样的app,一个很让人头疼的问题是如何保证执行bold,italic等格式化操作后保持先前鼠标所在的位置。要好好的解决这个问题,就必须将Selection和Range的api搞搞清楚。

    https://javascript.info/selection-range

    Selection and Range

    js可以获得当前的选中区域信息,可以选择或者去选择部分或者全部内容,清楚document中的选中部分,使用一个心的tag来进行包裹等操作。所有这些操作的基石就是Selction和Range这两个api.

    Range

    选择区的基本概念是Range:它是一对边界点组成,分别定义range的start和end.

    每一个端点都是以相对于父DOM Node的offset这些信息来表达的point。如果父亲node是一个element element node,那么offset就是child的number号,儿对于text node,则是在text中的位置。我们以例子来说明,我们以选中某些内容为例:

    首先,我们可以创建一个range:

    let range = new Range();

    然后我们可以通过使用 range.setStart(node, offset), range.setEnd(node, offset) 这两个api函数来设定range的边界,比如,如果我们的html代码如下:

    <p id="p">Example: <i>italic</i> and <b>bold</b></p>

    其对应的dom树如下:

    我们来选择 Example: <i>italic</i> 这一部分内容。它实际上是p这个父元素的前面两个儿子节点(包含text node)

    我们来看实际的代码:

    <p id="p">Example: <i>italic</i> and <b>bold</b></p>
    
    <script>
      let range = new Range();
    
      range.setStart(p, 0);
      range.setEnd(p, 2);
    
      // toString of a range returns its content as text (without tags)
      alert(range); // Example: italic
    
      // apply this range for document selection (explained later)
       document.getSelection().removeAllRanges();
      document.getSelection().addRange(range);
    </script>
    • range.setStart(p,0)- 设定该选择范围是p父元素的第0个child节点(也就是一个text node:  Example:  )
    • range.setEnd(p,2)-指定该range将延展到p父元素的第2个child(也就是" and "这个text node),但是注意这里是不包含额,也就是说实际上是到第1个child,因此也就是 i 节点

    需要注意的是我们实际上不需要在setStart和setEnd调用中使用同一个参考node节点,一个范围可能延展涵盖到多个不相关的节点。唯一需要注意的是end必须是在start的后面

    选中text nodes的部分,而非全部

    假设我们想像下面的情况来做选中操作:

    这也可以使用代码轻松实现,我们需要做的是设定start和end时使用相对于text nodes的offset位置就好了。

    我们需要先创建一个range:

    1. range的start是p父亲元素的first child的position 2,也就是"ample:"

    2.range的end则是b父亲元素的position 3,也就是"bol"

    <p id="p">Example: <i>italic</i> and <b>bold</b></p>
    
    <script>
      let range = new Range();
    
      range.setStart(p.firstChild, 2);
      range.setEnd(p.querySelector('b').firstChild, 3);
    
      alert(range); // ample: italic and bol
    
      // use this range for selection (explained later)
      document.getSelection().removeAllRanges();??
      window.getSelection().addRange(range);
    </script>

    这时,range属性如下图取值:

    • startContainer,startOffset-分别指定start点的node和相对于该node的offset,本例中是p节点的首个text node子节点,以及第2个position
    • endContainer,endOffset-分别指定end点的node和offset,本例中是b节点的首个text node子节点,以及position 3
    • collapsed - 布尔值,如果star和end point都指向了同一个point的话为true,也意味着在该range中没有内容被选中,本例中取值为false
    • commonAncestorContainer - 在本range中所有节点的最近的共同祖先节点,本例中为p节点

    Range的方法methods

    range对象有很多有用的方法用于操作range:

    设定range的start:

    setEnd(node, offset) set end at: position offset in node
    setEndBefore(node) set end at: right before node
    setEndAfter(node) set end at: right after node

    正如前面演示的那样,node可以是一个text或者element node,对于text node, offset意思是忽略几个字符,而如果是element node,则指忽略多少个child nodes

    其他的方法:

    • selectNode(node) set range to select the whole node
    • selectNodeContents(node) set range to select the whole node contents
    • collapse(toStart) if toStart=true set end=start, otherwise set start=end, thus collapsing the range
    • cloneRange() creates a new range with the same start/end

    用于操作range的内容的方法:

    • deleteContents() – remove range content from the document
    • extractContents() – remove range content from the document and return as DocumentFragment
    • cloneContents() – clone range content and return as DocumentFragment
    • insertNode(node) – insert node into the document at the beginning of the range
    • surroundContents(node) – wrap node around range content. For this to work, the range must contain both opening and closing tags for all elements inside it: no partial ranges like <i>abc.

    有了这些有用的方法,我们就可以基本上针对选中的nodes做任何事情了,看下面一个比价复杂的例子:

    Click buttons to run methods on the selection, "resetExample" to reset it.
    
    <p id="p">Example: <i>italic</i> and <b>bold</b></p>
    
    <p id="result"></p>
    <script>
      let range = new Range();
    
      // Each demonstrated method is represented here:
      let methods = {
        deleteContents() {
          range.deleteContents()
        },
        extractContents() {
          let content = range.extractContents();
          result.innerHTML = "";
          result.append("extracted: ", content);
        },
        cloneContents() {
          let content = range.cloneContents();
          result.innerHTML = "";
          result.append("cloned: ", content);
        },
        insertNode() {
          let newNode = document.createElement('u');
          newNode.innerHTML = "NEW NODE";
          range.insertNode(newNode);
        },
        surroundContents() {
          let newNode = document.createElement('u');
          try {
            range.surroundContents(newNode);
          } catch(e) { alert(e) }
        },
        resetExample() {
          p.innerHTML = `Example: <i>italic</i> and <b>bold</b>`;
          result.innerHTML = "";
    
          range.setStart(p.firstChild, 2);
          range.setEnd(p.querySelector('b').firstChild, 3);
    
          window.getSelection().removeAllRanges();
          window.getSelection().addRange(range);
        }
      };
    
      for(let method in methods) {
        document.write(`<div><button onclick="methods.${method}()">${method}</button></div>`);
      }
    
      methods.resetExample();
    </script>

    除此之外,还有一些很少使用的用于比较range的api,https://developer.mozilla.org/en-US/docs/Web/API/Range

    Selection

    Range是一个用于管理selection ranges的通用对象。我们可以创建这些range对象,然后传递给dom api

    document的selection是由Selection对象来表征的,这可以通过 window.getSelection()或者document.getSelection() 来获得。

    一个selection可以包括0个或者多个ranges,但是在实际使用中,仅仅firefox允许选中多个ranges,这需要通过ctrl+click来实现,比如下图:

    selection对象的属性

    和range类似,一个selection也有start,被称为"anchor",和一个end,被称为"focus",主要的属性如下:

    • anchorNode – the node where the selection starts,
    • anchorOffset – the offset in anchorNode where the selection starts,
    • focusNode – the node where the selection ends,
    • focusOffset – the offset in focusNode where the selection ends,
    • isCollapsed – true if selection selects nothing (empty range), or doesn’t exist.
    • rangeCount – count of ranges in the selection, maximum 1 in all browsers except Firefox.

    selection events

    1. elem.onselectstart -当一个selection从elem这个元素开始发生时,比如用户当按下左键同时拖动鼠标时就会发生该事件。需要注意的是,如果elem被prevent default时,不发生该事件

    2. document.onselectionchange,这个事件只能在document上发生,只要有selection发生变化就会触发该事件

    看以下代码

    selection的常用methods:

    • getRangeAt(i) – get i-th range, starting from 0. In all browsers except firefox, only 0 is used.
    • addRange(range) – add range to selection. All browsers except Firefox ignore the call, if the selection already has an associated range.
    • removeRange(range) – remove range from the selection.
    • removeAllRanges() – remove all ranges.
    • empty() – alias to removeAllRanges

    以下方法无需操作底层的range对象就可以直接完成对应的功能:

    • collapse(node, offset) – replace selected range with a new one that starts and ends at the given node, at position offset.
    • setPosition(node, offset) – alias to collapse.
    • collapseToStart() – collapse (replace with an empty range) to selection start,
    • collapseToEnd() – collapse to selection end,
    • extend(node, offset) – move focus of the selection to the given node, position offset,
    • setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset) – replace selection range with the given start anchorNode/anchorOffset and end focusNode/focusOffset. All content in-between them is selected.
    • selectAllChildren(node) – select all children of the node.
    • deleteFromDocument() – remove selected content from the document.
    • containsNode(node, allowPartialContainment = false) – checks whether the selection contains node (partically if the second argument is true)

    我们再来看看以下例子代码及其效果:

    Selection in form controls

    Form元素,比如input, textarea则提供了更多的api用于selection操作和处理,而没有selection或者说range对象。由于input的value仅仅是text,而非html,因此也没有必要提供这些selection和range对象,事情会变得更加简单。

    • input.selectionStart – position of selection start (writeable),
    • input.selectionEnd – position of selection start (writeable),
    • input.selectionDirection – selection direction, one of: “forward”, “backward” or “none” (if e.g. selected with a double mouse click)

    input.onselect – triggers when something is selected.

  • 相关阅读:
    vscode英文显示设置为中文语言
    vscode各插件使用-背景图-scss
    公众号关联小程序
    js点击遮罩空白区域关闭,点击遮罩内元素不关闭
    swiper使用-点击跳转指定页
    小程序内部引导关注公众号实现方法
    jq赋值input值为空
    sass中代码使用
    Sources”参数中指定了多次。“Sources”参数不支持重复项
    将已存在小程序项目添加云开发配置
  • 原文地址:https://www.cnblogs.com/kidsitcn/p/11628822.html
Copyright © 2020-2023  润新知