HOW - copy to clipboard 复制内容到剪贴板实践

一、前言

这个是一个简单的场景,也是一个非常常见的产品需求。

那在原生 JavaScript 场景下和 React 这类前端框架场景下,我们分别可以采取什么方案去实现呢?

二、实践

2.1 原生 JavaScript

1. 代码实现

在 JavaScript 原生项目中,你可以使用以下代码实现该功能:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Copy Text Example</title>
</head>
<body>
    <div>
        <span id="text-to-copy">这是要复制的文本。</span>
        <button id="copy-button">复制</button>
    </div>

    <script>
        // 获取元素
        const copyButton = document.getElementById('copy-button');
        const textToCopy = document.getElementById('text-to-copy').innerText;

        // 复制函数
        function copyText() {
            // 创建一个临时的文本区域
            const tempInput = document.createElement('textarea');
            tempInput.value = textToCopy;
            document.body.appendChild(tempInput);
            tempInput.select();
            document.execCommand('copy');
            document.body.removeChild(tempInput);

            // 提示用户
            alert('文本已复制到剪贴板');
        }

        // 绑定点击事件
        copyButton.addEventListener('click', copyText);
    </script>
</body>
</html>

2. 说明

  1. 创建一个临时的 <textarea> 元素,并将要复制的文本设置为其值。
  2. 选择该文本区域中的内容,并执行 document.execCommand('copy') 进行复制。
  3. 移除临时文本区域,并用 alert 提示用户复制成功。

2.2 React 项目

在 React 项目中,你可以使用以下代码实现相同的功能:

1. 代码实现

import React from 'react';

function App() {
    // 要复制的文本
    const textToCopy = '这是要复制的文本。';

    // 复制函数
    const copyText = () => {
        // 使用剪贴板 API 进行复制
        navigator.clipboard.writeText(textToCopy).then(() => {
            alert('文本已复制到剪贴板');
        }).catch(err => {
            console.error('复制失败', err);
        });
    };

    return (
        <div>
            <span>{textToCopy}</span>
            <button onClick={copyText}>复制</button>
        </div>
    );
}

export default App;

2. 说明

  1. 使用 navigator.clipboard.writeText API 来将文本复制到剪贴板,这是一种现代的方式,支持 Promise。
  2. 复制成功后,显示 alert 提示用户文本已复制。
  3. catch 块用于处理可能的错误,例如在不支持剪贴板 API 的浏览器中。

三、clipboard API

官方文档:https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API

navigator.clipboard.writeText 方法允许你将文本直接写入剪贴板。这是一种比传统 document.execCommand('copy') 更现代、更可靠的方法,因为它返回一个 Promise,可以方便地处理成功和失败的情况。

  1. 简化的语法:

    • 通过使用 navigator.clipboard.writeText,你可以避免使用传统方法中的额外步骤,如创建临时 <textarea> 元素、选择内容等。
  2. Promise 支持:

    • 使用 navigator.clipboard.writeText 返回一个 Promise,使得处理成功和失败的操作更加直观。你可以使用 thencatch 处理复制操作的结果。
  3. 安全和权限:

    • 剪贴板 API 具有更好的安全性和权限管理,能够更好地控制访问权限。浏览器会提示用户有关剪贴板访问的权限请求。

四、第三方库

推荐使用

后者是基于前者封装的 React 组件。我们直接来学习前者的源码做了什么。

copy-to-clipboard 介绍

copy-to-clipboard 是一个流行的第三方库,用于简化在网页应用中复制文本到剪贴板的操作。这个库提供了一种简单的方式来处理剪贴板操作,抽象了底层的细节,使得开发者可以更轻松地实现文本复制功能。与直接使用原生 JavaScript API(如 navigator.clipboard.writeText)相比,它有一些独特的优势。

copy-to-clipboard 的优势

  1. 简化使用:

    • copy-to-clipboard 提供了一个简单的 API,只需调用一个函数即可完成复制操作,而不需要处理剪贴板 API 的兼容性问题创建临时元素等步骤。
  2. 兼容性:

    • 库通常会处理不同浏览器和环境下的兼容性问题,包括老旧浏览器不支持 navigator.clipboard 的情况。例如,如果浏览器不支持现代剪贴板 API,库可能会回退到使用传统方法(如临时 <textarea>)。
  3. 额外功能:

    • 一些第三方库可能提供额外的功能,如对复制事件的监听、错误处理的改进,或与其他库(如 UI 库)的集成等。
  4. 更少的代码:

    • 使用第三方库可以减少自定义实现的代码量,简化开发流程。例如,你不需要自己处理剪贴板操作的细节。
  5. 更好的错误处理:

    • 库通常提供统一的错误处理机制,使得错误处理更为简洁和一致。

copy-to-clipboard 库的使用示例

安装 copy-to-clipboard 库:

