В 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
лучше всего подходит для моего случая использования, когда текст для копирования фактически отсутствует на странице. Спасибо
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


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

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

Это было полезно, потому что я хотел иметь тег абзаца вместо 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)}
                        />
                    );
            }

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)

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])
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.