在ReactJS中,如何将文本复制到剪贴板?

466

我正在使用ReactJS,当用户点击链接时,我想要将一些文本复制到剪贴板。

我正在使用Chrome 52,不需要支持其他任何浏览器。

我看不出来为什么这段代码没有将数据复制到剪贴板。(代码片段来源于Reddit帖子。)

我做错了吗?有人能建议实现使用ReactJS复制到剪贴板的“正确”方法吗?

copyToClipboard = (text) => {
  console.log('text', text)
  var textField = document.createElement('textarea')
  textField.innerText = text
  document.body.appendChild(textField)
  textField.select()
  document.execCommand('copy')
  textField.remove()
}

1
你是否尝试过使用第三方解决方案,比如 https://clipboardjs.com/ 或 https://github.com/zeroclipboard/zeroclipboard? - EugZol
54
我更喜欢编写代码而不是添加另一个依赖项,前提是代码相当简短。 - Duke Dougal
请查看这些答案:https://dev59.com/fHRC5IYBdhLWcg3wJNcN - elmeister
@elmeister 这个问题是关于ReactJS的。 - Duke Dougal
我们难道只是为了复制而使用库吗?真的吗? - codemon
31个回答

747

如果您想要以编程方式将数据写入剪贴板,请在按钮上使用此简单的内联 onClick 函数。

onClick={() => {navigator.clipboard.writeText(this.state.textToCopy)}}

35
似乎在2018年主流的浏览器已经很好地支持了它。https://caniuse.com/#search=clipboard - gasolin
5
根据你提供的链接,看起来它只在Safari浏览器中完全支持。 - Nibb
6
我会尽力为您翻译:适合我使用情况的最佳方案是,即使要复制的文本并不在页面上也能复制。谢谢。 - NSjonas
35
在Chrome上遇到了困难,navigator.clipboard 只有在页面来源于安全环境(即HTTPS或localhost)时才能起作用。如果需要检查,可以使用 window.isSecureContext。请注意,翻译过程中尽量保持原意不变,使内容易懂且简洁明了。 - aryanm
6
2021年,这应该是可以接受的答案。对于 navigator.clipboard 的支持已经足够普及了。 - basse
显示剩余13条评论

272

就我个人而言,我不认为需要专门为此编写一个库。查看 http://caniuse.com/#feat=clipboard ,现在已经得到了广泛的支持,但是您仍然可以执行诸如检查当前客户端中是否存在该功能,并在不存在时简单地隐藏复制按钮等操作。

import React from 'react';

class CopyExample extends React.Component {

  constructor(props) {
    super(props);

    this.state = { copySuccess: '' }
  }

  copyToClipboard = (e) => {
    this.textArea.select();
    document.execCommand('copy');
    // This is just personal preference.
    // I prefer to not show the whole text area selected.
    e.target.focus();
    this.setState({ copySuccess: 'Copied!' });
  };

  render() {
    return (
      <div>
        {
         /* Logical shortcut for only displaying the 
            button if the copy command exists */
         document.queryCommandSupported('copy') &&
          <div>
            <button onClick={this.copyToClipboard}>Copy</button> 
            {this.state.copySuccess}
          </div>
        }
        <form>
          <textarea
            ref={(textarea) => this.textArea = textarea}
            value='Some text to copy'
          />
        </form>
      </div>
    );
  }

}
    
export default CopyExample;

更新:在 React 16.7.0-alpha.0 中使用 React Hooks 进行重写

import React, { useRef, useState } from 'react';

export default function CopyExample() {

  const [copySuccess, setCopySuccess] = useState('');
  const textAreaRef = useRef(null);

  function copyToClipboard(e) {
    textAreaRef.current.select();
    document.execCommand('copy');
    // This is just personal preference.
    // I prefer to not show the whole text area selected.
    e.target.focus();
    setCopySuccess('Copied!');
  };

  return (
    <div>
      {
       /* Logical shortcut for only displaying the 
          button if the copy command exists */
       document.queryCommandSupported('copy') &&
        <div>
          <button onClick={copyToClipboard}>Copy</button> 
          {copySuccess}
        </div>
      }
      <form>
        <textarea
          ref={textAreaRef}
          value='Some text to copy'
        />
      </form>
    </div>
  );
}

