В reactJS, как скопировать текст в буфер обмена?

168

Я использую 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
12
@EugZol Я действительно предпочитаю писать код, а не добавлять еще одну зависимость, предполагая, что код довольно небольшой.
Герцог Дугал
Проверьте эти ответы stackoverflow.com/questions/400212/…
elmeister
@elmeister, вопрос относится к reactjs
Герцог Дугал

Ответы:

199

Я лично не вижу необходимости в библиотеке для этого. Глядя на 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 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 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>
  );
}
Нейт
источник
3
Для записи: единственная проблема заключается в том, что если вы пытаетесь скопировать текст, которого еще нет в каком-либо текстовом элементе на странице, вам нужно будет взломать набор элементов DOM, установить текст, скопировать его, и очистить его. Это очень много кода для чего-то очень маленького. Обычно я согласен с тем, что разработчиков не следует поощрять к постоянной установке библиотек.
Кристофер Роннинг
3
Для этой конкретной проблемы текст уже находится в элементе на странице. В каком случае на странице, которую вы хотите скопировать, есть видимый текст, которого нет в элементе? Это совершенно другая проблема, и я с радостью предложу решение. Вам не нужно ничего взламывать с помощью response, вы просто предоставите скрытый элемент в своей функции рендеринга, который также содержит текст. Нет необходимости создавать элементы ad hoc.
Нейт
2
Я получаю эту машинописную ошибку:Property 'select' does not exist on type 'never'
Alex C
3
Я получаю TypeError: textAreaRef.current.select не является функцией
псевдозач
2
Это в настоящее время устарели developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
user981320
170

Используйте эту простую встроенную функцию onClick для кнопки, если вы хотите программно записывать данные в буфер обмена.

onClick={() => {navigator.clipboard.writeText(this.state.textToCopy)}}
Гэри Вернон Грабб
источник
3
navigator.clipboard поддерживает не все браузеры
Premjeet
12
похоже, что в 2018 году он хорошо поддерживал основные браузеры caniuse.com/#search=clipboard
gasolin
3
Нибб
3
лучше всего подходит для моего случая использования, когда текст для копирования фактически отсутствует на странице. Спасибо
NSjonas
1
Частичная поддержка очень хороша, поэтому полностью поддерживается для большинства сценариев использования. Как уже упоминалось, это лучшее программное решение.
Dror Bar
43

Вам обязательно стоит подумать об использовании пакета, такого как @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
Потенциально лучшая кроссбраузерная поддержка и больше внимания к пакету на случай, если нужно исправить ошибку
Дрю Шустер,
работает как шарм. Да. Я также интересуюсь кроссбраузерностью.
Karl Pokus
вызовет ли это мерцание на экране, если, поскольку вы используете appendChild, независимо от того, как быстро вы удаляете его впоследствии?
robinnnnn
1
Это хорошо, но не работает в Chrome (72.0) на Android и FF (63.0) на Android.
Colin
38

Самый простой способ - использовать 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
Джейн Фред
29

Зачем вам нужен пакет npm, если вы можете получить все с помощью одной такой кнопки

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

Надеюсь, это поможет @jerryurenaa

Jerryurenaa
источник
Кстати, лучший запрос
Эдгар Мехиа
16

Почему бы не использовать только метод сбора событий 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

Ура!

РЕДАКТИРОВАТЬ: в целях тестирования я добавил код: https://codepen.io/dprzygodzki/pen/ZaJMKb

Дамиан Пшигодски
источник
3
@KarlPokus Спрашивающий ищет только решение для Chrome
TechTurtle
1
Проверено в Chrome версии 62.0.3202.94. Это работает. codepen.io/dprzygodzki/pen/ZaJMKb
Damian
1
@OliverDixon - объект по умолчанию для события React. reactjs.org/docs/events.html
Дамиан
1
@DamianPrzygodzki Я ненавижу подобные скрытые элементы, отличный способ запутать разработчиков.
Оливер Диксон,
1
@OliverDixon, я тебя чувствую, но думаю, полезно привыкнуть к тому, что иногда к методу применяются некоторые данные по умолчанию, особенно в событиях.
Damian Przygodzki
14

Лучшее решение с перехватчиками реакции, для этого не нужны внешние библиотеки

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

Джаман-Деды
источник
10

Я использовал подход, очень похожий на некоторые из вышеперечисленных, но, как мне кажется, сделал его более конкретным. Здесь родительский компонент будет передавать 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>
  );
};
tjgragg
источник
Это было полезно, потому что я хотел иметь тег абзаца вместо Textarea
Эхсан Ахмади
Благодарность! Единственная проблема - скрытие текстового
поля
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);
}
Купи
источник
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)}
                        />
                    );
            }
connect2Coder
источник
3

Вот еще один вариант использования, если вы хотите скопировать текущий URL в буфер обмена:

