copy-to-clipboard源码解读(含有toggle-selection源码以及navigator.clipboard的使用)

copy-to-clipboard

npm地址:https://www.npmjs.com/package/copy-to-clipboard

GitHub地址:https://github.com/sudodoki/copy-to-clipboard

该库使用的复制方法实现已经过时。window.clipboardData已经不支持;document.execCommand("copy")已经过时,已废除,只是还没删除。

使用

copy(text,options)
  • text,需要复制的字符串内容
  • options
    • onCopy(clipboardData),可选择的接收剪贴板数据元素,用于添加自定义行为,如其他格式
    • message,复制出错自定义错误信息

源码部分

"use strict";

var deselectCurrent = require("toggle-selection");

var clipboardToIE11Formatting = {
  "text/plain": "Text",
  "text/html": "Url",
  "default": "Text"
}

var defaultMessage = "Copy to clipboard: #{key}, Enter";

function format(message) {
  var copyKey = (/mac os x/i.test(navigator.userAgent) ? "⌘" : "Ctrl") + "+C";
  return message.replace(/#{\s*key\s*}/g, copyKey);
}

// 参数text, 要copy的内容字符串, 必填
// 参数options, 一些选项信息,非必填
function copy(text, options) {
  // 一次性声明多个变量
  var debug,
    message,
    reselectPrevious,
    range,
    selection,
    mark,
    success = false;
  if (!options) {
    options = {};
  }
  debug = options.debug || false;

  try {
    // 赋值,deselectCurrent方法主要取消选择当前浏览器选择并返回恢复选择的函数。
    reselectPrevious = deselectCurrent();

    //返回一个range对象,Range 接口表示一个包含节点与文本节点的一部分的文档片段。
    range = document.createRange();
    //返回一个selection对象,表示用户选择的文本范围或光标的当前位置。
    selection = document.getSelection();
    //创建一个span元素
    mark = document.createElement("span");
    //Node 接口的 textContent 属性表示一个节点及其后代的文本内容
    mark.textContent = text;
    // avoid screen readers from reading out loud the text,把 aria-hidden="true" 加到元素上会把该元素和它的所有子元素从无障碍树上移除
    mark.ariaHidden = "true"
    // reset user styles for span element,CSS all 简写属性 将除了 unicode-bidi 与 direction 之外的所有属性重设至其初始值,或继承值。unset,该关键字代表如果该元素的属性的值是可继承的,则改变该元素或该元素的父元素的所有属性的值为他们父元素的属性值,反之则改变为初始值。
    mark.style.all = "unset";
    // prevents scrolling to the end of the page
    mark.style.position = "fixed";
    mark.style.top = 0;
    //clip屬性废弃。 clip 属性定义了元素的哪一部分是可见的。clip 属性只适用于 position:absolute 的元素。 
    mark.style.clip = "rect(0, 0, 0, 0)";
    //whiteSpace处理空白元素。 pre连续的空白符会被保留。仅在遇到换行符或 <br> 元素时才会换行。
    mark.style.whiteSpace = "pre";
    // do not inherit user-select (it may be `none`)
    mark.style.webkitUserSelect = "text";
    mark.style.MozUserSelect = "text";
    mark.style.msUserSelect = "text";
    // user-select CSS 属性用于控制用户是否可以选择文本。这不会对作为浏览器用户界面(即 chrome)的一部分的内容加载产生任何影响,除非是在文本框中。
    mark.style.userSelect = "text";
    // 触发监听copy事件时执行
    mark.addEventListener("copy", function (e) {
      e.stopPropagation();
      if (options.,) {
        e.preventDefault();
        // 如果事件的event对象没有clipboardData对象,则使用 window.clipboardData
        if (typeof e.clipboardData === "undefined") { // IE 11
          debug && console.warn("unable to use e.clipboardData");
          debug && console.warn("trying IE specific stuff");
          // window.clipboardData为undef,如今已没有此API;删除所有类型的拖拽操作的数据
          window.clipboardData.clearData();
          var format = clipboardToIE11Formatting[options.format] || clipboardToIE11Formatting["default"]
          // format 数据的格式,是string类型
          window.clipboardData.setData(format, text);
        } else { // all other browsers
        // 当用户通过浏览器的用户界面发起复制动作时,将触发 copy 事件。该事件的默认行为是将当前选中的内容(如有)复制到系统剪贴板。事件处理器可以通过调用 ClipboardEvent.clipboardData 属性上的 setData(format, data) 修改剪贴板内容,并通过 event.preventDefault() 取消默认行为。
          e.clipboardData.clearData();
          e.clipboardData.setData(options.format, text);
        }
      }
      if (options.onCopy) {
        // 阻止默认行为
        e.preventDefault();
        options.onCopy(e.clipboardData);
      }
    });

    // 往body中插入一个子元素Mark
    document.body.appendChild(mark);

    // Range.selectNodeContents() 方法用于设置 Range,使其包含一个 Node 的内容。
    // Range 的起始和结束节点的父节点即为引用节点。 startOffset 为 0, endOffset 则是引用节点包含的字符数或子节点个数。
    range.selectNodeContents(mark);

    // 向选区(Selection)中添加一个区域(Range)。
    selection.addRange(range);

    // 利用系统命令实现
    var successful = document.execCommand("copy");
    if (!successful) {
      throw new Error("copy command was unsuccessful");
    }
    success = true;
  } catch (err) {
    debug && console.error("unable to copy using execCommand: ", err);
    debug && console.warn("trying IE specific stuff");
    try {
      // window.clipboardData为undefined,现在已经不支持了
      window.clipboardData.setData(options.format || "text", text);
      // 自定义格式化返回剪切板的数据
      options.onCopy && options.onCopy(window.clipboardData);
      success = true;
    } catch (err) {
      debug && console.error("unable to copy using clipboardData: ", err);
      debug && console.error("falling back to prompt");
      message = format("message" in options ? options.message : defaultMessage);
      window.prompt(message, text);
    }
  } finally {
    if (selection) {
      if (typeof selection.removeRange == "function") {
        // 从选区中移除一个区域。
        selection.removeRange(range);
      } else {
        // 将所有的区域都从选区中移除。
        selection.removeAllRanges();
      }
    }

    if (mark) {
      // 移除子节点mark
      document.body.removeChild(mark);
    }
    // 取消选择当前浏览器选择并返回恢复选择的函数。
    reselectPrevious();
  }

  return success;
}

module.exports = copy;

Window.getSelection()与 document.getSelection()

详细地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Selection

返回一个 Selection 对象,表示用户选择的文本范围或光标的当前位置。

Selection 对象表示用户选择的文本范围或插入符号的当前位置。它代表页面中的文本选区,可能横跨多个元素。文本选区由用户拖拽鼠标经过文字而产生。要获取用于检查或修改的 Selection 对象,请调用window.getSelection()

一般来说,插入光标的位置可通过 Selection 获取,这时它被标记为 Collapsed,这表示选区被压缩至一点,即光标位置。
但要注意它与 focus 事件或 Document.activeElement 等的值没有必然联系。

selection 是一个 Selection 对象。如果想要将 selection 转换为字符串,可通过连接一个空字符串(“”)或使用 String.toString() 方法。

const selection = window.getSelection() ;

selection对象属性

  • anchorNode: null ;返回该选区起点所在的节点(Node)。
  • anchorOffset: 0;返回一个数字,其表示的是选区起点在 anchorNode 中的位置偏移量。
    • 如果 anchorNode 是文本节点,那么返回的就是从该文字节点的第一个字开始,直到被选中的第一个字之间的字数(如果第一个字就被选中,那么偏移量为零)。
    • 如果 anchorNode 是一个元素,那么返回的就是在选区第一个节点之前的同级节点总数。(这些节点都是 anchorNode 的子节点)
  • baseNode: null
  • baseOffset: 0
  • extentNode: null
  • extentOffset: 0;
  • focusNode: null;返回该选区终点所在的节点。
  • focusOffset: 0;返回一个数字,其表示的是选区终点在 focusNode 中的位置偏移量。
    • 如果 focusNode 是文本节点,那么选区末尾未被选中的第一个字,在该文字节点中是第几个字(从 0 开始计),就返回它。
    • 如果 focusNode 是一个元素,那么返回的就是在选区末尾之后第一个节点之前的同级节点总数。
  • isCollapsed: true;返回一个布尔值,用于判断选区的起始点和终点是否在同一个位置。
  • rangeCount: 0;返回该选区所包含的连续范围的数量.
    • Selection.rangeCount 是一个返回选区(selection)中范围(range)数量的只读属性。
    • 在网页使用者点击一个加载完毕的新打开的页面之前,rangeCount 的值是 0。
    • 在使用者点击页面之后,rangeCount 的值变为 1,即使并没有可视的选区。
    • 通常情况下, rangeCount 为0或者1,因为浏览器一般不允许用户选择多个 range 。当用户没有选择任何区域,则 rangeCount 值为
  • type: "None";是 Selection 接口的只读属性,其返回的是DOMString即描述当前选择的类型。DOMString 描述的是当前选择的类型。可能的值为:
    • None: 当前没有选择。
    • Caret: 选区已折叠(即 光标在字符之间,并未处于选中状态)。
    • Range: 选择的是一个范围。

anchorOffsetbaseOffset为选取文本起点所在节点的位置(从0开始,从左向右数),extentOffset和focusOffset为选取文本结束点所在节点的位置(从0开始,从左向右数)

用户可能从左到右(与文档方向相同)选择文本或从右到左(与文档方向相反)选择文本。
anchor 指向用户开始选择的地方,而 focus 指向用户结束选择的地方。如果你使用鼠标选择文本的话,anchor 就指向你按下鼠标键的地方,而 focus 就指向你松开鼠标键的地方。

anchor focus 的概念不能与选区的起始位置和终止位置混淆,因为 anchor 指向的位置可能在 focus 指向的位置的前面,也可能在 focus 指向位置的后面,这取决于你选择文本时鼠标移动的方向(也就是按下鼠标键和松开鼠标键的位置)。

判断当前是否有选取的内容

  • anchorNode为null
  • toString返回为空
  • anchorOffsetfocusOffset相等
  • isCollapsed为true
  • type等于CaretNone(当有选取内容时,typeRange
  • rangeCount为0

Docuemnt.createRange()

详细地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Range

Docuemnt.createRange() 方法返回一个新的Range对象。一旦建立了 Range 对象,在使用它的大多数方法之前需要设置它的边界点。

Range 接口表示一个包含节点与文本节点的一部分的文档片段。

  • 可以使用 Document.createRange 方法创建 Range
  • 也可以用 Selection 对象的 getRangeAt()方法或者 Document 对象的 caretRangeFromPoint() 方法获取 Range 对象。
  • 还可以用 Range()构造函数。

range属性

  • collapsed: true;返回一个表示 Range 的起始位置和终止位置是否相同的布尔值
  • commonAncestorContainer: document;返回完整包含 startContainer endContainer 的、最深一级的节点。
  • endContainer: document;返回包含Range终点的节点。
  • endOffset: 0;返回一个表示 Range 终点在 endContainer 中的位置的数字。
  • startContainer: document;返回包含Range开始的节点。
  • startOffset: 0;返回一个数字,表示RangestartContainer 中的起始位置。

ClipboardEvent.clipboardData

ClipboardEvent.clipboardData 属性保存了一个 DataTransfer 对象,这个对象可用于:

描述哪些数据可以由 cut 和 copy 事件处理器放入剪切板,通常通过调用 setData(format, data) 方法;
获取由 paste 事件处理器拷贝进剪切板的数据,通常通过调用 getData(format) 方法

document.execCommand(“copy”)

执行copy命令,将文本放置剪切板

不过该命令已经过时,被废弃,但未删除。

Selection.removeAllRanges()

Selection.removeAllRanges() 方法会从当前 selection 对象中移除所有的 range 对象,取消所有的选择只 留下anchorNode focusNode属性并将其设置为 null

Range.selectNodeContents()

Range.selectNodeContents() 方法用于设置 Range,使其包含一个 Node 的内容。
Range 的起始和结束节点的父节点即为引用节点。 startOffset 为 0, endOffset 则是引用节点包含的字符数或子节点个数。

range.selectNodeContents(referenceNode);

referenceNode,此 Node 中的内容被包含在 Range 中。

document.activeElement

Document 和 ShadowRoot 接口的 activeElement 只读属性,用来返回当前在 DOM 或者 shadow DOM 树中处于聚焦状态的Element。

通常情况下,如果 HTMLInputElement 或者 HTMLTextAreaElement元素中有文字被选中时, activeElement属性就会返回该元素。这时,你可以调用该元素的selectionStart 和 selectionEnd 属性获取更多选中文字的信息。其他情况下,焦点元素也可能是元素 (menu) 或者一个别的<input>元素,比如 "button""checkbox" 或者 "radio"

element = DocumentOrShadowRoot.activeElement

当前获得焦点的 Element ,如果没有焦点元素,会返回<body>或者 null

toggle-selection

npm地址:https://www.npmjs.com/package/toggle-selection
一个简单的模块,用于显示取消选择当前浏览器选择并返回恢复选择的函数。

module.exports = function () {
  // 返回一个 Selection 对象,表示用户选择的文本范围或光标的当前位置。
  var selection = document.getSelection();

  // Selection.rangeCount 是一个返回选区(selection)中范围(range)数量的只读属性。通常情况下, rangeCount 为0或者1,因为浏览器一般不允许用户选择多个 range 。当用户没有选择任何区域,则 rangeCount 值为
  if (!selection.rangeCount) { //新打开的页面rangeCount为0,返回一个空函数
    return function () {};
  }

  // 当前获得焦点的 Element ,如果没有焦点元素,会返回 <body> 或者 null 。
  var active = document.activeElement;

  // 存储区域rang对象的一个数组
  var ranges = [];
  // selection.rangeCount一般情况下值为0或1
  for (var i = 0; i < selection.rangeCount; i++) {
    // Range 接口表示一个包含节点与文本节点的一部分的文档片段。
    ranges.push(selection.getRangeAt(i));
  }

  switch (active.tagName.toUpperCase()) { // .toUpperCase handles XHTML
    case 'INPUT':
    case 'TEXTAREA':
      active.blur(); //失去焦点
      break;

    default:
      active = null;
      break;
  }
  // Selection.removeAllRanges() 方法会从当前 selection 对象中移除所有的 range 对象,取消所有的选择只 留下anchorNode 和focusNode属性并将其设置为 null。
  selection.removeAllRanges();
  // 
  return function () {
    //  selection.type  返回的是DOMString即描述当前选择的类型。
    /**
     * DOMString 描述的是当前选择的类型。可能的值为:
      None: 当前没有选择。
      Caret: 选区已折叠(即 光标在字符之间,并未处于选中状态)。
      Range: 选择的是一个范围。
     */
    selection.type === 'Caret' &&
      selection.removeAllRanges(); // 将所有的区域都从选区中移除。

    if (!selection.rangeCount) { //当页面没被点击过 rangCount为0
      ranges.forEach(function (range) {
        // 向选区(Selection)中添加一个区域(Range)。
        selection.addRange(range);
      });
    }

    active &&
      active.focus(); //获得焦点
  };
};

navigator.clipboard实现复制

剪贴板 Clipboard API 为 Navigator 接口添加了只读属性 clipboard,该属性返回一个可以读写剪切板内容的 Clipboard 对象。在 Web 应用中,剪切板 API 可用于实现剪切、复制、粘贴的功能。

只有在用户事先授予网站或应用对剪切板的访问许可之后,才能使用异步剪切板读写方法。许可操作必须通过取得权限 Permissions API (en-US) 的 “clipboard-read” 和/或 “clipboard-write” 项获得。

支持环境:https、localhost

不支持环境:直接IP访问的网页、http(navigator.clipboard直接返回undefined)

实现代码

<body>
  <div id="content">
    <button id="btnCopy">copy</button>
  </div>
</body>
<script>
  let dom = document.getElementById('btnCopy')
  console.log('dom', dom);
  dom.onclick = () => {
    navigator.clipboard.writeText('hhyyyyy').then(() => {
      console.log('copy success');
    })
  }
</script>
  • 17
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值