JS 复制文本基本分为两步 - First: 选中需要复制的节点, 及选区; Second: 执行 document.execCommand('copy')命令复制
对于选区, 属于 htmlInputElement 的 < textarea> 和 < input > 元素支持 element.select()方法
- <div id="test1">
- <input type="text" placeholder="你能复制我的人, 但不能复制我的心" /><button>
点击复制文字
- </button>
- </div>
- // 复制输入框文字
- const target1 = document.querySelector('#test1>input')
- const btn1 = document.querySelector('#test1>button')
- btn1.addEventListener(
- 'click',
- () => {
- target1.select() // 选中输入框中文字, 这个方法执行后没有返回值
- document.execCommand('copy') // 复制成功则返回值为 true
- target1.blur() // 解除文字选中状态
- },
- false
- )

其他元素不支持 select()方法, 但是浏览器也提供了相关选区的 API, 主要借助 document.createRange()和 document.getSelection()这两个方法同时也存在于 Windows 对象上
- <div id="test2">
- <span > 你能复制我的人, 但不能带走我的心</span>
- <button > 点击复制文字</button>
- </div>
- // 复制任意节点文字
- const target2 = document.querySelector('#test2>span')
- const btn2 = document.querySelector('#test2>button')
- btn2.addEventListener(
- 'click',
- () => {
- const range = document.createRange()
- const selection = document.getSelection()
- range.selectNodeContents(target2)
- selection.removeAllRanges()
- selection.addRange(range)
- document.execCommand('copy')
- // 清除选中状态
- selection.removeAllRanges()
- },
- false
- )
如果我们想自定义复制的内容, 该怎么办呢? 我们可以临时创建一个节点, 把需要复制的内容作为节点内容, 然后使用上面提到的方法进行复制, 如下面的两种方法都是可行的.
- <div id="test3">
- <button > 点击复制变量</button>
- </div>
- // 复制自定义文字 方法一
- const target3 = '你能复制我的人, 但不能带走我的心 eee'
- const btn3 = document.querySelector('#test3>button')
- btn3.addEventListener(
- 'click',
- () => {
- const range = document.createRange()
- const selection = document.getSelection()
- const fake = document.createElement('span')
- // 重置样式
- fake.style.all = 'unset'
- fake.textContent = target3
- // fixed 定位防止选中节点时页面滚动到页面底部
- fake.style.position = 'fixed'
- fake.style.top = 0
- // 让这个临时元素不可见, 注意不能使用 display: none 和 visibility: hidden, 会导致元素选不中
- fake.style.clip = 'rect(0, 0, 0, 0)'
- // 保存空格和换行
- fake.style.whiteSpace = 'pre'
- document.body.appendChild(fake)
- range.selectNodeContents(fake)
- // 清除已有选区
- selection.removeAllRanges()
- selection.addRange(range)
- document.execCommand('copy')
- document.body.removeChild(fake)
- },
- false
- )
上述代码中的注释部分都是非常重要的, 网上很多教程的实现方式都有缺陷, 本文实现的方法是从 https://github.com/sudodoki/copy-to-clipboard 抄袭的, 基本考虑到了很多异常情况. 不过我个人觉得使用 textarea 元素作为中转节点是更好的选择, 起兼容性更好, 结果可控.
- <div id="test4">
- <button > 点击复制变量</button>
- </div>
- // 复制自定义文字 方法二
- const target4 = '你能复制我的人, 但不能带走我的心'
- const btn4 = document.querySelector('#test4>button')
- btn4.addEventListener(
- 'click',
- () => {
- const fake = document.createElement('textarea')
- // 防止弹出键盘
- fake.setAttribute('readonly', 'readonly')
- fake.value = target4
- fake.style.position = 'fixed'
- fake.style.top = 0
- fake.style.clip = 'rect(0, 0, 0, 0)'
- document.body.appendChild(fake)
- fake.select()
- document.execCommand('copy')
- document.body.removeChild(fake)
- },
- false
- )
为啥不使用 input 元素呢, 因为 textarea 能保持换行等.
上述方法在 React 组件中也是可以使用的, 不过在使用过程中很多人发现一个问题 document.execCommand ('copy') don't work in React, 这个问题一开始我也遇到了, 有人回答是因为 document.execCommand ('copy')不能在 React 的合成事件中调用, 只能直接在原生事件中调用, 其实这是不对的, https://github.com/nkbt/react-copy-to-clipboard 这个组件不也是在合成事件中调用这个命令吗? 经过不断尝试, 我发现问题出现在一个小的细节上, 我们一开始通过 const selection = document.getSelection()获取选区操作对象, 默认我们添加选区前, 选区信息应该为空, 可以使用下面的语句打印信息
console.log('选区个数', selection.rangeCount, '选区对象', selection)

