实现一键复制粘贴踩坑实践

2,645 阅读5分钟

最近在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>
     )
    }
    
    1. 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
    1. 使用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>
        </>
    )
 }
最后,由于以上代码都是现敲的,如果有错误的地方望大佬们指出,我会及时更正!