Typescript implements one-click copy text to clipboard
Scenes
When building some pages that display code, a common requirement is to click a button to copy the page's code into the clipboard.
At present @vueuse/core
this Vue combined API tool library provides useClipboard
method to support the copy clipboard function, which can be implemented using the browser Clipboard API
.
The core code is await navigator!.clipboard.writeText(value)
My application scenario is in the process of using Vitepress + Typescript
to build the component library document station, and applied @vueuse/core
to realize clicking the button to copy the component code. In subsequent tests, it was found that the function of clicking the button to copy the code in the development environment is normal, but after packaging and deploying it to the production environment, clicking the button will prompt that the copy fails, and the two environments use the same version of the Chrome browser.
core code
<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>
By reading the source code of @vueuse/core
, you can find that its isSupported
judgment function uses Permissions API
.
core judgment method
permissionStatus = await navigator!.permissions.query('clipboard-write')
It is used to judge whether the user has write permission to the clipboard. In the production environment, isSupported
The judgment result is not supported, while in the development environment it is supported.
After analysis, it was found that in the browser F12 running the packaged code 'clipboard' in navigator === false
Looking back at the MDN documentation of Clipboard API
has a hint
Secure context: This feature is available only in secure contexts (HTTPS), in some or all supporting browsers.
and the question discussion on 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.
The conclusion is that Clipboard API
is only supported for use in Secure contexts, which refers to services accessed based on the https
protocol or localhost/127.0.0.1
local environment.
However, in the actual scenario, there are indeed services that need to be deployed in the ordinary http
environment, especially some projects within the enterprise, you need to find an alternative to Clipboard API
.
Program
Before the emergence of Clipboard API
, the mainstream clipboard operation was implemented using document.execCommand
;
The compatibility idea is to determine whether clipboard is supported, and if not, return document.execCommand
;
document.execCommand
The process of one-click copying
- Record the content of focus/select in the current page
- Create a new textarea
- Put the text you want to copy into
textarea.value
- Insert the textarea into the page document and style it so that it does not affect the display of the existing page
- Select the text of the textarea
-
document.execCommand
Copy into clipboard - remove textarea
- Restore the original selection on the page from the record
Implementation code 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()
}
}
}
use
<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>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。