Як реагувати JS, як скопіювати текст у буфер обміну?


147

Я використовую 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
Чи спробували ви використовувати сторонні рішення, наприклад clipboardjs.com або github.com/zeroclipboard/zeroclipboard ?
EugZol

11
@EugZol Я дійсно вважаю за краще писати код, а не додавати іншу залежність, якщо вважати, що код досить малий.
Герцог Дугал

Перевірте ці відповіді stackoverflow.com/questions/400212 / ...
elmeister

@elmeister питання характерне для reactjs
герцог Дугал

Відповіді:


180

Я особисто не бачу необхідності в цьому бібліотеці. Дивлячись на 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 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 Hooks в React 16.7.0-alpha.0

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 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>
  );
}

26
Це найкраща відповідь. Ми не повинні заохочувати розробників використовувати пакети для кожної дрібниці, якщо їм не потрібна стара підтримка браузера.
tugce

3
Тільки для запису: єдина проблема з цим полягає в тому, що якщо ви намагаєтесь скопіювати текст, який вже не міститься в якомусь текстовому елементі на сторінці, вам потрібно буде зламати набір елементів DOM, встановити текст, скопіювати його, і очистити його. Це багато коду для чогось дуже маленького. Зазвичай я погоджуюся, що розробникам не слід заохочувати постійно встановлювати бібліотеки.
Крістофер Роннінг

3
Для цієї конкретної проблеми текст вже є в елементі на сторінці. Який випадок буде там, де на сторінці є видимий текст, який ви хочете скопіювати, який не є в елементі? Це зовсім інше питання, на яке я би радий показати рішення. Вам не потрібно буде нічого злому реагувати, ви просто надасте прихований елемент у своїй функції візуалізації, який також містить текст. Не потрібно створювати елементи ad hoc.
Нейт

2
Я отримую цю помилку машинопису:Property 'select' does not exist on type 'never'
Алекс C

3
Я отримую TypeError: textAreaRef.current.select не є функцією
pseudozach

120

Використовуйте цю просту функцію onClick на кнопці, якщо ви хочете програмно записувати дані у буфер обміну.

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

3
navigator.clipboard підтримує не всі браузери
Premjeet

8
здається, це була хороша підтримка основних браузерів у 2018 році caniuse.com/#search=clipboard
gasolin

2
На основі посилання, яке ви надали, схоже, його єдиний повністю підтримується в сафарі ...
Nibb

2
Найкраще підходить для мого використання, де текст для копіювання насправді не є на сторінці. Спасибі
NSjonas

1
Часткова підтримка дуже хороша, тому вона повністю підтримується для більшості випадків використання. І як було сказано, це найкраще програмне рішення.
Dror Bar

40

Ви обов'язково повинні розглянути можливість використання такого пакета, як @Shubham, але я створив робочий коден на основі того, що ви описали: http://codepen.io/dtschust/pen/WGwdVN?editors=1111 . Він працює в моєму браузері в хромі, можливо, ви можете побачити, чи є щось, що я там зробив, що ви пропустили, або якщо у вашій програмі є якась розширена складність, яка заважає цьому працювати.

// 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'))

3
Чому пакет краще, ніж ваше рішення?
Герцог Дугал

6
Потенційно краща підтримка перехресного веб-переглядача та більше уваги на пакет, якщо потрібно виправити помилку
Дрю Шустер,

працює як шарм. Так. Цікаво і про підтримку крос-браузера.
Карл Покус

це спричинить мерехтіння на екрані, якщо ви використовуєте appendChild, незалежно від того, як швидко ви його видалите згодом?
robinnnnn

1
Це добре, але це не працює ні на Chrome (72.0) на Android, ні на FF (63.0) на Android.
colin

35

Найпростішим способом буде використання 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

Можливо, вам потрібно підключити onpasteподію
Коен

Як я можу використовувати цей пакет, якщо хочу скопіювати вміст таблиці HTML у буфер обміну? @Shubham Khatri
Джейн Фред

19

Навіщо використовувати пакет npm, коли ви можете отримати все в межах однієї кнопки

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

Я сподіваюся, що це допомагає @jerryurenaa


16

Чому б не використовувати лише метод події clipboardData collection e.clipboardData.setData(type, content)?