4
仅供参考:唯一的问题是,如果你想复制的文本不是页面上已经存在的文本元素,则需要通过修改DOM元素来设置文本、复制并清理它。这是为了一件小事情而需要很多代码。通常情况下,我同意不鼓励开发者频繁安装库。 - Christopher Ronning
3
对于这个特定的问题,文本已经在页面上的一个元素中。如果页面上有可见文本想要复制,但不在一个元素中,你会遇到什么情况?那就完全是另外一个问题了,我很乐意展示解决方案。你不需要使用 react 进行任何黑客技巧,只需要在你的渲染函数中提供一个隐藏的元素来保存文本即可,不需要临时创建元素。 - Nate
2
我遇到了这个 TypeScript 错误:属性“select”在类型“never”上不存在 - Alex C
4
我收到了“TypeError: textAreaRef.current.select不是函数”的错误提示。 - pseudozach
17
这个现在已经过时了。https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand - Benjamin
显示剩余7条评论

136

您可以在不使用外部库的情况下完成此操作,例如在按钮内

<button 
  onClick={() =>  navigator.clipboard.writeText('Copy this text to clipboard')}
>
  Copy
</button>

对于Internet Explorer 11及更早版本的浏览器,你可能需要稍微修改代码,以下是一个示例:

<button 
  onClick={() =>  window.clipboardData.setData("Text", 'Copy this text to clipboard')}>
 Copy
</button>

1
最好的请求,顺便说一句。 - Edgar Mejía
我更新了答案以支持旧版浏览器!感谢您的评论。 - jerryurenaa
5
我遇到了以下错误:TypeError: Cannot read property 'setData' of undefined - Rodrigo R
获取恐慌:https://codesandbox.io/s/old-sea-0i7o2?file=/src/App.js - user_78361084
1
嗨 @e-info128,这里并没有说 window.navigator.clipboard,而是 navigator.clipboard,所以这就是你的问题所在。 - jerryurenaa
显示剩余2条评论

55

你应该考虑使用像@Shubham建议的包,但我根据你所描述的创建了一个工作的代码笔: http://codepen.io/dtschust/pen/WGwdVN?editors=1111 . 在我的chrome浏览器中它可以正常运行,也许你可以看看是否有什么细节被你忽略了,或者你的应用程序有一些扩展复杂性阻止了这个方法的工作。

// html
<html>
  <body>
    <div id="container">

    </div>
  </body>
</html>


// js
const Hello = React.createClass({
  copyToClipboard: () => {
    var textField = document.createElement('textarea')
    textField.innerText = 'foo bar baz'
    document.body.appendChild(textField)
    textField.select()
    document.execCommand('copy')
    textField.remove()
  },
  render: function () {
    return (
      <h1 onClick={this.copyToClipboard}>Click to copy some text</h1>
    )
  }
})

ReactDOM.render(
<Hello/>,
  document.getElementById('container'))

4
为什么一个包装方案比你的解决方案更好? - Duke Dougal
9
可能会有更好的跨浏览器支持,并且如果需要修复错误,会有更多人关注该软件包。 - Drew Schuster
像魔法般运作。是的。我也在想跨浏览器支持的问题。 - Karl Pokus
如果你使用appendChild,无论你之后多快地将其删除,这会在屏幕上引起闪烁吗? - robinnnnn
1
这很好,但在安卓上的Chrome(72.0)和FF(63.0)上无法运行。 - colin
显示剩余2条评论

50

最简单的方法是使用 react-copy-to-clipboard npm 包。

您可以使用以下命令安装它:

npm install --save react react-copy-to-clipboard

请按以下方式使用。

const App = React.createClass({
  getInitialState() {
    return {value: '', copied: false};
  },


  onChange({target: {value}}) {
    this.setState({value, copied: false});
  },


  onCopy() {
    this.setState({copied: true});
  },


  render() {
    return (
      <div>

          <input value={this.state.value} size={10} onChange={this.onChange} />

        <CopyToClipboard text={this.state.value} onCopy={this.onCopy}>
          <button>Copy</button>
        </CopyToClipboard>

                <div>
        {this.state.copied ? <span >Copied.</span> : null}
                </div>
        <br />

        <input type="text" />

      </div>
    );
  }
});

ReactDOM.render(<App />, document.getElementById('container'));

