Javascript一键复制文本进剪贴板

场景

在搭建组件库的文档时,一个常见的需要是点击按钮能够把页面的代码复制进剪贴板。

目前 @vueuse/core 这个 Vue 的组合式 API 工具库提供了 useClipboard 办法来反对复制剪贴板性能,应用浏览器 Clipboard API 实现。
外围代码是 await navigator!.clipboard.writeText(value)

在应用 Vitepress + @vueuse/core 搭建文档站的过程中,呈现了一个景象,在开发环境中点击按钮复制代码的性能失常,然而在进行打包部署至生产环境后,点击按钮会提醒复制失败,两个环境应用的是同一版本的 Chrome 浏览器。

外围代码
<script setup lang="ts">import { useClipboard } from '@vueuse/core'const vm = getCurrentInstance()!const props = defineProps<{  rawSource: string}>()const { copy, isSupported } = useClipboard({  source: decodeURIComponent(props.rawSource),  read: false,})const copyCode = async () => {    // $message来自element-plus   const { $message } = vm.appContext.config.globalProperties;  if (!isSupported) {    $message.error('复制失败')  }  try {    await copy()    $message.success('复制胜利')  } catch (e: any) {    $message.error(e.message)  }}</script>

通过浏览 @vueuse/core 的源码,能够发现其isSupported 判断性能应用 Permissions API

外围的判断办法 permissionStatus = await navigator!.permissions.query('clipboard-write')
用于判断用户是否有对剪贴板的写入权限,而在生产环境中,isSupported 判断的后果是不反对,而在开发环境中则是反对。

通过剖析,发现跑打包后代码的浏览器 F12 中 'clipboard' in navigator === false

回头查阅 Clipboard API 的MDN文档有一项提醒

Secure context: This feature is available only in secure contexts (HTTPS), in some or all supporting browsers.

以及 stackoverflow 上的问题探讨

This requires a secure origin — either HTTPS or localhost (or disabled by running Chrome with a flag). Just like for ServiceWorker, this state is indicated by the presence or absence of the property on the navigator object.

论断是 Clipboard API 仅反对在 平安上下文(Secure contexts) 中应用,在这里指的是基于 https 协定或者 localhost/127.0.0.1 本地环境拜访的服务。

然而理论场景中的确存在须要部署在一般 http 环境中的服务,尤其是一些在企业外部的我的项目,须要寻找 Clipboard API 的代替计划。

计划

Clipboard API 呈现之前,支流的剪切板操作应用 document.execCommand 来实现;

兼容思路是,判断是否反对 clipboard,不反对则退回 document.execCommand

document.execCommand 实现一键点击复制的流程

  • 记录以后页面中 focus/select 的内容
  • 新建一个 textarea
  • 将要复制的文本放入 textarea.value
  • 将 textarea 插入页面 document,并且设置款式使其不影响现有页面的展现
  • 选中 textarea 的文本
  • document.execCommand 复制进剪贴板
  • 移除 textarea
  • 从记录中还原页面中原选中内容
实现代码 copy-code.ts
export async function copyToClipboard(text: string) {  try {    return await navigator.clipboard.writeText(text)  } catch {    const element = document.createElement('textarea')    const previouslyFocusedElement = document.activeElement    element.value = text    // Prevent keyboard from showing on mobile    element.setAttribute('readonly', '')    element.style.contain = 'strict'    element.style.position = 'absolute'    element.style.left = '-9999px'    element.style.fontSize = '12pt' // Prevent zooming on iOS    const selection = document.getSelection()    const originalRange = selection      ? selection.rangeCount > 0 && selection.getRangeAt(0)      : null    document.body.appendChild(element)    element.select()    // Explicit selection workaround for iOS    element.selectionStart = 0    element.selectionEnd = text.length    document.execCommand('copy')    document.body.removeChild(element)    if (originalRange) {      selection!.removeAllRanges() // originalRange can't be truthy when selection is falsy      selection!.addRange(originalRange)    }    // Get the focus back on the previously focused element, if any    if (previouslyFocusedElement) {      ;(previouslyFocusedElement as HTMLElement).focus()    }  }}
应用
<script setup lang="ts">import { copyToClipboard } from './copy-code';const vm = getCurrentInstance()!const props = defineProps<{  rawSource: string}>()const copyCode = async () => {    // $message来自element-plus   const { $message } = vm.appContext.config.globalProperties;  try {    await copyToClipboard(decodeURIComponent(props.rawSource))    $message.success('复制胜利')  } catch (e: any) {    $message.error(e.message)  }}</script>

参考

  • Clipboard API
  • Permissions API
  • document.execCommand
  • google chrome - navigator.clipboard is undefined - Stack Overflow
  • useClipboard | VueUse
  • fix: copy code in non-secure contexts (#792) · vuejs/vitepress@2935ed2