На мій погляд, це найпростіший метод для досягнення натискання smth всередині буфера обміну, перевірте це (я використовував це для зміни даних під час нативної дії копіювання):

...

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

Ура!

EDIT: Для тестування я додав codepen: https://codepen.io/dprzygodzki/pen/ZaJMKb


3
@KarlPokus Опитувач шукає лише рішення Chrome
TechTurtle

1
Тестовано на версії Chrome 62.0.3202.94. Це працює. codepen.io/dprzygodzki/pen/ZaJMKb
Damian Przygodzki

1
@OliverDixon - це за замовчуванням об'єкт події React. reactjs.org/docs/events.html
Damian Przygodzki

1
@DamianPrzygodzki Я ненавиджу такі приховані елементи, як чудовий спосіб заплутати розробників.
Олівер Діксон

1
@OliverDixon я відчуваю тебе, але я думаю, що добре, щоб звикнути, що іноді до методу застосовуються деякі дані за замовчуванням, особливо в подіях.
Damian Przygodzki

8

Ваш код повинен працювати ідеально, я його використовую так само. Переконайтесь, що якщо подія натискання запускається всередині спливаючого екрану, як модальний завантажувач або щось подібне, створений елемент повинен знаходитись у межах цього модалу, інакше він не копіюється. Ви завжди можете надати ідентифікатор елемента в цьому модалі (як другий параметр) і отримати його за допомогою getElementById, а потім додати новостворений елемент до цього замість документа. Щось на зразок цього:

copyToClipboard = (text, elementId) => {
  const textField = document.createElement('textarea');
  textField.innerText = text;
  const parentElement = document.getElementById(elementId);
  parentElement.appendChild(textField);
  textField.select();
  document.execCommand('copy');
  parentElement.removeChild(textField);
}

8

Я застосував дуже подібний підхід, як і деякі з перерахованих, але я зробив це дещо конкретнішим. Тут батьківський компонент передасть 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>
  );
};

Це було корисно, тому що я хотів мати тег абзацу замість Textarea
Ehsan Ahmadi

Дякую! Єдине питання приховує текстове поле
itshinkswhenitscold

3

Для тих людей, які намагаються вибрати з DIV замість текстового поля, ось код. Код не пояснюється, але прокоментуйте тут, якщо вам потрібна додаткова інформація:

     import React from 'react';
     ....

    //set ref to your div
          setRef = (ref) => {
            // debugger; //eslint-disable-line
            this.dialogRef = ref;
          };

          createMarkeup = content => ({
            __html: content,
          });

    //following function select and copy data to the clipboard from the selected Div. 
   //Please note that it is only tested in chrome but compatibility for other browsers can be easily done

          copyDataToClipboard = () => {
            try {
              const range = document.createRange();
              const selection = window.getSelection();
              range.selectNodeContents(this.dialogRef);
              selection.removeAllRanges();
              selection.addRange(range);
              document.execCommand('copy');
              this.showNotification('Macro copied successfully.', 'info');
              this.props.closeMacroWindow();
            } catch (err) {
              // console.log(err); //eslint-disable-line
              //alert('Macro copy failed.');
            }
          };

              render() {
                    return (
                        <div
                          id="macroDiv"
                          ref={(el) => {
                            this.dialogRef = el;
                          }}
                          // className={classes.paper}
                          dangerouslySetInnerHTML={this.createMarkeup(this.props.content)}
                        />
                    );
            }

3

Ось ще один випадок використання, якщо ви хочете скопіювати поточний URL у буфер обміну:

Визначте метод

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

Викличте цей метод

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

3

Найкраще рішення з гачками для реагування, для цього не потрібні зовнішні бібліотеки

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.clip, документацію щодо навігатора.clipboard , навігатор.clipboard підтримує величезна кількість браузерів, дивіться тут підтримуваний браузер


2
import React, { Component } from 'react';

export default class CopyTextOnClick extends Component {
    copyText = () => {
        this.refs.input.select();

        document.execCommand('copy');

        return false;
    }

    render () {
        const { text } = this.state;

        return (
            <button onClick={ this.copyText }>
                { text }

                <input
                    ref="input"
                    type="text"
                    defaultValue={ text }
                    style={{ position: 'fixed', top: '-1000px' }} />
            </button>
        )
    }
}

