一、前言
这个是一个简单的场景,也是一个非常常见的产品需求。
那在原生 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. 说明
- 创建一个临时的
<textarea>
元素,并将要复制的文本设置为其值。 - 选择该文本区域中的内容,并执行
document.execCommand('copy')
进行复制。 - 移除临时文本区域,并用
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. 说明
- 使用
navigator.clipboard.writeText
API 来将文本复制到剪贴板,这是一种现代的方式,支持 Promise。 - 复制成功后,显示
alert
提示用户文本已复制。 catch
块用于处理可能的错误,例如在不支持剪贴板 API 的浏览器中。
三、clipboard API
官方文档:https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API
navigator.clipboard.writeText
方法允许你将文本直接写入剪贴板。这是一种比传统 document.execCommand('copy')
更现代、更可靠的方法,因为它返回一个 Promise,可以方便地处理成功和失败的情况。
-
简化的语法:
- 通过使用
navigator.clipboard.writeText
,你可以避免使用传统方法中的额外步骤,如创建临时<textarea>
元素、选择内容等。
- 通过使用
-
Promise 支持:
- 使用
navigator.clipboard.writeText
返回一个 Promise,使得处理成功和失败的操作更加直观。你可以使用then
和catch
处理复制操作的结果。
- 使用
-
安全和权限:
- 剪贴板 API 具有更好的安全性和权限管理,能够更好地控制访问权限。浏览器会提示用户有关剪贴板访问的权限请求。
四、第三方库
推荐使用
后者是基于前者封装的 React 组件。我们直接来学习前者的源码做了什么。
copy-to-clipboard 介绍
copy-to-clipboard
是一个流行的第三方库,用于简化在网页应用中复制文本到剪贴板的操作。这个库提供了一种简单的方式来处理剪贴板操作,抽象了底层的细节,使得开发者可以更轻松地实现文本复制功能。与直接使用原生 JavaScript API(如 navigator.clipboard.writeText
)相比,它有一些独特的优势。
copy-to-clipboard
的优势
-
简化使用:
copy-to-clipboard
提供了一个简单的 API,只需调用一个函数即可完成复制操作,而不需要处理剪贴板 API 的兼容性问题或创建临时元素等步骤。
-
兼容性:
- 库通常会处理不同浏览器和环境下的兼容性问题,包括老旧浏览器不支持
navigator.clipboard
的情况。例如,如果浏览器不支持现代剪贴板 API,库可能会回退到使用传统方法(如临时<textarea>
)。
- 库通常会处理不同浏览器和环境下的兼容性问题,包括老旧浏览器不支持
-
额外功能:
- 一些第三方库可能提供额外的功能,如对复制事件的监听、错误处理的改进,或与其他库(如 UI 库)的集成等。
-
更少的代码:
- 使用第三方库可以减少自定义实现的代码量,简化开发流程。例如,你不需要自己处理剪贴板操作的细节。
-
更好的错误处理:
- 库通常提供统一的错误处理机制,使得错误处理更为简洁和一致。
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;
对比原生实现和使用第三方库
-
原生实现:
- 优点: 不依赖外部库,适用于小型项目或对性能要求极高的场景。
- 缺点: 需要处理兼容性问题和低层实现细节。
-
使用第三方库:
- 优点: 使用简便、兼容性好、功能扩展方便。适合中到大型项目,尤其是在需要处理多个浏览器兼容性或自定义行为时。
- 缺点: 依赖外部库,可能引入额外的包体积和维护开销。
选择建议
- 小型项目或不希望引入额外依赖: 直接使用原生 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
)。然后创建一个新的Range
和Selection
对象,用于选择要复制的内容。
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 的不同方法。它的工作流程如下:
- 取消当前选中的内容。
- 创建一个隐藏的
span
元素,并将要复制的文本放入其中。 - 使用
document.execCommand("copy")
尝试复制文本。 - 如果复制失败,则尝试使用
window.clipboardData
进行复制。 - 如果所有方法都失败,则使用
window.prompt
作为最后的备用方案。 - 清理选择和移除临时
span
元素。
这个实现确保了在大多数浏览器环境中都能成功进行复制操作,同时也提供了调试信息和兼容旧版 IE 的处理。
拓展:document.createRange() & document.getSelection()
document.createRange()
和 document.getSelection()
是 Web API 中的两个重要方法,用于处理文档中的文本选择和范围操作。它们主要用于实现文本选择、范围设置、以及执行与选定范围相关的操作(如复制)。
1. document.createRange()
- 功能: 创建一个新的
Range
对象。 - 作用:
Range
对象表示文档中的一段连续区域,可以用来对该区域进行各种操作,如选择文本、操作 DOM 元素等。
常见用法:
-
创建范围:
var range = document.createRange();
-
设置范围的起始和结束位置:
range.setStart(startNode, startOffset); range.setEnd(endNode, endOffset);
setStart(node, offset)
: 设置范围的开始位置。setEnd(node, offset)
: 设置范围的结束位置。startNode
和endNode
是要设置的 DOM 节点,startOffset
和endOffset
是相对于这些节点的字符位置。
-
选择范围的内容:
range.selectNodeContents(node);
selectNodeContents(node)
: 选择节点node
内的所有内容。
-
创建一个
DocumentFragment
:var fragment = range.cloneContents();
cloneContents()
: 克隆范围内的内容并返回一个DocumentFragment
。
2. document.getSelection()
- 功能: 返回当前文档的
Selection
对象。 - 作用:
Selection
对象表示用户当前在文档中选择的内容,允许你获取、修改或取消这些选择。
常见用法:
-
获取当前选择:
var selection = document.getSelection();
-
添加或移除范围:
selection.addRange(range); // 添加一个范围到当前选择中 selection.removeRange(range); // 从当前选择中移除一个范围 selection.removeAllRanges(); // 清除所有选择
-
获取选择的内容:
var selectedText = selection.toString();
-
检查是否有选择:
if (selection.rangeCount > 0) { // 有选择 }
3. 在复制操作中的使用
在 copy
函数中,这两个方法用于确保文本正确地被选中,以便可以通过 document.execCommand("copy")
将其复制到剪贴板。
-
创建和设置范围:
range = document.createRange(); range.selectNodeContents(mark);
- 这里创建一个新的
Range
对象,并设置其范围为mark
元素的所有内容。mark
是一个临时创建的span
元素,其中包含了要复制的文本。
- 这里创建一个新的
-
获取和操作选择:
selection = document.getSelection(); selection.addRange(range);
- 获取当前的
Selection
对象,并将之前创建的Range
添加到选择中。这意味着,mark
元素的内容现在被选中。
- 获取当前的
4. 总结
document.createRange()
创建一个Range
对象,该对象表示文档中的一段连续区域。document.getSelection()
返回当前的Selection
对象,表示用户选择的内容或可以操作的内容。
在处理文本选择和复制操作时,这两个 API 提供了必要的功能,使得可以精确地控制和操作选中的文本区域。