最近在react项目开发中有遇到实现一键复制的需求,也有百度到两种实现一键复制粘贴的实例,但是这两种方法都有一定的局限性,不能完全满足项目需求,我希望将需要复制的内容放在一个input框里然后点击复制按钮的时候可以选中输入框中的内容(网上的实现一键复制的方法是将需要复制的内容放在一个div标签中,这样的话如果内容过长就不能像input框中的内容可以拖拽预览)
一、首先介绍一下实现一键复制需要用到的几个原生Api
-
1.Range 构造器是一个webApi的接口,用于表示包含节点与文本节点的一部分的文档片段,有两种创建的方式, 一种是通过document.createRang()的方法创建, 一种是通过window.getSelection().getRangeAt(0)的方式创建,以下是在一键复制功能实现过程中会用到,或者可能会用到的API:
- range.selectNode(node): 将节点的以及节点中的内容设置为range的内容,接收一个参数node(range内选择的方法), 这个方法将会使range开始于结束的父节点与传入参数node的父节点相同;
- range.selectNodeContents(node): 将节点的内容设置为range的内容,接收一个参数node(需要选择内容的节点),这个方法会是range的开始于结束的父节点为node;
- range.toString(): 将range的内容转化为字符串;
// selectNode方法的使用 import React, { useRef } from 'react' const CopyDemo = (props) => { const copyRef = useRef(); const handelCopy = () => { let val = copyRef.current; const range = document.createRange(); range.selectNode(val); // range的父节点是传入Node节点的父节点,可以打印出来看下range对象下的startContainer和endContainer console.log(range); ..... } return ( <div ref={copyRef}> <p>内容一</p> <p>内容二</p> <p>内容三</p> </div> <button onClick={handelCopy}>一键复制</button> ) }// selectNodeContents方法的使用 import React, { useRef } from 'react' const CopyDemo = (props) => { const copyRef = useRef(); const handelCopy = () => { let val = copyRef.current; const range = document.createRange(); range.selectNodeContents(val); // range的父节点是传入Node节点的父节点,可以打印出来看下range对象下的startContainer和endContainer,与selectNode方法的区别就是endOffset(引用节点中包含子节点的数目)不同 console.log(range); ..... } return ( <div ref={copyRef}> <p>内容一</p> <p>内容二</p> <p>内容三</p> </div> <button onClick={handelCopy}>一键复制</button> ) } -
- Selection 对象表示用户选择的文本范围活插入符号的位置,通过调用window.getSelection()方法获取到用户选择的文本范围活插入符号的当前位置,以下介绍几个一键复制功能中使用到的Api:
- addRange(range): 将range的内容添加到选择器中;
- removeRange(range): 从选择器中清除range;
- removeAllRanges(): 从选择器中清除所有的range;
- selectAllChildren(parentNode): 选择节点下的所有内容,parentNode下的所有内容都会被选中,但是选中的内容中并不包含parentNode本身,该方法的参数必须滴DOM 节点,不能支持传入range对象;
- getRangeAt(index): 该方法返回一个range对象,表示当前选定的一个范围,参数index是要选定范围的起始索引
import React, { useRef } from 'react';
import './style.less';
const TopBar = (props) => {
const copyRef = useRef()
const handelCopyClick = () => {
let val = copyRef.current;
console.log(val);
const range = document.createRange();
range.selectNode(val);
let selection = window.getSelection()
// selection.addRange(range);
// range的内容会被添加selection的anchorNode对象下;
// console.log(selection);
selection.selectAllChildren(val);
console.log(selection);
selection.removeRange(range);
selection.removeAllRange();
}
return (
<div>
<div ref={copyRef}>
<p>内容一</p>
<p>内容二</p>
<p>内容三</p>
<p>内容四</p>
</div>
<button onClick={handelCopyClick}>一键复制</button>
</div>
)
}
export default TopBar;
- 3.execCommand: 当HTML的文档被切换到designMode时,document对象会向外暴露一个execCommand方法用于运行操作当前编辑区域的命令,以下是一些Api:
- copy: 将当前内容复制到剪贴板
- cut: 删除当前内容并复制到剪贴板
- createLink: 仅在存在内容时,将当前内容设置为超链接
import React, { useRef } from 'react';
const TopBar = (props) => {
const copyRef = useRef()
const handelCopyClick = () => {
let val = copyRef.current;
// const range = document.createRange();
// range.selectNode(val);
let selection = window.getSelection()
// selection.addRange(range);
// range的内容会被添加selection的anchorNode对象下;
// console.log(selection);
selection.selectAllChildren(val);
console.log(selection)
// 复制
const res = document.execCommand('copy');
// 剪切
// const res = document.execCommand('cut');
// 设为超链接
// const res = document.execCommand('createLink');
}
return (
<div>
<div ref={copyRef}>
<p>内容一</p>
<p>内容二</p>
<p>内容三</p>
<p>内容四</p>
</div>
<button onClick={handelCopyClick}>一键复制</button>
</div>
)
}
export default TopBar;
2.接下来就说一下实现一键复制的几种方法
- 1.将需要复制的内容放在div中
import React, { useRef } from 'react';
import { message, Button } from 'antd';
const CopyDemo = (props) => {
const copyRef = useRef();
const copy = () => {
const value = copyRef.current;
window.getSelection.selectAllChildren(value);
const res = document.execCommand('copy');
if(res) {
message.success('复制成功,快去粘贴吧!');
} else {
message.warning('复制失败,请手动复制!');
}
}
return (
<>
<div>http://www.baidu.com</div>
<Button type="primary" onClick={copy}>一键复制</Button>
</>
)
}
- 2.将需要复制的内容放在Input输入框中,此时Input框是禁用状态,是不可以显示选中效果的,此时可以用Range来实现
import React, { useRef } from 'react';
import { message, Button, Input } from 'antd';
const CopyDemo = (props) => {
const copyRef = useRef();
const copy = () => {
const value = copyRef.current;
const range = document.createRange();
// 首先需要清除所有的range
window.getSelection.removeAllRanges();
// 将需要复制的内容添加到range中
range.selectNode(value);
// 将range的内容添加到选择器中
window.getSelection.addRange(range);
// 执行复制的命令
const res = document.execCommand('copy');
if(res) {
message.success('复制成功,快去粘贴吧!');
} else {
message.warning('复制失败,请手动复制!');
}
// 执行完毕之后再次清除所有的range
window.getSelection.removeAllRanges();
}
return (
<>
<Input ref={copyRef} disabled />
<Button type="primary" onClick={copy}>一键复制</Button>
</>
)
}
这样实现的效果并不理想,一方面是没有选中的效果,另一方面是复制出来的内容有很明显的空格,目前我还没能找出基于 以上代码去除前后空格的方法,而是想了另一种方案,接下来回详细介绍,
值得注意的是,当我们想把input或者是textarea的内容用第二种方法复制时会报错,此时不要怀疑是自己代码写错了,主要原因是getSelection()方法对这两个标签不支持,可以尝试使用HTMLInputElement.setSelectionRange(selectionStart, selectionEnd)代替,这里如果大家感兴趣的话可以查看文档
https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange
-
- 使用input的特性实现一键复制
import React, { useRef } from 'react';
import { message, Input, Button } from 'antd';
const CopyDemo = (props) => {
const copyRef = useRef();
const copy = () => {
const val = copyRef.current;
val.select();
const res = document.execCommand('copy');
if(res) {
message.success('复制成功, 快去粘贴吧!');
} else {
message.warning('复制失败, 请手动复制!');
}
}
// 将输入框设置为禁用状态
const handelInputChange = () => {
const val = copyRef.current;
val.blur();
return false;
}
return (
<>
<Input ref={copyRef} value="http://www.baidu.com" onChange={handelInputChange} />
<Button type="primary" onClick={copy}>一键复制</Button>
</>
)
}