W ReactJS, jak skopiować tekst do schowka?


147

Używam ReactJS i kiedy użytkownik kliknie łącze, chcę skopiować tekst do schowka.

Używam Chrome 52 i nie potrzebuję obsługi żadnych innych przeglądarek.

Nie rozumiem, dlaczego ten kod nie powoduje skopiowania danych do schowka. (źródło fragmentu kodu pochodzi z posta na Reddicie).

Czy robię to źle? Czy ktoś może zasugerować, czy istnieje „poprawny” sposób na zaimplementowanie kopiowania do schowka za pomocą reagjs?

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
Czy próbowałeś skorzystać z rozwiązań innych firm, takich jak clipboardjs.com lub github.com/zeroclipboard/zeroclipboard ?
EugZol,

11
@EugZol Naprawdę wolę pisać kod niż dodawać kolejną zależność, zakładając, że kod jest dość mały.
Duke Dougal


@elmeister, pytanie jest specyficzne dla reakcji
Duke Dougal,

Odpowiedzi:


180

Osobiście nie widzę w tym potrzeby biblioteki. Patrząc na http://caniuse.com/#feat=clipboard, jest teraz dość szeroko obsługiwany, jednak nadal możesz robić takie rzeczy, jak sprawdzanie, czy funkcja istnieje w obecnym kliencie i po prostu ukrywać przycisk kopiowania, jeśli tak nie jest.

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;

Aktualizacja: Przepisano przy użyciu React Hooks w 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
To najlepsza odpowiedź. Nie powinniśmy zachęcać programistów do używania pakietów do wszystkiego, chyba że potrzebują obsługi starej przeglądarki.
tugce

3
Tak dla przypomnienia: jedynym problemem jest to, że jeśli próbujesz skopiować tekst, którego nie ma jeszcze w jakimś elemencie tekstowym na stronie, musisz zhakować zestaw elementów DOM, ustawić tekst, skopiować go, i posprzątaj. To dużo kodu na coś bardzo małego. Normalnie zgodziłbym się, że deweloperów nie powinno się zachęcać do ciągłego instalowania bibliotek.
Christopher Ronning

3
W przypadku tego konkretnego problemu tekst znajduje się już w elemencie na stronie. Jaki byłby przypadek, gdyby na stronie był widoczny tekst, który chcesz skopiować, a którego nie ma w elemencie? To zupełnie inna kwestia, na którą z przyjemnością pokażę rozwiązanie. Nie musiałbyś niczego hakować za pomocą reakcji, po prostu dostarczasz ukryty element w funkcji renderowania, który również przechowuje tekst. Nie ma potrzeby tworzenia elementów ad hoc.
Nate

2
Otrzymuję ten błąd maszynopisu:Property 'select' does not exist on type 'never'
Alex C

3
Dostaję TypeError: textAreaRef.current.select nie jest funkcją
pseudozach

120

Użyj tej prostej funkcji inline onClick na przycisku, jeśli chcesz programowo zapisać dane do schowka.

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

3
navigator.clipboard nie obsługuje wszystkich przeglądarek
Premjeet

8
wygląda na to, że w 2018 roku były dobrze obsługiwane przez główne przeglądarki caniuse.com/#search=clipboard
gasolin

2
na podstawie podanego przez Ciebie linku wygląda na to, że jest całkowicie obsługiwany tylko w safari ...
Nibb

2
działa najlepiej w moim przypadku, w którym tekst do skopiowania nie znajduje się na stronie. Dzięki
NSjonas

1
Częściowe wsparcie jest bardzo dobre, więc jest w pełni obsługiwane w większości przypadków użycia. Jak już wspomniano, jest to najlepsze rozwiązanie programistyczne.
Dror Bar

40

Zdecydowanie powinieneś rozważyć użycie pakietu takiego jak @Shubham powyżej, który radzi, ale stworzyłem działający kod na podstawie tego, co opisałeś: http://codepen.io/dtschust/pen/WGwdVN?editors=1111 . Działa w mojej przeglądarce w Chrome, być może możesz zobaczyć, czy jest coś, co tam zrobiłem, co przegapiłeś, lub czy w twojej aplikacji jest jakaś rozszerzona złożoność, która uniemożliwia to działanie.

// 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
Dlaczego pakiet jest lepszy niż Twoje rozwiązanie?
Duke Dougal

6
Potencjalnie lepsza obsługa wielu przeglądarek i więcej oczu na pakiet w przypadku konieczności naprawienia błędu
Drew Schuster

działa jak marzenie. Tak. Zastanawiam się również nad obsługą wielu przeglądarek.
Karl Pokus

czy spowodowałoby to migotanie ekranu, jeśli używasz appendChild, bez względu na to, jak szybko usuwasz go później?
robinnnnn

1
To dobrze, ale nie działa w Chrome (72.0) na Androidzie ani na FF (63.0) na Androidzie.
colin

35

Najprostszym sposobem będzie użycie react-copy-to-clipboard pakietu npm.

Możesz go zainstalować za pomocą następującego polecenia

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

Użyj go w następujący sposób.

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

Szczegółowe wyjaśnienie znajduje się pod poniższym linkiem

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

Oto biegające skrzypce .


Czy jest jakieś rozwiązanie, jeśli muszę cofnąć? tj. Autor przekopiuje tekst z e-maila do obszaru tekstowego w aplikacji reagjs. Nie muszę zachowywać tagów HTML, jednak muszę zachować tylko znaki końca wiersza.
TechTurtle,