需要注意的是 selection 对象是不能深克隆的, 在控制台上如果展开对象打印结果显示的结果是 selection 最终的结果 (如果后面修改过 selection 的话) 而不是打印语句时的结果
这个结果在上面几个原生 JS 的例子中都适用. 但是在 React 的事件回调中, 通过
ocument.getSelection()
获取的默认选区却不为空, 如果把我们创建的节点直接塞进去, 再执行
document.getSelection()
是不会复制成功的.

解决方法很简单, 在添加选区前清除选区, 不过我发现如果使用 textarea 作为中转节点则不存在这个问题.
- if (selection.rangeCount> 0) {
- selection.removeAllRanges()
- }
- import React, { Component } from 'react'
- class App extends Component {
- render() {
- return (
- <div className="App">
- <h1
- onClick={e => {
- const range = document.createRange()
- const selection = document.getSelection()
- const mark = document.createElement('span')
- mark.textContent = 'text to copy'
- // reset user styles for span element
- mark.style.all = 'unset'
- // prevents scrolling to the end of the page
- mark.style.position = 'fixed'
- mark.style.top = 0
- mark.style.clip = 'rect(0, 0, 0, 0)'
- // used to preserve spaces and line breaks
- 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'
- mark.style.userSelect = 'text'
- mark.addEventListener('copy', function(e) {
- e.stopPropagation()
- })
- document.body.appendChild(mark)
- // The following line is very important
- if (selection.rangeCount> 0) {
- selection.removeAllRanges()
- }
- range.selectNodeContents(mark)
- selection.addRange(range)
- document.execCommand('copy')
- document.body.removeChild(mark)
- }}
- >
- Click to Copy Text
- </h1>
- </div>
- )
- }
- }
- export default App
- import React, { Component } from 'react'
- class App extends Component {
- render() {
- return (
- <div className="App">
- <h1
- onClick={e => {
- const mark = document.createElement('textarea')
- // 防止移动端键盘弹出
- mark.setAttribute('readonly', 'readonly')
- mark.value = 'copy me'
- mark.style.position = 'fixed'
- mark.style.top = 0
- mark.style.clip = 'rect(0, 0, 0, 0)'
- document.body.appendChild(mark)
- mark.select()
- document.execCommand('copy')
- document.body.removeChild(mark)
- }}
- >
- Click to Copy Text
- </h1>
- </div>
- )
- }
- }
- export default App
最后再补充一点, 使用上面的方法复制图片是否可行呢,
- <div id="test">
- <img
- src="https://www.zxing.top/media/15546249493670/15547312147294.jpg"
- alt=""
- />
- <button > 点击复制文 x 字</button>
- </div>
- const target = document.querySelector('#test>img')
- const btn = document.querySelector('#test>button')
- btn.addEventListener(
- 'click',
- () => {
- const range = document.createRange()
- const selection = document.getSelection()
- selection.removeAllRanges()
- // range.selectNodeContents(target)
- range.selectNode(target)
- selection.addRange(range)
- document.execCommand('copy')
- selection.removeAllRanges()
- },
- false
- )
注意上面的代码修改了一处, range.selectNode(target), 上面的代码我测试了一下, 可以复制图片, 但是复制的是某种 HTML 对象, 走一些编辑器上是可以粘贴的, 但不保障兼容性, 我测试了 Mac 上的 Page 应用, 印象笔记和手机上的 wps 是可以直接粘贴的.
来源: https://www.cnblogs.com/star91/p/javascript-fu-zhi-wen-ben-tan-jiu.html