这个问题来源于我的一个项目需求,需要在点击分享按钮时,将文本复制到剪贴板以方便用户直接进行粘贴操作。

在解决另一个衍生问题的同时,我在这里使用了四种不同的复制方式,如下文:

  1. react-copy-to-clipboard
  2. copy-to-clipboard
  3. clipboard.js
  4. execCommand

react-copy-to-clipboard

这个组件是对 copy-to-clipboard 组件的二次封装

说起来这类通用组件的使用其实很简单,按照 README 指导就行。

但是我这里要实现的效果和官方提供的 Demo 略有不同,也是在这一步最后测试发现出现问题。

class App extends React.PureComponent {
  state = {value: 'some\ntext', copied: false};

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

  onClick = ({target: {innerHTML}}) => {
    console.log(`Clicked on "${innerHTML}"!`); // eslint-disable-line
  };

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

  render() {
    return (
      <div className="app">
        <h1>react-copy-to-clipboard</h1>

        <section className="section">
          <textarea onChange={this.onChange} rows={2} cols={10} value={this.state.value} />
        </section>

        <section className="section">
          <h2>with onClick</h2>
          <CopyToClipboard
            onCopy={this.onCopy}
            options={{message: 'Whoa!'}}
            text={this.state.value}>
            <button onClick={this.onClick}>Copy to clipboard with onClick prop</button>
          </CopyToClipboard>
        </section>

      </div>
    );
  }
}

const appRoot = document.createElement('div');
document.body.appendChild(appRoot);
ReactDOM.render(<App />, appRoot);

因为我需要在 <CopyToClipBoard> 中包裹两个按钮,而只让其中一个产生复制效果,这里就出现了问题。

组件高度封装导致包裹在组件中的按钮默认都和复制功能相绑定,因此反而无法应用在这一场景。

copy-to-clipboard

这里的实现方式其实和 react-copy-to-clipboard 相差不多

相对而言,这个组件使用较为简单。我们只需要在 copy 方法执行时将需要被复制对文本作为参数传递进去即可。

import copy from 'copy-to-clipboard';

onChange = ({ target: { value } }) => {
    this.setState({ value });
};

onClickCopy = () => {
    const { value } = this.state;
    copy(value);
    ...
}

render() {
    const { value } = this.state
    return (
        ...
        <textarea value={value} onChange={this.onChange}></textarea>
        ...
    )
}

这里将 textarea 的数据实时监听,并在点击事件触发时传递给 copy,从而实现复制内容。

clipboard.js

clipboard.js 的实现是通过使用类 jQuery 式的选择器,绑定文本框,然后可以去监听 success 事件

<!-- Target -->
<textarea id="bar">Mussum ipsum cacilds...</textarea>
 
<!-- Trigger -->
<button class="btn" data-clipboard-action="cut" data-clipboard-target="#bar">
    Cut to clipboard
</button>
var clipboard = new ClipboardJS('.btn');
 
clipboard.on('success', function(e) {
    console.info('Action:', e.action);
    console.info('Text:', e.text);
    console.info('Trigger:', e.trigger);
 
    e.clearSelection();
});
 
clipboard.on('error', function(e) {
    console.error('Action:', e.action);
    console.error('Trigger:', e.trigger);
});

execCommand

这个是浏览器原生API

使用 document.execCommand 时在 ios 端会出现一点问题,因为无法使用 input.select()方法

所以在这里我使用了 range 对象,构建了一个可以被选中的文本区域:

selectText(textbox, startIndex, stopIndex) {
    if (textbox.createTextRange) {
        //ie
        const range = textbox.createTextRange();
        range.collapse(true);
        range.moveStart('character', startIndex); //起始光标
        range.moveEnd('character', stopIndex - startIndex); //结束光标
        range.select(); //不兼容苹果
    } else {
        //firefox/chrome
        textbox.setSelectionRange(startIndex, stopIndex);
        textbox.focus();
    }
}

let textarea = document.getElementById('clipboard-text');
this.selectText(textarea, 0, value.length);

if (document.execCommand('copy')) {
    // ...
}

最终选择

考虑到客户端问题,目前选择了 copy-to-clipboard 进行实现。

react-copy-to-clipboard 存在问题,被放弃

clipboard.js 没有进行过多尝试

document.execCommand 因为需要进行文本域选中,不符合客户端要求,所以放弃。

总结

事实上后续我在查看前面封装好的组件源码时,发现其实它们底层调用的基本都是 document.execCommand,只是在这一基础上进行了其他更完善的考虑。

Last modification:September 24th, 2020 at 02:31 pm
如果觉得我的文章对你有用,请随意赞赏