npm install copy-to-clipboard

在 React 项目中使用:

import React from 'react';
import copy from 'copy-to-clipboard';

function App() {
    const textToCopy = '这是要复制的文本。';

    const copyText = () => {
        copy(textToCopy);
        alert('文本已复制到剪贴板');
    };

    return (
        <div>
            <span>{textToCopy}</span>
            <button onClick={copyText}>复制</button>
        </div>
    );
}

export default App;

对比原生实现和使用第三方库

  1. 原生实现:

    • 优点: 不依赖外部库,适用于小型项目或对性能要求极高的场景。
    • 缺点: 需要处理兼容性问题和低层实现细节。
  2. 使用第三方库:

    • 优点: 使用简便、兼容性好、功能扩展方便。适合中到大型项目,尤其是在需要处理多个浏览器兼容性或自定义行为时。
    • 缺点: 依赖外部库,可能引入额外的包体积和维护开销。

选择建议

  • 小型项目或不希望引入额外依赖: 直接使用原生 JavaScript API(如 navigator.clipboard.writeText)。
  • 中大型项目或需要处理多浏览器兼容性: 使用第三方库如 copy-to-clipboard,以减少开发复杂度和提高代码的可维护性。

总的来说,第三方库可以简化开发工作,但是否使用取决于你的项目需求和对外部依赖的接受程度。

源码分析

源码地址:https://github.com/sudodoki/copy-to-clipboard/blob/main/index.js

这段代码是一个用于在网页中复制文本到剪贴板的 JavaScript 函数。它包含了对不同浏览器(特别是旧版 Internet Explorer)的兼容性处理,以及处理用户自定义选项的能力。让我们逐步分析这个函数的实现细节:

1. 引入依赖

var deselectCurrent = require("toggle-selection");
  • 该行代码引入了 toggle-selection 模块,用于在复制操作前取消选中的内容。这在需要确保复制操作不受当前页面选择影响时非常有用。

2. 定义 IE11 格式映射

var clipboardToIE11Formatting = {
  "text/plain": "Text",
  "text/html": "Url",
  "default": "Text"
}
  • 这部分定义了一个对象 clipboardToIE11Formatting,用于将 MIME 类型映射到 Internet Explorer 11 的剪贴板数据格式。这是因为 IE11 使用不同的格式来处理剪贴板数据。

3. 默认消息格式

var defaultMessage = "Copy to clipboard: #{key}, Enter";
  • 这是一个默认的提示消息,用于告知用户如何进行复制操作(例如,使用 Ctrl+C⌘+C)。

4. 格式化消息函数

