解决navigator.clipboard的兼容性问题
- 问题描述:在实现快捷复制的时候出现的一个问题,使用navigator.clipboard实现复制的时候,在本地测试没有问题,但是上线之后复制不能正常使用。
- 问题分析:这个问题可能是由于浏览器安全策略导致的。在本地开发环境中,navigator.clipboard API 可以正常工作,但在生产环境中,由于安全限制,只有在以下条件下才能使用剪贴板API:1. 网站必须使用HTTPS协议;2. 网页必须处于活动标签页;3. 用户必须已经授予剪贴板权限。建议检查生产环境是否满足这些条件,特别是HTTPS协议的支持情况。如果确实是HTTPS环境,那么可能是浏览器没有正确授予剪贴板权限,或者在某些浏览器中clipboard API的支持存在兼容性问题。但是我使用同一个浏览器,排除浏览器兼容,发现是上线之后的协议是http协议。
- 解决方案:由于环境使用的是HTTP协议,而navigator.clipboard API在非HTTPS环境下会受到安全策略限制。有以下解决方案:1. 将网站升级到HTTPS协议(推荐);2. 使用传统的document.execCommand(‘copy’)方法作为降级方案;3. 如果必须使用HTTP,可以考虑使用第三方复制库如clipboard.js作为替代方案。因为使用什么协议不是我决定的,所以最后用传统的document.execCommand(‘copy’)做降级处理。
- 代码:
<template>
<div class="text-container" @mouseenter="showButton = true" @mouseleave="showButton = false">
<!-- 复制按钮,仅在鼠标悬停时显示 -->
<el-tooltip placement="top" content="复制">
<el-icon v-if="showButton" class="copy-button" @click="handleCopy"><CopyDocument /></el-icon>
</el-tooltip>
<!-- 显示的内容 -->
<div class="content">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { CopyDocument } from "@element-plus/icons-vue";
import { message } from "@/utils/message";
const props = defineProps({
text: {
type: String,
required: true
}
});
const showButton = ref(false);
const handleCopy = () => {
navigator.clipboard
.writeText(props.text)
.then(() => {
message("复制成功", { type: "success" });
})
.catch(err => {
console.error("复制失败:", err);
message("复制失败", { type: "error" });
});
};
</script>
<style lang="scss">
.text-container {
position: relative;
display: block;
padding: 0 10px;
min-height: 24px;
}
.content {
padding-right: 30px;
word-break: break-word;
}
.copy-button {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
color: #409eff;
cursor: pointer;
font-size: 16px;
transition: all 0.3s;
&:hover {
color: #66b1ff;
}
}
.copy-button::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 32px;
height: 32px;
background-color: rgba(0, 0, 0, .03);
border-radius: 6px;
z-index: -1;
opacity: 0;
transition: opacity 0.3s;
}
.copy-button:hover::after {
opacity: 1;
}
</style>
<template>
<div class="text-container" @mouseenter="showButton = true" @mouseleave="showButton = false">
<!-- 复制按钮,仅在鼠标悬停时显示 -->
<el-tooltip placement="top" content="复制">
<el-icon v-if="showButton" class="copy-button" @click="handleCopy"><CopyDocument /></el-icon>
</el-tooltip>
<!-- 显示的内容 -->
<div class="content">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { CopyDocument } from "@element-plus/icons-vue";
import { message } from "@/utils/message";
const props = defineProps({
text: {
type: String,
required: true
}
});
const showButton = ref(false);
const handleCopy = async () => {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(props.text);
message("复制成功", { type: "success" });
return;
}
const textArea = document.createElement('textarea');
textArea.value = props.text;
document.body.appendChild(textArea);
textArea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
if (successful) {
message("复制成功", { type: "success" });
} else {
message("复制失败", { type: "error" });
}
} catch (err) {
console.error("复制失败:", err);
message("复制失败", { type: "error" });
}
};
</script>
<style lang="scss">
.text-container {
position: relative;
display: block;
padding: 0 10px;
min-height: 24px;
}
.content {
padding-right: 30px;
word-break: break-word;
}
.copy-button {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
color: #409eff;
cursor: pointer;
font-size: 16px;
transition: all 0.3s;
&:hover {
color: #66b1ff;
}
}
.copy-button::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 32px;
height: 32px;
background-color: rgba(0, 0, 0, .03);
border-radius: 6px;
z-index: -1;
opacity: 0;
transition: opacity 0.3s;
}
.copy-button:hover::after {
opacity: 1;
}
</style>