关于前端:Javascript一键复制文本进剪贴板

40次阅读

共计 3379 个字符,预计需要花费 9 分钟才能阅读完成。

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

正文完
 0