在Web开发中,复制当前页面URL地址是一个看似简单但实际高频使用的功能需求。特别是在Vue3单页应用(SPA)环境下,由于路由机制的特殊性,直接使用原生JavaScript的window.location.href可能会遇到哈希模式路由的兼容问题。
这个功能的核心价值在于:
现代浏览器提供了Clipboard API来实现安全的剪贴板操作:
javascript复制async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text)
return true
} catch (err) {
console.error('复制失败:', err)
return false
}
}
注意:Clipboard API要求页面必须通过HTTPS提供服务,或在localhost开发环境下才能正常工作
对于不支持Clipboard API的旧浏览器,可以采用经典的document.execCommand方法:
javascript复制function fallbackCopy(text) {
const textarea = document.createElement('textarea')
textarea.value = text
document.body.appendChild(textarea)
textarea.select()
try {
document.execCommand('copy')
return true
} catch (err) {
console.error('复制失败:', err)
return false
} finally {
document.body.removeChild(textarea)
}
}
结合Vue3的Composition API,我们可以封装一个可复用的hook:
javascript复制// useClipboard.js
import { ref } from 'vue'
export function useClipboard() {
const isCopied = ref(false)
const copy = async (text) => {
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(text)
} else {
fallbackCopy(text)
}
isCopied.value = true
setTimeout(() => isCopied.value = false, 2000)
return true
} catch (error) {
console.error('复制失败:', error)
return false
}
}
return { isCopied, copy }
}
function fallbackCopy(text) {
// 同上省略
}
在Vue Router中获取完整URL需要考虑路由模式:
javascript复制import { useRouter } from 'vue-router'
const router = useRouter()
function getCurrentURL() {
// 基础URL处理
const baseUrl = window.location.origin
if (router.mode === 'history') {
return baseUrl + router.currentRoute.value.fullPath
} else {
// hash模式处理
return baseUrl + '#' + router.currentRoute.value.fullPath
}
}
结合上述技术点,实现一个可复用的复制按钮组件:
vue复制<template>
<button @click="handleCopy" :disabled="isCopied">
{{ isCopied ? '已复制!' : '复制链接' }}
</button>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useClipboard } from './useClipboard'
const router = useRouter()
const { isCopied, copy } = useClipboard()
function getCurrentUrl() {
const base = window.location.origin
return router.mode === 'history'
? base + router.currentRoute.value.fullPath
: base + '#' + router.currentRoute.value.fullPath
}
function handleCopy() {
copy(getCurrentUrl())
}
</script>
移动端浏览器存在一些特殊限制:
解决方案:
javascript复制function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
)
}
async function mobileCopy(text) {
if (isMobile()) {
// 创建临时输入框获取焦点
const input = document.createElement('input')
input.value = text
document.body.appendChild(input)
input.select()
input.setSelectionRange(0, 99999)
document.execCommand('copy')
document.body.removeChild(input)
return true
}
return false
}
提供更好的视觉反馈:
vue复制<template>
<button @click="handleCopy" :class="{ 'copied': isCopied }">
<span v-if="!isCopied">复制链接</span>
<span v-else>
<svg class="checkmark" viewBox="0 0 52 52">...</svg>
已复制!
</span>
</button>
</template>
<style>
.copied {
background-color: #4CAF50;
color: white;
}
.checkmark {
width: 1em;
height: 1em;
margin-right: 0.5em;
fill: currentColor;
}
</style>
完善的错误处理机制:
javascript复制async function copyWithFallback(text) {
try {
// 尝试现代API
if (navigator.clipboard) {
await navigator.clipboard.writeText(text)
return true
}
// 尝试移动端方案
if (isMobile() && await mobileCopy(text)) {
return true
}
// 最终降级方案
return fallbackCopy(text)
} catch (error) {
console.error('所有复制方案均失败:', error)
// 显示提示让用户手动复制
alert(`请手动复制以下内容:\n\n${text}`)
return false
}
}
复制内容不正确
window.location.origin是否包含协议头移动端复制失效
HTTPS要求
延迟加载Clipboard API
javascript复制const clipboardAPI = navigator.clipboard
? navigator.clipboard
: { writeText: (text) => fallbackCopy(text) }
减少DOM操作
防抖处理
javascript复制import { debounce } from 'lodash-es'
const debouncedCopy = debounce(copy, 300)
权限控制
用户隐私
错误监控
javascript复制function trackCopyError(error) {
if (typeof Sentry !== 'undefined') {
Sentry.captureException(error)
}
}
以下是生产环境可用的完整实现:
vue复制<template>
<button
@click="handleCopy"
:class="['copy-btn', { 'copied': isCopied }]"
:disabled="isCopied"
aria-label="复制当前页面链接"
>
<span v-if="!isCopied">
<svg class="icon" viewBox="0 0 24 24">...</svg>
复制链接
</span>
<span v-else>
<svg class="checkmark" viewBox="0 0 52 52">...</svg>
已复制!
</span>
</button>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { debounce } from 'lodash-es'
const router = useRouter()
const isCopied = ref(false)
function getCurrentUrl() {
const base = window.location.origin
const path = router.currentRoute.value.fullPath
return router.mode === 'history' ? `${base}${path}` : `${base}#${path}`
}
async function copyToClipboard(text) {
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(text)
return true
}
return fallbackCopy(text)
} catch (error) {
console.error('复制失败:', error)
return fallbackCopy(text)
}
}
function fallbackCopy(text) {
const textarea = document.createElement('textarea')
textarea.value = text
textarea.style.position = 'fixed'
textarea.style.opacity = '0'
document.body.appendChild(textarea)
textarea.select()
try {
const successful = document.execCommand('copy')
document.body.removeChild(textarea)
return successful
} catch (err) {
document.body.removeChild(textarea)
return false
}
}
const handleCopy = debounce(async () => {
const url = getCurrentUrl()
const success = await copyToClipboard(url)
isCopied.value = success
if (success) {
setTimeout(() => {
isCopied.value = false
}, 2000)
} else {
alert(`复制失败,请手动复制:\n\n${url}`)
}
}, 300)
</script>
<style scoped>
.copy-btn {
display: inline-flex;
align-items: center;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
transition: all 0.2s;
}
.copy-btn:hover {
background: #f5f5f5;
}
.copy-btn.copied {
background: #4CAF50;
color: white;
border-color: #4CAF50;
}
.icon, .checkmark {
width: 1em;
height: 1em;
margin-right: 0.5em;
fill: currentColor;
}
</style>
这个实现包含了以下关键特性:
在实际项目中,你可以直接引入这个组件,或者根据具体需求进行进一步定制。例如添加多语言支持、调整样式以匹配你的设计系统等。