1

Якщо ви хочете вибрати з тексту DIV замість текстового поля, ось код. "Код" - це значення, яке потрібно скопіювати

import React from 'react'
class CopyToClipboard extends React.Component {

  copyToClipboard(code) {
    var textField = document.createElement('textarea')
    textField.innerText = code
    document.body.appendChild(textField)
    textField.select()
    document.execCommand('copy')
    textField.remove()
  }
  render() {
    return (
      <div onClick={this.copyToClipboard.bind(this, code)}>
        {code}
      </div>

    )
  }
}

export default CopyToClipboard

1
Найкраща практика SO - виконати свій код із поясненням. Будь ласка, зроби це.
MartenCatcher

0

ось мій код:

import React from 'react'

class CopyToClipboard extends React.Component {

  textArea: any

  copyClipBoard = () => {
    this.textArea.select()
    document.execCommand('copy')
  }

  render() {
    return (
      <>
        <input style={{display: 'none'}} value="TEXT TO COPY!!" type="text" ref={(textarea) => this.textArea = textarea}  />
        <div onClick={this.copyClipBoard}>
        CLICK
        </div>
      </>

    )
  }
}

export default CopyToClipboard

0
<input
value={get(data, "api_key")}
styleName="input-wrap"
title={get(data, "api_key")}
ref={apikeyObjRef}
/>
  <div
onClick={() => {
  apikeyObjRef.current.select();
  if (document.execCommand("copy")) {
    document.execCommand("copy");
  }
}}
styleName="copy"
>
  复制
</div>

7
Будь ласка, додайте пояснення, як цей код вирішує проблему, а не просто розміщувати код.
Олександр ван Оостенрійк

0

Знайшов найкращий спосіб це зробити. я маю на увазі найшвидший спосіб: w3school

https://www.w3schools.com/howto/howto_js_copy_clipboard.asp

Всередині реагує функціональний компонент. Створіть функцію на ім'я handleCopy:

function handleCopy() {
  // get the input Element ID. Save the reference into copyText
  var copyText = document.getElementById("mail")
  // select() will select all data from this input field filled  
  copyText.select()
  copyText.setSelectionRange(0, 99999)
  // execCommand() works just fine except IE 8. as w3schools mention
  document.execCommand("copy")
  // alert the copied value from text input
  alert(`Email copied: ${copyText.value} `)
}

<>
              <input
                readOnly
                type="text"
                value="exemple@email.com"
                id="mail"
              />
              <button onClick={handleCopy}>Copy email</button>

</>

Якщо не використовується React, w3schools також має один класний спосіб зробити це за допомогою підказки: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_copy_clipboard2

Якщо ви використовуєте React, круто подумайте зробити: Використовуйте Toastify, щоб попередити повідомлення. https://github.com/fkhadra/react-toastify Це lib дуже простий у використанні. Після установки ви можете змінити цей рядок:

 alert(`Email copied: ${copyText.value} `)

Для чогось такого типу:

toast.success(`Email Copied: ${copyText.value} `)

Якщо ви хочете використовувати його, не забудьте встановити toastify. імпортувати ToastContainer, а також тости css:

import { ToastContainer, toast } from "react-toastify"
import "react-toastify/dist/ReactToastify.css"

і додайте контейнер тостів всередину повернення.

import React from "react"

import { ToastContainer, toast } from "react-toastify"
import "react-toastify/dist/ReactToastify.css"


export default function Exemple() {
  function handleCopy() {
    var copyText = document.getElementById("mail")
    copyText.select()
    copyText.setSelectionRange(0, 99999)
    document.execCommand("copy")
    toast.success(`Hi! Now you can: ctrl+v: ${copyText.value} `)
  }

  return (
    <>
      <ToastContainer />
      <Container>
                <span>E-mail</span>
              <input
                readOnly
                type="text"
                value="myemail@exemple.com"
                id="mail"
              />
              <button onClick={handleCopy}>Copy Email</button>
      </Container>
    </>
  )
}

Ваша відповідь містить лише посилання на інший ресурс, але немає конкретної відповіді. Якщо w3schools посилається як правильне рішення, введіть його тут.
f.khantsis
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.