【译】JavaScript实现文字剪贴板&React版本

2,227 阅读3分钟

目录

写在最前面

  • 有一个简单的需求,用户需要快捷的复制一些相关的信息,然后进行下一步信息的填写。前端这里需要做一个剪贴板方便用户体验。想直接参考 react 使用的可以看 使用react和typescript改写和优化一下
  • 大概设计如下,有多条信息,然后用户可以点击右边的复制 icon 进行快捷的复制。

image

怎么使用JavaScript实现一个剪贴板

  • 具体分为五步

    • 1、创建一个 textarea ,把需要的文本放进 textarea
    • 2、将 textarea 元素插入 body 中。
    • 3、使用 HTMLInputElement.select() 方法选择 textarea 中的文本内容
    • 4、使用 document.execCommand('copy') 复制 textarea 中的文本内容到剪贴板
    • 5、从 body 删除 textarea 元素
  • code

const copyToClipboard = str => {
  const el = document.createElement('textarea');
  el.value = str;
  document.body.appendChild(el);
  el.select();
  document.execCommand('copy');
  document.body.removeChild(el);
};

必要 api 参考

上面的方法不是很完美我们优化一下

  • 这个方法不是在每个地方都能运行,由于 textarea 的插入和移除,有时候会出现页面的频闪和抖动
  • 下面用 css 优化一下我们的 textarea 样式,隐藏 textarea 的显示。
const copyToClipboard = str => {
  const el = document.createElement('textarea');
  el.value = str;
  el.setAttribute('readonly', '');
  el.style.position = 'absolute';
  el.style.left = '-9999px';
  
  document.body.appendChild(el);
  el.select();
  document.execCommand('copy');
  document.body.removeChild(el);
};

思考一个问题

  • 我们用户在使用我们的剪贴板之前可能已经选择了已存在 html 中的文本内容了,所以我们这里需要多加一些判断防止遗漏用户选择的文本。
  • DocumentOrShadowRoot.getSelection(), Selection.rangeCount, Selection.getRangeAt(), Selection.removeAllRanges() and Selection.addRange() 这些方法存储用户选择的文本内容和解决范围选择的问题
const copyToClipboard = str => {
  const el = document.createElement('textarea');  // Create a <textarea> element
  el.value = str;                                 // Set its value to the string that you want copied
  el.setAttribute('readonly', '');                // Make it readonly to be tamper-proof
  el.style.position = 'absolute';                 
  el.style.left = '-9999px';                      // Move outside the screen to make it invisible
  document.body.appendChild(el);                  // Append the <textarea> element to the HTML document
  const selected =            
    document.getSelection().rangeCount > 0        // Check if there is any content selected previously
      ? document.getSelection().getRangeAt(0)     // Store selection if found
      : false;                                    // Mark as false to know no selection existed before
  el.select();                                    // Select the <textarea> content
  document.execCommand('copy');                   // Copy - only works as a result of a user action (e.g. click events)
  document.body.removeChild(el);                  // Remove the <textarea> element
  if (selected) {                                 // If a selection existed before copying
    document.getSelection().removeAllRanges();    // Unselect everything on the HTML document
    document.getSelection().addRange(selected);   // Restore the original selection
  }
};

使用react和typescript改写和优化一下

  • 学习了上面的文章,结合产品的需求改写一下相关代码。
  • 思路
    • 1、首先创建一个 targetNode,设置绝对布局,赢藏我们的元素
    • 2、document.getSelection() 已经由 window.getSelection() 替代了,具体流程如上
    • 3、创建一个 result 标记能否能正常 使用剪贴功能,不能的返回 false
    • 4、删除这个 targetNode
function createNode(text) {
    const node = document.createElement('div');

    node.innerText = text;
    node.style.cssText = 'position:absolute; top: 0; left: 0; height:0; width:0; pointer-events: none;';

    document.body.appendChild(node);

    return node;
}

export default function copyMe(text) {
    const targetNode = createNode(text);
    const range = document.createRange();
    
    const selection = window.getSelection()!;
    const selected = selection.rangeCount > 0       
      ? selection.getRangeAt(0)    
      : false;  

    targetNode.focus(); // focus 我们需要的文本
    range.selectNodeContents(targetNode); 
    
    if(selected){
        selection.removeAllRanges();
        selection.addRange(range);
    }

    let result; 

    try {
        result = document.execCommand('copy');
    } catch (e) {
        result = false;
    }

    document.body.removeChild(targetNode);

    return result;
}

如何使用copyme

import React, { Fragment } from 'react';
import copyMe from 'utils/copyMe';

 interface ItemProps {
    value?: string | number;
}

const Item: React.FC<ItemProps> = props => {
    const { value } = props;

    const copyme = () => {
       alert(copyMe(value) ? 'Copied!' : 'Failed!');
    };

    return (
        <Fragment>
            {value && (
                <div>
                    {value}
                    <textarea  value={value} readOnly></textarea>
                    <span onClick={copyme}></span>
                </div>
            )}
        </Fragment>
    );
};

export default Item;

必要 api 参考

原文参考