木灵鱼儿

木灵鱼儿

阅读:131

最后更新:2021/08/18/ 16:39:10

Copy 一个复制操作的类

前言

js有一个31k多的star的开源复制库:clipboard.js;但是一些简单复制并不想安装一个库来解决,所以就想自己写一个。

copy所需要的东西

Selection 对象

用于获取被用户选中的部分,通过toString()方法可以获取被选中的文本内容,以及js操作选中。

MDN文档:Selection

execCommand 对象

用于以命令的形式来操作网页的内容,说白了就是用它来实现复制文本操作,复制的是选中的文本

MDN文档:execCommand

需要注意的是,execCommand在未来将会被遗弃,因为这个api本身是从ie浏览器那边继承的,久而久之各大浏览器都对其做了兼容,虽然说遗弃,但是目前除了它没有别的办法进行复制操作。

暂时没有可用的替代方法。
确切的说这个 API 本来也不是标准 API,而是一个 IE 的私有 API,在 IE9 时被引入,后续的若干年里陆续被 Chrome / Firefix / Opera 等浏览器也做了兼容支持,但始终没有形成标准。
这个 API 被废弃的主要原因第一个就是安全问题,在用户未经授权的情况下就可以执行一些敏感操作,这就很恐怖了;第二个问题是因为这是一个同步方法,而且操作了 DOM 对象,会阻塞页面渲染和脚本执行,因当初还没 Promise,所以没设计成异步,挖坑了。新设计的 API 肯定是要解决这两个问题。
不过 W3C 也正在拟草案,大概率以后会引入一个叫 Clipboard 的类型(Chrome 66.0 开始已经有这个类型了,不过还不能用,相关 API 仅存在于文档中),用来处理跟剪贴版相关的操作,不过之后肯定会是像现在获取地理位置啊、麦克风啊什么的,浏览器先会弹出一个对话框让用户授权,你才能读写剪贴板了。

出自: 如果document.execCommand被正式删除,用什么可以实现复制到剪贴板功能呢?

功能要求

传入普通文本,数字,dom元素都能进行复制操作,dom就复制其文本内容,一般用于复制dom内的文本内容。

抛出一个promise对象,方便捕获

Copy类源码

class Copy {
      constructor(data) {
        return this.init(data);
      }

      //初始化
      init(data) {
        return new Promise((resolve, reject) => {
          let copyVal = "";
          let element = null;

          if (this.isNode(data)) {
            //元素
            if (data.nodeName === 'SELECT') {
              copyVal = data.value;
              element = this.createElemnt(copyVal);
              document.body.appendChild(element);

              element.select();
              element.setSelectionRange(0, copyVal.length);

            } else if (data.nodeName === 'INPUT' || data.nodeName === 'TEXTAREA') {
              const isReadOnly = data.hasAttribute('readonly'); //是否存在只读属性(只读可以防止移动端键盘被拉起)

              if (!isReadOnly) {
                data.setAttribute('readonly', '');
              }

              data.select();
              data.setSelectionRange(0, data.value.length);

              if (!isReadOnly) {
                data.removeAttribute('readonly', '');
              }

              copyVal = data.value;

            } else {
              if (data.hasAttribute('contenteditable')) {
                data.focus();
              }

              const selection = window.getSelection();
              const range = document.createRange();

              range.selectNodeContents(data);
              selection.removeAllRanges();
              selection.addRange(range);

              copyVal = selection.toString();
            }
          } else if (typeof data === 'string' || typeof data === 'number') {
            //纯文本&数字
            copyVal = typeof data === 'number' ? String(data) : data;
            element = this.createElemnt(copyVal);
            document.body.appendChild(element);

            element.select();
            element.setSelectionRange(0, copyVal.length);
          } else {
            return reject({ msg: "不支持复制的参数", copyVal, origin: data });
          }

          const status = document.execCommand('copy');
          element && element.remove(); //移除创建的dom
          !element && data.blur(); //取消聚焦
          window.getSelection().removeAllRanges(); //取消选中

          if (status) {
            return resolve({ msg: "复制成功", copyVal, origin: data });
          } else {
            return reject({ msg: "复制失败", copyVal, origin: data });
          }
        })
      }

      //是否node
      isNode(value) {
        return value !== undefined && value instanceof HTMLElement && value.nodeType === 1;
      }

      //创建textarea
      createElemnt(value) {
        const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
        const element = document.createElement("textarea");
        // Prevent zooming on iOS
        element.style.fontSize = '12pt';
        // Reset box model
        element.style.border = '0';
        element.style.padding = '0';
        element.style.margin = '0';
        // Move element out of screen horizontally
        element.style.position = 'absolute';
        element.style[isRTL ? 'right' : 'left'] = '-9999px';
        // Move element to the same position vertically
        const yPosition = window.pageYOffset || document.documentElement.scrollTop;
        element.style.top = `${yPosition}px`;

        element.setAttribute('readonly', '');
        element.value = value;

        return element;
      }
    }

使用

const btn = document.getElementById("btn");

btn.addEventListener("click", () => {
    new Copy(document.querySelector("code")).then((res) => {
        console.log(res)
    }).catch(error => console.log(error));
});

一些特别的地方:

ios端复制内容不全的问题

data.select();
data.setSelectionRange(0, data.value.length);

当data是input或者textarea元素时,虽然select()可以让他选中所有文本,但是在ios端无法选中所有文本,所以还需要使用setSelectionRange

移动端会拉起键盘

data.setAttribute('readonly', '');

通过设置表单元素的readonly属性,可以解决这个问题

复制指令也是有状态的

document.execCommand('copy');   //true||false

表示复制成功或者失败

版权申明

本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

关于作者

站点职位 博主
获得点赞 0
文章被阅读 131

相关文章