function format(message) {
  var copyKey = (/mac os x/i.test(navigator.userAgent) ? "⌘" : "Ctrl") + "+C";
  return message.replace(/#{\s*key\s*}/g, copyKey);
}
  • format 函数用于将消息中的占位符 #{key} 替换为适合当前操作系统的复制快捷键(Ctrl+C⌘+C)。

5. copy 函数

function copy(text, options) {
  var debug,
    message,
    reselectPrevious,
    range,
    selection,
    mark,
    success = false;
  • 初始化变量。其中 success 变量用于指示复制操作是否成功。
  if (!options) {
    options = {};
  }
  debug = options.debug || false;
  • 如果 options 没有提供,则初始化为空对象。并从 options 中读取是否启用调试模式。
  try {
    reselectPrevious = deselectCurrent();

    range = document.createRange();
    selection = document.getSelection();
  • try 块中,首先取消当前选中内容(deselectCurrent)。然后创建一个新的 RangeSelection 对象,用于选择要复制的内容。
    mark = document.createElement("span");
    mark.textContent = text;
    mark.ariaHidden = "true";
    mark.style.all = "unset";
    mark.style.position = "fixed";
    mark.style.top = 0;
    mark.style.clip = "rect(0, 0, 0, 0)";
    mark.style.whiteSpace = "pre";
    mark.style.webkitUserSelect = "text";
    mark.style.MozUserSelect = "text";
    mark.style.msUserSelect = "text";
    mark.style.userSelect = "text";
  • 创建一个隐藏的 span 元素 (mark),并将要复制的文本放入其中。这个元素会被添加到页面中,但其样式被设置为隐藏,以避免影响用户界面。
    mark.addEventListener("copy", function(e) {
      e.stopPropagation();
      if (options.format) {
        e.preventDefault();
        if (typeof e.clipboardData === "undefined") {
          window.clipboardData.clearData();
          var format = clipboardToIE11Formatting[options.format] || clipboardToIE11Formatting["default"];
          window.clipboardData.setData(format, text);
        } else {
          e.clipboardData.clearData();
          e.clipboardData.setData(options.format, text);
        }
      }
      if (options.onCopy) {
        e.preventDefault();
        options.onCopy(e.clipboardData);
      }
    });
  • mark 元素添加 copy 事件监听器。根据浏览器是否支持 e.clipboardData,它会分别处理现代浏览器和 IE11 的剪贴板数据。并且,如果 options.onCopy 存在,则调用它。
    document.body.appendChild(mark);

    range.selectNodeContents(mark);
    selection.addRange(range);

    var successful = document.execCommand("copy");
    if (!successful) {
      throw new Error("copy command was unsuccessful");
    }
    success = true;
  • mark 元素添加到 document.body,并选择其内容进行复制。然后调用 document.execCommand("copy") 进行复制。如果失败,则抛出错误。
  } catch (err) {
    debug && console.error("unable to copy using execCommand: ", err);
    debug && console.warn("trying IE specific stuff");
    try {
      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) {
      document.body.removeChild(mark);
    }
    reselectPrevious();
  }

  return success;
}
  • catch 块用于处理 execCommand 失败的情况,尝试使用 window.clipboardData 进行复制。如果这也失败,则使用 window.prompt 作为最后的备选方案。finally 块用于清理选择和移除 mark 元素,并恢复之前的选择状态。

总结

这段代码实现了一个具有广泛兼容性的剪贴板复制功能,包括处理现代浏览器和旧版 IE11 的不同方法。它的工作流程如下:

  1. 取消当前选中的内容。
  2. 创建一个隐藏的 span 元素,并将要复制的文本放入其中。
  3. 使用 document.execCommand("copy") 尝试复制文本。
  4. 如果复制失败,则尝试使用 window.clipboardData 进行复制。
  5. 如果所有方法都失败,则使用 window.prompt 作为最后的备用方案。
  6. 清理选择和移除临时 span 元素。

这个实现确保了在大多数浏览器环境中都能成功进行复制操作,同时也提供了调试信息和兼容旧版 IE 的处理。

拓展:document.createRange() & document.getSelection()

document.createRange()document.getSelection() 是 Web API 中的两个重要方法,用于处理文档中的文本选择和范围操作。它们主要用于实现文本选择、范围设置、以及执行与选定范围相关的操作(如复制)。

1. document.createRange()
  • 功能: 创建一个新的 Range 对象。
  • 作用: Range 对象表示文档中的一段连续区域,可以用来对该区域进行各种操作,如选择文本、操作 DOM 元素等。

常见用法:

  1. 创建范围:

    var range = document.createRange();
    
  2. 设置范围的起始和结束位置:

    range.setStart(startNode, startOffset);
    range.setEnd(endNode, endOffset);
    
    • setStart(node, offset): 设置范围的开始位置。
    • setEnd(node, offset): 设置范围的结束位置。
    • startNodeendNode 是要设置的 DOM 节点,startOffsetendOffset 是相对于这些节点的字符位置。
  3. 选择范围的内容:

    range.selectNodeContents(node);
    
    • selectNodeContents(node): 选择节点 node 内的所有内容。
  4. 创建一个 DocumentFragment:

    var fragment = range.cloneContents();
    
    • cloneContents(): 克隆范围内的内容并返回一个 DocumentFragment
2. document.getSelection()
  • 功能: 返回当前文档的 Selection 对象。
  • 作用: Selection 对象表示用户当前在文档中选择的内容,允许你获取、修改或取消这些选择。

常见用法:

  1. 获取当前选择:

    var selection = document.getSelection();
    
  2. 添加或移除范围:

    selection.addRange(range); // 添加一个范围到当前选择中
    selection.removeRange(range); // 从当前选择中移除一个范围
    selection.removeAllRanges(); // 清除所有选择
    
  3. 获取选择的内容:

    var selectedText = selection.toString();
    
  4. 检查是否有选择:

    if (selection.rangeCount > 0) {
        // 有选择
    }
    
3. 在复制操作中的使用

copy 函数中,这两个方法用于确保文本正确地被选中,以便可以通过 document.execCommand("copy") 将其复制到剪贴板。

  1. 创建和设置范围:

    range = document.createRange();
    range.selectNodeContents(mark);
    
    • 这里创建一个新的 Range 对象,并设置其范围为 mark 元素的所有内容。mark 是一个临时创建的 span 元素,其中包含了要复制的文本。
  2. 获取和操作选择:

    selection = document.getSelection();
    selection.addRange(range);
    
    • 获取当前的 Selection 对象,并将之前创建的 Range 添加到选择中。这意味着,mark 元素的内容现在被选中。
4. 总结
  • document.createRange() 创建一个 Range 对象,该对象表示文档中的一段连续区域。
  • document.getSelection() 返回当前的 Selection 对象,表示用户选择的内容或可以操作的内容。

在处理文本选择和复制操作时,这两个 API 提供了必要的功能,使得可以精确地控制和操作选中的文本区域。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@PHARAOH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值