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>

refer to


OceanZH
325 声望14 粉丝

前端开发