以下链接提供了详细的解释:

https://www.npmjs.com/package/react-copy-to-clipboard

这里有一个运行中的示例


如果我需要进行反向操作,有没有解决方案?即作者将从电子邮件中复制文本到ReactJS应用程序中的文本区域。我不需要保留HTML标记,但是我只需要保留换行符。 - TechTurtle
1
你可能需要插入 onpaste 事件。 - Koen
如果我想将HTML表格的内容复制到剪贴板中,该如何使用这个包?@Shubham Khatri - Jane Fred

38

使用React Hooks的最佳解决方案,无需外部库。

import React, { useState } from 'react';

const MyComponent = () => {
const [copySuccess, setCopySuccess] = useState('');

// your function to copy here

  const copyToClipBoard = async copyMe => {
    try {
      await navigator.clipboard.writeText(copyMe);
      setCopySuccess('Copied!');
    } catch (err) {
      setCopySuccess('Failed to copy!');
    }
  };

return (
 <div>
    <Button onClick={() => copyToClipBoard('some text to copy')}>
     Click here to copy
     </Button>
  // after copying see the message here
  {copySuccess}
 </div>
)
}

查看有关navigator.clipboard的更多文档,请单击此处 navigator.clipboard文档。大量浏览器支持navigotor.clipboard,点击此处查看支持的浏览器


运行代码片段会抛出错误Uncaught SyntaxError:无法在模块外部使用import语句。 - trainoasis

35

剪贴板在2021年得到了主流浏览器的很好的支持。一种方法是首先构建一个复制到剪贴板的函数,然后使用onClick事件处理程序调用它。

function copy(text){
  navigator.clipboard.writeText(text)
}

为了避免硬编码,假设将 string 赋值给一个名为 someText 的变量。
<span onClick={() => copy(someText)}>
  {someText}
</span>

writeText 返回一个 Promise,所以使用 await 来等待它完成可能是个不错的主意。 - mkupiniak

21

你可以使用事件clipboardData集合的方法e.clipboardData.setData(type, content)

在我看来,这是实现将某些内容放入剪贴板最直接的方法,试试这个方法(我曾在本机复制操作中使用它来修改数据):

...

handleCopy = (e) => {
    e.preventDefault();
    e.clipboardData.setData('text/plain', 'Hello, world!');
}

render = () =>
    <Component
        onCopy={this.handleCopy}
    />

我走的是这条路线:https://developer.mozilla.org/en-US/docs/Web/Events/copy

干杯!

编辑:为了测试目的,我添加了Codepen:https://codepen.io/dprzygodzki/pen/ZaJMKb


3
@KarlPokus 问询者只是在寻找Chrome的解决方案。 - TechTurtle
1
已在Chrome版本62.0.3202.94上测试,它可以正常工作。https://codepen.io/dprzygodzki/pen/ZaJMKb - Damian Przygodzki
2
@OliverDixon 这是 React 事件的默认对象。https://reactjs.org/docs/events.html - Damian Przygodzki
1
@DamianPrzygodzki 我讨厌这样的隐藏元素,这是让开发人员困惑的好方法。 - Oliver Dixon
1
@OliverDixon 我理解你的感受,但我认为习惯有时会应用一些默认数据到方法中是好事,特别是在事件中。 - Damian Przygodzki
显示剩余6条评论

16

我采用了与上述某些方法非常相似的方法,但我认为它更具体。在这里,父组件将url(或您想要的任何文本)作为属性传递。

import * as React from 'react'

export const CopyButton = ({ url }: any) => {
  const copyToClipboard = () => {
    const textField = document.createElement('textarea');
    textField.innerText = url;
    document.body.appendChild(textField);
    textField.select();
    document.execCommand('copy');
    textField.remove();
  };

  return (
    <button onClick={copyToClipboard}>
      Copy
    </button>
  );
};

这很有用,因为我想要段落标签而不是文本区域。 - Ehsan Ahmadi
谢谢!唯一的问题是隐藏文本框。 - notElonMusk

10

以下是另一个用例,如果您想将当前网址复制到剪贴板:

定义一个方法

const copyToClipboard = e => {
  navigator.clipboard.writeText(window.location.toString())
}

调用那个方法

<button copyToClipboard={shareLink}>
   Click to copy current url to clipboard
</button>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接