共计 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