乐趣区

关于javascript:想要复制图像Clipboard-API-了解一下

在写了 这个 29.7 K 的剪贴板 JS 库有点货色!这篇文章之后,收到了小伙伴提的两个问题:

1.clipboard.js 这个库除了复制文字之外,能复制图像么?

2.clipboard.js 这个库依赖的 document.execCommand API 已被废除了,当前应该怎么办?

(图片起源:https://developer.mozilla.org…)

接下来,本文将围绕上述两个问题开展,不过在看第一个问题之前,咱们先来简略介绍一下 剪贴板 ????。

剪贴板(英语:clipboard),有时也称剪切板、剪贴簿、剪贴本。它是一种软件性能,通常由操作系统提供,作用是应用复制和粘贴操作短期存储数据和在文档或应用程序间转移数据。它是图形用户界面(GUI)环境中最罕用的性能之一,通常实现为匿名、长期的数据缓冲区,能够被环境内的大部分或所有程序应用编程接口拜访。—— 维基百科

通过以上的形容咱们能够晓得,剪贴板架起了一座桥梁,使得在各种应用程序之间,传递和共享信息成为可能。然而美中不足的是,剪贴板只能保留一份数据,每当新的数据传入,旧的便会被笼罩。

理解完 剪贴板 ???? 的概念和作用之后,咱们马上来看一下第一个问题:clipboard.js 这个库除了复制文字之外,能复制图像么?

关注「全栈修仙之路」浏览阿宝哥原创的 4 本收费电子书(累计下载近 2.1 万)及 50 几篇“重学 TS”教程。

一、clipboard.js 是否复制图像?

clipboard.js 是一个用于将 文本 复制到剪贴板的 JS 库。没有应用 Flash,没有应用任何框架,开启 gzipped 压缩后仅仅只有 3kb

(图片起源:https://clipboardjs.com/#exam…)

当你看到 “A modern approach to copy text to clipboard” 这个形容,你是不是曾经晓得答案了。那么理论的状况是怎么呢?上面咱们来入手验证一下。在 这个 29.7 K 的剪贴板 JS 库有点货色!这篇文章中,阿宝哥介绍了在实例化 ClipboardJS 对象时,能够通过 options 对象的 target 属性来设置复制的指标:

// https://github.com/zenorocha/clipboard.js/blob/master/demo/function-target.html
let clipboard = new ClipboardJS('.btn', {target: function() {return document.querySelector('div');
  }
});

利用 clipboard.js 的这个个性,咱们能够定义以下 HTML 构造:

<div id="container">
   <img src="http://cdn.semlinker.com/abao.png" width="80" height="80"/>
   <p> 大家好,我是阿宝哥 </p>
</div>
<button class="btn"> 复制 </button>

而后在实例化 ClipboardJS 对象时设置复制的指标是 #container 元素:

const clipboard = new ClipboardJS(".btn", {target: function () {return document.querySelector("#container");
  }
});

之后,咱们点击页面中的 复制 按钮,对应的成果如下图所示:

察看上图可知,页面中的图像和文本都曾经被复制了。对于文本来说,大家应该都很分明。而对于图像来说,到底复制了什么?咱们又该如何获取已复制的内容呢?针对这个问题,咱们能够利用 HTMLElement 对象上的 onpaste 属性或者监听元素上的 paste 事件。

这里咱们通过设置 document 对象的 onpaste 属性,来打印一下粘贴事件对应的事件对象:

document.onpaste = function (e) {console.dir(e);
}

当咱们点击 复制 按钮,而后在页面执行 粘贴 操作后,控制台会打印出以下内容:

通过上图可知,在 ClipboardEvent 对象中含有一个 clipboardData 属性,该属性蕴含了与剪贴板相关联的数据。详细分析了 clipboardData 属性之后,咱们发现已复制的图像和一般文本被封装为 DataTransferItem 对象。

为了更不便地剖析 DataTransferItem 对象,阿宝哥从新更新了 document 对象的 onpaste 属性:

在上图中,咱们能够分明的看到 DataTransferItem 对象上含有 kindtype 属性别离用于示意数据项的类型(string 或 file)及数据对应的 MIME 类型。利用 DataTransferItem 对象提供的 getAsString 办法,咱们能够获取该对象中保留的数据:

置信看完以上的输入后果,小伙伴们就很分明第一个问题的答案了。那么如果想要复制图像的话,应该如何实现呢?其实这个问题的答案与小伙伴提的第二个问题的答案是一样的,咱们能够利用 Clipboard API 来实现复制图像的问题及解决 document.execCommand API 已被废除的问题。

接下来,咱们的指标就是实现复制图像的性能了,因为要利用到 Clipboard API,所以阿宝哥先来介绍一下该 API。

二、Clipboard API 简介

Clipboard 接口实现了 Clipboard API,如果用户授予了相应的权限,就能提供零碎剪贴板的读写访问。在 Web 应用程序中,Clipboard API 可用于实现剪切、复制和粘贴性能。该 API 用于取代通过 document.execCommand API 来实现剪贴板的操作。

在理论我的项目中,咱们不须要手动创立 Clipboard 对象,而是通过 navigator.clipboard 来获取 Clipboard 对象:

在获取 Clipboard 对象之后,咱们就能够利用该对象提供的 API 来拜访剪贴板,比方:

navigator.clipboard.readText().then(clipText => document.querySelector(".editor").innerText = clipText);

以上代码将 HTML 中含有 .editor 类的第一个元素的内容替换为剪贴板的内容。如果剪贴板为空,或者不蕴含任何文本,则元素的内容将被清空。这是因为在剪贴板为空或者不蕴含文本时,readText 办法会返回一个空字符串。

在持续介绍 Clipboard API 之前,咱们先来看一下 Navigator API: clipboard 的兼容性:

(图片起源:https://caniuse.com/mdn-api_n…)

异步剪贴板 API 是一个绝对较新的 API,浏览器仍在逐步实现它。因为潜在的平安问题和技术复杂性,大多数浏览器正在逐渐集成这个 API。对于浏览器扩大来说,你能够申请 clipboardRead 和 clipboardWrite 权限以应用 clipboard.readText() 和 clipboard.writeText()。

好的,接下来阿宝哥来演示一下如何应用 clipboard 对象提供的 API 来操作剪贴板,以下示例的运行环境是 Chrome 87.0.4280.88

三、将数据写入到剪贴板

3.1 writeText()

writeText 办法能够把指定的字符串写入到零碎的剪贴板中,调用该办法后会返回一个 Promise 对象:

<button onclick="copyPageUrl()"> 拷贝以后页面地址 </button>
<script>
   async function copyPageUrl() {
     try {await navigator.clipboard.writeText(location.href);
       console.log("页面地址曾经被拷贝到剪贴板中");
     } catch (err) {console.error("页面地址拷贝失败:", err);
     }
  }
</script>

对于上述代码,当用户点击 拷贝以后页面地址 按钮时,将会把以后的页面地址拷贝到剪贴板中。

3.2 write()

write 办法除了反对文本数据之外,还反对将图像数据写入到剪贴板,调用该办法后会返回一个 Promise 对象。

<button onclick="copyPageUrl()"> 拷贝以后页面地址 </button>
<script>
   async function copyPageUrl() {const text = new Blob([location.href], {type: 'text/plain'});
     try {
       await navigator.clipboard.write(
         new ClipboardItem({"text/plain": text,}),
       );
       console.log("页面地址曾经被拷贝到剪贴板中");
     } catch (err) {console.error("页面地址拷贝失败:", err);
     }
  }
</script>

在以上代码中,咱们先通过 Blob API 创立 Blob 对象,而后应用该 Blob 对象来结构 ClipboardItem 对象,最初再通过 write 办法把数据写入到剪贴板。介绍完如何将数据写入到剪贴板,上面咱们来介绍如何从剪贴板中读取数据。

对 Blob API 感兴趣的小伙伴,能够浏览 你不晓得的 Blob 这篇文章。

四、从剪贴板中读取数据

4.1 readText()

readText 办法用于读取剪贴板中的文本内容,调用该办法后会返回一个 Promise 对象:

<button onclick="getClipboardContents()"> 读取剪贴板中的文本 </button>
<script>
   async function getClipboardContents() {
     try {const text = await navigator.clipboard.readText();
       console.log("已读取剪贴板中的内容:", text);
     } catch (err) {console.error("读取剪贴板内容失败:", err);
     }
   }
</script>

对于上述代码,当用户点击 读取剪贴板中的文本 按钮时,如果以后剪贴板含有文本内容,则会读取剪贴板中的文本内容。

4.2 read()

read 办法除了反对读取文本数据之外,还反对读取剪贴板中的图像数据,调用该办法后会返回一个 Promise 对象:

<button onclick="getClipboardContents()"> 读取剪贴板中的内容 </button>
<script>
async function getClipboardContents() {
  try {const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {for (const type of clipboardItem.types) {const blob = await clipboardItem.getType(type);
        console.log("已读取剪贴板中的内容:", await blob.text());
      }
    }
  } catch (err) {console.error("读取剪贴板内容失败:", err);
    }
  }
</script>

对于上述代码,当用户点击 读取剪贴板中的内容 按钮时,则会开始读取剪贴板中的内容。到这里 clipboard 对象中波及的 4 个 API,阿宝哥都曾经介绍完了,最初咱们来看一下如何实现复制图像的性能。

五、实现复制图像的性能

在最初的这个示例中,阿宝哥将跟大家一步步实现复制图像的外围性能,除了复制图像之外,还会同时反对复制文本。在看具体代码前,咱们先来看一下理论的成果:

在上图对应的网页中,咱们先点击 复制 按钮,则图像和文本都会被选中。之后,咱们在点击 粘贴 按钮,则控制台会输入从剪贴板中读取的理论内容。在剖析具体的实现形式前,咱们先来看一下对应的页面构造:

<div id="container">
   <img src="http://cdn.semlinker.com/abao.png" width="80" height="80" />
   <p> 大家好,我是阿宝哥 </p>
</div>
<button onclick="writeDataToClipboard()"> 复制 </button>
<button onclick="readDataFromClipboard()"> 粘贴 </button>

下面的页面构造很简略,下一步咱们来逐渐剖析一下以上性能的实现过程。

5.1 申请剪贴板写权限

默认状况下,会为以后的激活的页面主动授予剪贴板的写入权限。出于平安方面思考,这里咱们还是被动向用户申请剪贴板的写入权限:

async function askWritePermission() {
  try {const { state} = await navigator.permissions.query({name: "clipboard-write",});
      return state === "granted";
  } catch (error) {return false;}
}

5.2 往剪贴板写入图像和一般文本数据

要往剪贴板写入图像数据,咱们就须要应用 navigator.clipboard 对象提供的 write 办法。如果要写入图像数据,咱们就须要获取该图像对应的 Blob 对象,这里咱们能够通过 fetch API 从网络上获取图像对应的响应对象并把它转化成 Blob 对象,具体实现形式如下:

async function createImageBlob(url) {const response = await fetch(url);
  return await response.blob();}

而对于一般文本来说,只须要应用后面介绍的 Blob API 就能够把一般文本转换为 Blob 对象:

function createTextBlob(text) {return new Blob(, { type: "text/plain"});
}

在创立完图像和一般文本对应的 Blob 对象之后,咱们就能够利用它们来创立 ClipboardItem 对象,而后再调用 write 办法把这些数据写入到剪贴板中,对应的代码如下所示:

async function writeDataToClipboard() {if (askWritePermission()) {if (navigator.clipboard && navigator.clipboard.write) {const textBlob = createTextBlob("大家好,我是阿宝哥");
        const imageBlob = await createImageBlob("http://cdn.semlinker.com/abao.png");
        try {
          const item = new ClipboardItem({[textBlob.type]: textBlob,
            [imageBlob.type]: imageBlob,
          });
          select(document.querySelector("#container"));
          await navigator.clipboard.write([item]);
          console.log("文本和图像复制胜利");
        } catch (error) {console.error("文本和图像复制失败", error);
        }
      }
   }
}

在以上代码中,应用了一个 select 办法,该办法用于实现抉择的成果,对应的代码如下所示:

function select(element) {const selection = window.getSelection();
  const range = document.createRange();
  range.selectNodeContents(element);
  selection.removeAllRanges();
  selection.addRange(range);
}

通过 writeDataToClipboard 办法,咱们曾经把图像和一般文本数据写入剪贴板了。上面咱们来应用 navigator.clipboard 对象提供的 read 办法,来读取已写入的数据。如果你须要读取剪贴板的数据,则须要向用户申请 clipboard-read 权限。

5.3 申请剪贴板读取权限

这里咱们定义了一个 askReadPermission 函数来向用户申请剪贴板读取权限:

async function askReadPermission() {
  try {const { state} = await navigator.permissions.query({name: "clipboard-read",});
    return state === "granted";
  } catch (error) {return false;}
}

当调用 askReadPermission 办法后,将会向以后用户申请剪贴板读取权限,对应的成果如下图所示:

5.4 读取剪贴板中已写入的数据

创立好 askReadPermission 函数,咱们就能够利用之前介绍的 navigator.clipboard.read 办法来读取剪贴板的数据了:

async function readDataFromClipboard() {if (askReadPermission()) {if (navigator.clipboard && navigator.clipboard.read) {
      try {const clipboardItems = await navigator.clipboard.read();
        for (const clipboardItem of clipboardItems) {console.dir(clipboardItem);
          for (const type of clipboardItem.types) {const blob = await clipboardItem.getType(type);
            console.log("已读取剪贴板中的内容:", await blob.text());
          }
        }
      } catch (err) {console.error("读取剪贴板内容失败:", err);
      }
     }
   }
}

其实,除了点击 粘贴 按钮之外,咱们还能够通过监听 paste 事件来读取剪贴板中的数据。须要留神的是,如果以后的浏览器不反对异步 Clipboard API,咱们能够通过 clipboardData.getData 办法来读取剪贴板中的文本数据:

document.addEventListener('paste', async (e) => {e.preventDefault();
  let text;
  if (navigator.clipboard) {text = await navigator.clipboard.readText();
  } else {text = e.clipboardData.getData('text/plain');
  }
  console.log('已获取的文本数据:', text);
});

而对于图像数据,则能够通过以下形式进行读取:

const IMAGE_MIME_REGEX = /^image\/(p?jpeg|gif|png)$/i;

document.addEventListener("paste", async (e) => {e.preventDefault();
  if (navigator.clipboard) {let clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {for (const type of clipboardItem.types) {if (IMAGE_MIME_REGEX.test(type)) {const blob = await clipboardItem.getType(type);
           loadImage(blob);
           return;
         }
        }
     }
   } else {
       const items = e.clipboardData.items;
       for (let i = 0; i < items.length; i++) {if (IMAGE_MIME_REGEX.test(items[i].type)) {loadImage(items[i].getAsFile());
         return;
       }
    }
  }
});

以上代码中的 loadImage 办法用于实现把复制的图片插入到以后选区已抉择的区域中,对应的代码如下:

function loadImage(file) {const reader = new FileReader();
  reader.onload = function (e) {let img = document.createElement("img");
    img.src = e.target.result;

    let range = window.getSelection().getRangeAt(0);
    range.deleteContents();
    range.insertNode(img);
  };
  reader.readAsDataURL(file);
}

在后面代码中,咱们监听了 document 对象的 paste 事件。除了该事件之外,与剪贴板相干的常见事件还有 copycut 事件。篇幅无限,阿宝哥就不持续开展介绍了,感兴趣的小伙伴能够自行浏览相干材料。好的,至此本文就曾经完结了,心愿浏览完本文之后,大家对异步的 Clipboard API 会有些理解,有写得不分明的中央,欢送你随时跟阿宝哥交换哟。

关注「全栈修仙之路」浏览阿宝哥原创的 4 本收费电子书(累计下载近 2 万)及 9 篇源码剖析系列教程。

查看”复制图片“残缺示例(Gist)

六、参考资源

  • 维基百科 – 剪贴板
  • MDN – Clipboard
  • MDN – execCommand
  • Web.dev – async-clipboard
  • W3C – clipboard-apis
退出移动版