Определите метод

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

Назовите этот метод

<button copyToClipboard={shareLink}>
   Click to copy current url to clipboard
</button>
Джейсонлеонхард
источник
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
1

Нашел лучший способ сделать это. я имею в виду самый быстрый способ: w3school

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

Внутри функционального компонента React. Создайте функцию с именем 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 Это очень простая в использовании библиотека. После установки вы можете изменить эту строку:

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

Для чего-то вроде:

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

Если вы хотите его использовать, не забудьте установить toastify. import ToastContainer, а также всплывает css:

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

и добавьте контейнер для тостов внутрь return.

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
1

navigator.clipboard не работает через http-соединение в соответствии с их документом. Таким образом, вы можете проверить, идет ли он неопределенным, и вместо этого использовать document.execCommand ('copy'), это решение должно охватывать почти все браузеры.

const defaultCopySuccessMessage = 'ID copied!'

const CopyItem = (props) => {
  const { copySuccessMessage = defaultCopySuccessMessage, value } = props

  const [showCopySuccess, setCopySuccess] = useState(false)


  function fallbackToCopy(text) {
    if (window.clipboardData && window.clipboardData.setData) {
      // IE specific code path to prevent textarea being shown while dialog is visible.
      return window.clipboardData.setData('Text', text)
    } else if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
      const textarea = document.createElement('textarea')
      textarea.innerText = text
      // const parentElement=document.querySelector(".up-CopyItem-copy-button")
      const parentElement = document.getElementById('copy')
      if (!parentElement) {
        return
      }
      parentElement.appendChild(textarea)
      textarea.style.position = 'fixed' // Prevent scrolling to bottom of page in MS Edge.
      textarea.select()
      try {
        setCopySuccess(true)
        document.execCommand('copy') // Security exception may be thrown by some browsers.
      } catch (ex) {
        console.log('Copy to clipboard failed.', ex)
        return false
      } finally {
        parentElement.removeChild(textarea)
      }
    }
  }

  const copyID = () => {
    if (!navigator.clipboard) {
      fallbackToCopy(value)
      return
    }
    navigator.clipboard.writeText(value)
    setCopySuccess(true)
  }

  return showCopySuccess ? (
    <p>{copySuccessMessage}</p>
  ) : (
    <span id="copy">
      <button onClick={copyID}>Copy Item </button>
    </span>
  )
}

И вы можете просто вызвать и повторно использовать компонент в любом месте, где хотите

const Sample=()=>(
   <CopyItem value="item-to-copy"/>
)
Угур Йылмаз
источник
1

используйте эту команду, чтобы передать свое значение функции

var promise = navigator.clipboard.writeText(newClipText)
Tomnyson
источник
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
Пожалуйста, добавьте объяснение того, как этот код решает проблему, а не просто отправьте код.
Alexander van
0
 copyclip = (item) => {
    var textField = document.createElement('textarea')
    textField.innerText = item
    document.body.appendChild(textField)
    textField.select()
    document.execCommand('copy')
    this.setState({'copy':"Copied"});
    textField.remove()
    setTimeout(() => {
      this.setState({'copy':""});
    }, 1000);
 }

 <span   className="cursor-pointer ml-1" onClick={()=> this.copyclip(passTextFromHere)} >Copy</span> <small>{this.state.copy}</small>
Маной Патель
источник
0

Вы также можете использовать перехватчики реакции в функциональные компоненты или компоненты без состояния с помощью этого фрагмента кода: PS: Убедитесь, что вы устанавливаете useClippy через npm / yarn с помощью этой команды: npm install use-clippy или yarn add use-clippy

import React from 'react';
import useClippy from 'use-clippy';

export default function YourComponent() {

// clipboard is the contents of the user's clipboard.
  // setClipboard('new value') wil set the contents of the user's clipboard.

  const [clipboard, setClipboard] = useClippy();

  return (
    <div>

      {/* Button that demonstrates reading the clipboard. */}
      <button
        onClick={() => {
          alert(`Your clipboard contains: ${clipboard}`);
        }}
      >
        Read my clipboard
      </button>

      {/* Button that demonstrates writing to the clipboard. */}
      <button
        onClick={() => {
          setClipboard(`Random number: ${Math.random()}`);
        }}
      >
        Copy something
      </button>
    </div>
  );
}
Хирва Благословение
источник
0

Эта работа для меня:

const handleCopyLink = useCallback(() => {
    const textField = document.createElement('textarea')
    textField.innerText = url
    document.body.appendChild(textField)
    if (window.navigator.platform === 'iPhone') {
      textField.setSelectionRange(0, 99999)
    } else {
      textField.select()
    }
    document.execCommand('copy')
    textField.remove()
    
    toast.success('Link Copiado com sucesso')
  }, [url])
Антонио Карлос Араужо
источник