Prawdopodobnie musisz podłączyć onpastewydarzenie
Koen

Jak mogę użyć tego pakietu, jeśli chcę skopiować zawartość tabeli html do schowka? @Shubham Khatri
Jane Fred

19

Dlaczego potrzebujesz pakietu npm, skoro możesz uzyskać wszystko za pomocą jednego przycisku, takiego jak ten

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

Mam nadzieję, że to pomoże @jerryurenaa


16

Dlaczego nie używać tylko metody zbierania danych ze schowka zdarzeń e.clipboardData.setData(type, content) ?

Moim zdaniem jest to najbardziej prosta metoda na wypchnięcie czegoś do schowka, sprawdź to (użyłem tego do modyfikacji danych podczas natywnej akcji kopiowania):

...

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

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

Podążałem tą ścieżką: https://developer.mozilla.org/en-US/docs/Web/Events/copy

Twoje zdrowie!

EDYCJA: Do testów dodałem kod: https://codepen.io/dprzygodzki/pen/ZaJMKb


3
@KarlPokus Pytający szuka tylko rozwiązania Chrome
TechTurtle

1
Przetestowano w Chrome w wersji 62.0.3202.94. To działa. codepen.io/dprzygodzki/pen/ZaJMKb
Damian Przygodzki

1
@OliverDixon jest to domyślny obiekt zdarzenia React. actjs.org/docs/events.html
Damian Przygodzki

1
@DamianPrzygodzki Nienawidzę takich ukrytych elementów, świetny sposób na zmylenie deweloperów.
Oliver Dixon

1
@OliverDixon Czuję cię, ale myślę, że dobrze jest przyzwyczaić się, że czasami istnieją pewne domyślne dane zastosowane do metody, szczególnie w przypadku wydarzeń.
Damian Przygodzki

8

Twój kod powinien działać idealnie, używam go w ten sam sposób. Upewnij się tylko, że jeśli zdarzenie kliknięcia jest wywoływane z poziomu wyskakującego ekranu, takiego jak modalny bootstrap lub coś podobnego, utworzony element musi znajdować się w tym modalu, w przeciwnym razie nie zostanie skopiowany. Zawsze możesz podać id elementu wewnątrz tego modalu (jako drugi parametr) i pobrać go za pomocą metody getElementById, a następnie dołączyć nowo utworzony element do tego elementu zamiast do dokumentu. Coś takiego:

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

Podjąłem bardzo podobne podejście, jak w przypadku niektórych z powyższych, ale wydaje mi się, że stało się to trochę bardziej konkretne. Tutaj komponent nadrzędny przekaże adres URL (lub dowolny tekst, który chcesz) jako rekwizyt.

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

Było to przydatne, ponieważ chciałem mieć tag akapitu zamiast Textarea
Ehsan Ahmadi

Dzięki! Jedynym problemem jest ukrywanie pola
tekstowego

3

Dla osób, które próbują wybrać z DIV zamiast pola tekstowego, oto kod. Kod jest zrozumiały, ale jeśli chcesz uzyskać więcej informacji, dodaj komentarz:

     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

Oto inny przypadek użycia, jeśli chcesz skopiować bieżący adres URL do schowka:

Zdefiniuj metodę

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

Wywołaj tę metodę

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

3

Najlepsze rozwiązanie z zaczepami do reagowania, bez konieczności korzystania z zewnętrznych bibliotek

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

sprawdź tutaj, aby uzyskać dalszą dokumentację dotyczącą tablicy navigator.clip , navigator.clipboard dokumentacja navigotor.clipboard jest obsługiwana przez ogromną liczbę przeglądarek spójrz tutaj obsługiwana przeglądarka


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

Jeśli chcesz wybrać z DIV zamiast pola tekstowego, oto kod. „Kod” to wartość, która ma zostać skopiowana

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
Najlepszą praktyką w przypadku SO jest wykonanie kodu z wyjaśnieniem. Proszę zrób to.
MartenCatcher

0

oto mój kod:

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
Dodaj wyjaśnienie, w jaki sposób ten kod rozwiązuje problem, zamiast tylko wysyłania kodu.
Alexander van Oostenrijk

0

Znalazłem najlepszy sposób na zrobienie tego. mam na myśli najszybszy sposób: w3school

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

Wewnątrz elementu funkcjonalnego Reaguj. Utwórz funkcję o nazwie 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>

</>

Jeśli nie używasz Reacta, w3schools mają również jeden fajny sposób na zrobienie tego z dołączoną etykietką: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_copy_clipboard2

Jeśli korzystasz z Reacta, fajne rozwiązanie: użyj Toastify, aby zaalarmować wiadomość. https://github.com/fkhadra/react-toastify To jest biblioteka bardzo łatwa w użyciu. Po instalacji możesz zmienić tę linię:

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

Na coś takiego:

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

Jeśli chcesz go użyć, nie zapomnij zainstalować toastify. import ToastContainer, a także toasty css:

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

i włóż pojemnik na tosty do powrotu.

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

Twoja odpowiedź zawiera tylko odniesienie do innego zasobu, ale nie zawiera konkretnej odpowiedzi. Jeśli link w3schools jest poprawnym rozwiązaniem, wpisz go tutaj.
f.khantsis
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.