剪贴板API(Clipboard API)提供了异步访问剪贴板的能力,可以读取和写入文本、图片等多种格式的数据。相比传统的document.execCommand()方法,新的剪贴板API更加现代化和安全。
东巴文(db-w.cn) 认为:剪贴板API让Web应用能够更好地与系统剪贴板交互,提升用户体验。
| 特点 | 说明 |
|---|---|
| 异步操作 | 使用Promise,不阻塞主线程 |
| 权限管理 | 需要用户授权,更安全 |
| 多种格式 | 支持文本、图片等多种格式 |
| 现代API | 替代传统的execCommand方法 |
| 跨浏览器 | 主流浏览器支持良好 |
// 剪贴板API方法
const ClipboardMethods = {
// 写入文本
writeText: navigator.clipboard.writeText,
// 读取文本
readText: navigator.clipboard.readText,
// 写入多种格式
write: navigator.clipboard.write,
// 读取多种格式
read: navigator.clipboard.read
};
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>写入剪贴板示例</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.copy-container {
background: #f9f9f9;
padding: 30px;
border-radius: 10px;
margin: 20px 0;
}
.text-area {
width: 100%;
min-height: 120px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
resize: vertical;
margin-bottom: 15px;
}
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
padding: 12px 24px;
font-size: 16px;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
}
button:hover {
background: #0056b3;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.copy-btn {
background: #28a745;
}
.copy-btn:hover {
background: #218838;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
background: #28a745;
color: white;
border-radius: 5px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
transform: translateX(200%);
transition: transform 0.3s;
}
.notification.show {
transform: translateX(0);
}
.notification.error {
background: #dc3545;
}
.examples {
margin-top: 30px;
}
.example-item {
display: flex;
align-items: center;
gap: 10px;
padding: 15px;
background: white;
border-radius: 5px;
margin-bottom: 10px;
}
.example-item code {
flex: 1;
padding: 10px;
background: #f5f5f5;
border-radius: 3px;
font-family: 'Courier New', monospace;
}
.example-item button {
padding: 8px 16px;
font-size: 14px;
}
</style>
</head>
<body>
<h1>写入剪贴板示例</h1>
<div class="copy-container">
<h2>复制文本</h2>
<textarea class="text-area" id="textInput" placeholder="输入要复制的文本...">这是一段示例文本,可以复制到剪贴板。</textarea>
<div class="button-group">
<button class="copy-btn" onclick="copyText()">复制文本</button>
<button onclick="copySpecialText()">复制特殊文本</button>
<button onclick="copyMultiple()">批量复制</button>
</div>
</div>
<div class="examples">
<h2>快速复制示例</h2>
<div class="example-item">
<code>https://db-w.cn</code>
<button onclick="copyToClipboard('https://db-w.cn')">复制</button>
</div>
<div class="example-item">
<code>contact@db-w.cn</code>
<button onclick="copyToClipboard('contact@db-w.cn')">复制</button>
</div>
<div class="example-item">
<code>console.log('Hello, World!');</code>
<button onclick="copyToClipboard('console.log(\'Hello, World!\');')">复制</button>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
// 复制文本
async function copyText() {
const text = document.getElementById('textInput').value;
if (!text) {
showNotification('请输入要复制的文本', 'error');
return;
}
try {
await navigator.clipboard.writeText(text);
showNotification('文本已复制到剪贴板!');
} catch (err) {
showNotification('复制失败: ' + err.message, 'error');
}
}
// 复制特殊文本
async function copySpecialText() {
const specialText = `
特殊文本示例:
• 时间:${new Date().toLocaleString()}
• 随机数:${Math.random().toFixed(6)}
• 用户代理:${navigator.userAgent}
`.trim();
try {
await navigator.clipboard.writeText(specialText);
showNotification('特殊文本已复制!');
} catch (err) {
showNotification('复制失败: ' + err.message, 'error');
}
}
// 批量复制
async function copyMultiple() {
const items = [
'第一项内容',
'第二项内容',
'第三项内容'
];
const text = items.join('\n');
try {
await navigator.clipboard.writeText(text);
showNotification('批量内容已复制!');
} catch (err) {
showNotification('复制失败: ' + err.message, 'error');
}
}
// 复制到剪贴板
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
showNotification('已复制: ' + text);
} catch (err) {
showNotification('复制失败: ' + err.message, 'error');
}
}
// 显示通知
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = 'notification ' + type + ' show';
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// 检查API支持
if (!navigator.clipboard) {
showNotification('您的浏览器不支持剪贴板API', 'error');
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>读取剪贴板示例</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.paste-container {
background: #f9f9f9;
padding: 30px;
border-radius: 10px;
margin: 20px 0;
}
.paste-area {
width: 100%;
min-height: 150px;
padding: 15px;
border: 2px dashed #ddd;
border-radius: 5px;
font-size: 16px;
resize: vertical;
margin-bottom: 15px;
background: white;
transition: border-color 0.3s;
}
.paste-area:focus {
outline: none;
border-color: #007bff;
}
button {
padding: 12px 24px;
font-size: 16px;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background: #0056b3;
}
.paste-btn {
background: #17a2b8;
}
.paste-btn:hover {
background: #138496;
}
.info-box {
background: #e7f3ff;
border-left: 4px solid #007bff;
padding: 15px;
margin: 20px 0;
border-radius: 5px;
}
.permission-status {
display: inline-block;
padding: 5px 10px;
border-radius: 3px;
font-size: 14px;
margin-left: 10px;
}
.permission-status.granted {
background: #d4edda;
color: #155724;
}
.permission-status.denied {
background: #f8d7da;
color: #721c24;
}
.permission-status.prompt {
background: #fff3cd;
color: #856404;
}
</style>
</head>
<body>
<h1>读取剪贴板示例</h1>
<div class="paste-container">
<h2>粘贴文本 <span class="permission-status" id="permissionStatus">检查权限中...</span></h2>
<textarea class="paste-area" id="pasteArea" placeholder="点击下方按钮粘贴剪贴板内容,或直接在此处粘贴..."></textarea>
<button class="paste-btn" onclick="pasteText()">从剪贴板粘贴</button>
<button onclick="checkPermission()">检查权限</button>
<button onclick="requestPermission()">请求权限</button>
</div>
<div class="info-box">
<strong>说明:</strong>
<ul>
<li>读取剪贴板需要用户授权</li>
<li>某些浏览器要求页面必须是活动标签页</li>
<li>用户也可以使用Ctrl+V或右键菜单粘贴</li>
</ul>
</div>
<script>
// 检查权限
async function checkPermission() {
const statusElem = document.getElementById('permissionStatus');
try {
const result = await navigator.permissions.query({ name: 'clipboard-read' });
statusElem.textContent = result.state;
statusElem.className = 'permission-status ' + result.state;
// 监听权限变化
result.onchange = function() {
statusElem.textContent = this.state;
statusElem.className = 'permission-status ' + this.state;
};
} catch (err) {
statusElem.textContent = '不支持';
statusElem.className = 'permission-status';
}
}
// 请求权限
async function requestPermission() {
try {
const result = await navigator.permissions.query({ name: 'clipboard-read' });
if (result.state === 'prompt') {
// 尝试读取剪贴板,这会触发权限请求
await navigator.clipboard.readText();
}
checkPermission();
} catch (err) {
console.error('权限请求失败:', err);
}
}
// 粘贴文本
async function pasteText() {
try {
const text = await navigator.clipboard.readText();
document.getElementById('pasteArea').value = text;
console.log('已从剪贴板粘贴文本');
} catch (err) {
console.error('粘贴失败:', err);
alert('无法读取剪贴板。请确保已授权剪贴板访问权限。');
}
}
// 监听粘贴事件
document.getElementById('pasteArea').addEventListener('paste', function(e) {
console.log('用户粘贴了内容');
// 可以阻止默认行为,自定义处理
// e.preventDefault();
// const text = e.clipboardData.getData('text');
// this.value = text;
});
// 初始检查
checkPermission();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>写入多种格式示例</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.format-container {
background: #f9f9f9;
padding: 30px;
border-radius: 10px;
margin: 20px 0;
}
.format-item {
margin-bottom: 20px;
}
.format-item label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
textarea {
width: 100%;
min-height: 100px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
resize: vertical;
}
button {
padding: 12px 24px;
font-size: 16px;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin: 5px;
}
button:hover {
background: #0056b3;
}
.result {
margin-top: 20px;
padding: 15px;
background: white;
border-radius: 5px;
border-left: 4px solid #28a745;
}
.result.error {
border-left-color: #dc3545;
background: #fff5f5;
}
</style>
</head>
<body>
<h1>写入多种格式示例</h1>
<div class="format-container">
<h2>复制多种格式</h2>
<div class="format-item">
<label>纯文本格式:</label>
<textarea id="plainText">这是纯文本内容</textarea>
</div>
<div class="format-item">
<label>HTML格式:</label>
<textarea id="htmlText"><strong>这是HTML内容</strong>,包含<strong>加粗</strong>和<em>斜体</em>。</textarea>
</div>
<button onclick="copyMultipleFormats()">复制多种格式</button>
<button onclick="copyAsHTML()">仅复制HTML</button>
<button onclick="copyAsText()">仅复制文本</button>
<div class="result" id="result" style="display: none;"></div>
</div>
<script>
// 复制多种格式
async function copyMultipleFormats() {
const plainText = document.getElementById('plainText').value;
const htmlText = document.getElementById('htmlText').value;
try {
// 创建ClipboardItem
const data = [
new ClipboardItem({
'text/plain': new Blob([plainText], { type: 'text/plain' }),
'text/html': new Blob([htmlText], { type: 'text/html' })
})
];
await navigator.clipboard.write(data);
showResult('已复制多种格式到剪贴板!\n粘贴到富文本编辑器时会保留格式。');
} catch (err) {
showResult('复制失败: ' + err.message, true);
}
}
// 仅复制HTML
async function copyAsHTML() {
const htmlText = document.getElementById('htmlText').value;
try {
const data = [
new ClipboardItem({
'text/html': new Blob([htmlText], { type: 'text/html' }),
'text/plain': new Blob([htmlText.replace(/<[^>]*>/g, '')], { type: 'text/plain' })
})
];
await navigator.clipboard.write(data);
showResult('已复制HTML格式!');
} catch (err) {
showResult('复制失败: ' + err.message, true);
}
}
// 仅复制文本
async function copyAsText() {
const plainText = document.getElementById('plainText').value;
try {
await navigator.clipboard.writeText(plainText);
showResult('已复制纯文本格式!');
} catch (err) {
showResult('复制失败: ' + err.message, true);
}
}
// 显示结果
function showResult(message, isError = false) {
const result = document.getElementById('result');
result.textContent = message;
result.className = 'result' + (isError ? ' error' : '');
result.style.display = 'block';
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>复制图片示例</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.image-container {
background: #f9f9f9;
padding: 30px;
border-radius: 10px;
margin: 20px 0;
}
.canvas-area {
width: 100%;
height: 300px;
background: white;
border: 2px dashed #ddd;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
overflow: hidden;
}
#drawCanvas {
border: 1px solid #ddd;
border-radius: 5px;
cursor: crosshair;
}
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
padding: 12px 24px;
font-size: 16px;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
.color-picker {
display: flex;
align-items: center;
gap: 10px;
}
.color-picker input {
width: 50px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
background: #28a745;
color: white;
border-radius: 5px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
transform: translateX(200%);
transition: transform 0.3s;
}
.notification.show {
transform: translateX(0);
}
</style>
</head>
<body>
<h1>复制图片示例</h1>
<div class="image-container">
<h2>绘制并复制图片</h2>
<div class="canvas-area">
<canvas id="drawCanvas" width="400" height="250"></canvas>
</div>
<div class="controls">
<div class="color-picker">
<label>画笔颜色:</label>
<input type="color" id="colorPicker" value="#007bff">
</div>
<button onclick="clearCanvas()">清空画布</button>
<button onclick="copyCanvas()">复制图片</button>
<button onclick="downloadCanvas()">下载图片</button>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
const canvas = document.getElementById('drawCanvas');
const ctx = canvas.getContext('2d');
const colorPicker = document.getElementById('colorPicker');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
// 初始化画布
function initCanvas() {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = colorPicker.value;
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
}
// 开始绘制
canvas.addEventListener('mousedown', function(e) {
isDrawing = true;
[lastX, lastY] = [e.offsetX, e.offsetY];
});
// 绘制
canvas.addEventListener('mousemove', function(e) {
if (!isDrawing) return;
ctx.strokeStyle = colorPicker.value;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
[lastX, lastY] = [e.offsetX, e.offsetY];
});
// 停止绘制
canvas.addEventListener('mouseup', () => isDrawing = false);
canvas.addEventListener('mouseout', () => isDrawing = false);
// 清空画布
function clearCanvas() {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 复制画布
async function copyCanvas() {
try {
// 转换为Blob
const blob = await new Promise(resolve => {
canvas.toBlob(resolve, 'image/png');
});
// 复制到剪贴板
await navigator.clipboard.write([
new ClipboardItem({
'image/png': blob
})
]);
showNotification('图片已复制到剪贴板!');
} catch (err) {
showNotification('复制失败: ' + err.message);
console.error(err);
}
}
// 下载画布
function downloadCanvas() {
const link = document.createElement('a');
link.download = 'drawing.png';
link.href = canvas.toDataURL();
link.click();
}
// 显示通知
function showNotification(message) {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// 初始化
initCanvas();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>剪贴板事件示例</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.event-container {
background: #f9f9f9;
padding: 30px;
border-radius: 10px;
margin: 20px 0;
}
.input-area {
width: 100%;
min-height: 100px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
resize: vertical;
margin-bottom: 15px;
}
.event-log {
max-height: 300px;
overflow-y: auto;
background: white;
border-radius: 5px;
padding: 15px;
margin-top: 20px;
}
.log-entry {
padding: 10px;
margin: 5px 0;
background: #f5f5f5;
border-radius: 3px;
border-left: 4px solid #007bff;
font-size: 14px;
}
.log-entry.copy {
border-left-color: #28a745;
}
.log-entry.cut {
border-left-color: #ffc107;
}
.log-entry.paste {
border-left-color: #17a2b8;
}
.log-entry .time {
color: #666;
font-size: 12px;
}
.log-entry .type {
font-weight: bold;
color: #333;
}
.log-entry .data {
color: #666;
margin-top: 5px;
word-break: break-all;
}
</style>
</head>
<body>
<h1>剪贴板事件示例</h1>
<div class="event-container">
<h2>剪贴板事件监听</h2>
<p>在下方文本框中尝试复制、剪切、粘贴操作:</p>
<textarea class="input-area" id="inputArea" placeholder="在此输入文本,然后尝试复制、剪切、粘贴..."></textarea>
<div class="event-log" id="eventLog">
<p style="color: #999; text-align: center;">事件日志将显示在这里...</p>
</div>
</div>
<script>
const inputArea = document.getElementById('inputArea');
const eventLog = document.getElementById('eventLog');
// 添加日志
function addLog(type, data) {
// 移除初始提示
const placeholder = eventLog.querySelector('p');
if (placeholder) {
placeholder.remove();
}
const entry = document.createElement('div');
entry.className = 'log-entry ' + type.toLowerCase();
entry.innerHTML = `
<div>
<span class="time">[${new Date().toLocaleTimeString()}]</span>
<span class="type">${type} 事件</span>
</div>
<div class="data">${data}</div>
`;
eventLog.insertBefore(entry, eventLog.firstChild);
}
// 监听复制事件
inputArea.addEventListener('copy', function(e) {
const selection = window.getSelection().toString();
addLog('Copy', `复制内容: "${selection}"`);
// 可以修改复制的内容
// e.preventDefault();
// e.clipboardData.setData('text/plain', '修改后的内容');
});
// 监听剪切事件
inputArea.addEventListener('cut', function(e) {
const selection = window.getSelection().toString();
addLog('Cut', `剪切内容: "${selection}"`);
});
// 监听粘贴事件
inputArea.addEventListener('paste', function(e) {
// 获取剪贴板数据
const clipboardData = e.clipboardData || window.clipboardData;
const text = clipboardData.getData('text/plain');
const html = clipboardData.getData('text/html');
let logData = `粘贴文本: "${text}"`;
if (html) {
logData += `\nHTML内容: ${html.substring(0, 100)}...`;
}
addLog('Paste', logData);
// 可以阻止默认粘贴行为
// e.preventDefault();
// 自定义粘贴逻辑
});
// 监听beforecopy事件
inputArea.addEventListener('beforecopy', function(e) {
console.log('beforecopy事件触发');
});
// 监听beforecut事件
inputArea.addEventListener('beforecut', function(e) {
console.log('beforecut事件触发');
});
// 监听beforepaste事件
inputArea.addEventListener('beforepaste', function(e) {
console.log('beforepaste事件触发');
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>剪贴板工具 - 东巴文</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
padding: 30px 0;
}
.header h1 {
font-size: 36px;
margin-bottom: 10px;
}
.card {
background: white;
border-radius: 15px;
padding: 30px;
margin: 20px 0;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.card h2 {
margin-bottom: 20px;
color: #333;
}
.clipboard-history {
max-height: 400px;
overflow-y: auto;
}
.history-item {
display: flex;
align-items: center;
padding: 15px;
border-bottom: 1px solid #eee;
transition: background 0.3s;
}
.history-item:hover {
background: #f9f9f9;
}
.history-content {
flex: 1;
overflow: hidden;
}
.history-text {
font-size: 14px;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.history-time {
font-size: 12px;
color: #999;
margin-top: 5px;
}
.history-actions {
display: flex;
gap: 5px;
}
.action-btn {
padding: 8px 15px;
font-size: 12px;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.action-btn:hover {
background: #0056b3;
}
.action-btn.delete {
background: #dc3545;
}
.action-btn.delete:hover {
background: #c82333;
}
.quick-actions {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin: 20px 0;
}
.quick-btn {
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
transition: transform 0.3s;
font-size: 16px;
}
.quick-btn:hover {
transform: translateY(-5px);
}
.quick-btn .icon {
font-size: 24px;
display: block;
margin-bottom: 10px;
}
.text-input {
width: 100%;
min-height: 150px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 10px;
font-size: 16px;
resize: vertical;
margin-bottom: 15px;
}
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
font-size: 16px;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.btn:hover {
background: #0056b3;
}
.btn-success {
background: #28a745;
}
.btn-success:hover {
background: #218838;
}
.empty-state {
text-align: center;
padding: 40px;
color: #999;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
background: #28a745;
color: white;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
transform: translateX(200%);
transition: transform 0.3s;
z-index: 1000;
}
.notification.show {
transform: translateX(0);
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📋 剪贴板工具</h1>
<p>管理和操作剪贴板内容</p>
</div>
<div class="card">
<h2>快速操作</h2>
<div class="quick-actions">
<button class="quick-btn" onclick="copyTimestamp()">
<span class="icon">🕐</span>
复制时间戳
</button>
<button class="quick-btn" onclick="copyDate()">
<span class="icon">📅</span>
复制日期
</button>
<button class="quick-btn" onclick="copyUUID()">
<span class="icon">🔑</span>
复制UUID
</button>
<button class="quick-btn" onclick="copyRandomString()">
<span class="icon">🎲</span>
复制随机字符串
</button>
</div>
</div>
<div class="card">
<h2>自定义内容</h2>
<textarea class="text-input" id="customText" placeholder="输入要复制的内容..."></textarea>
<div class="button-group">
<button class="btn btn-success" onclick="copyCustomText()">复制</button>
<button class="btn" onclick="pasteCustomText()">粘贴</button>
<button class="btn" onclick="clearCustomText()">清空</button>
</div>
</div>
<div class="card">
<h2>剪贴板历史</h2>
<div class="clipboard-history" id="history">
<div class="empty-state">暂无历史记录</div>
</div>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
// 剪贴板管理器
class ClipboardManager {
constructor() {
this.history = [];
this.maxHistory = 20;
}
async copy(text) {
try {
await navigator.clipboard.writeText(text);
this.addToHistory(text);
return true;
} catch (err) {
console.error('复制失败:', err);
return false;
}
}
async paste() {
try {
const text = await navigator.clipboard.readText();
return text;
} catch (err) {
console.error('粘贴失败:', err);
return null;
}
}
addToHistory(text) {
// 避免重复
const exists = this.history.find(item => item.text === text);
if (exists) {
exists.time = new Date();
this.renderHistory();
return;
}
this.history.unshift({
text: text,
time: new Date()
});
// 限制历史数量
if (this.history.length > this.maxHistory) {
this.history.pop();
}
this.renderHistory();
}
deleteFromHistory(index) {
this.history.splice(index, 1);
this.renderHistory();
}
renderHistory() {
const container = document.getElementById('history');
if (this.history.length === 0) {
container.innerHTML = '<div class="empty-state">暂无历史记录</div>';
return;
}
container.innerHTML = this.history.map((item, index) => `
<div class="history-item">
<div class="history-content">
<div class="history-text">${this.escapeHtml(item.text)}</div>
<div class="history-time">${item.time.toLocaleString()}</div>
</div>
<div class="history-actions">
<button class="action-btn" onclick="copyFromHistory(${index})">复制</button>
<button class="action-btn delete" onclick="deleteFromHistory(${index})">删除</button>
</div>
</div>
`).join('');
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// 初始化管理器
const clipboardManager = new ClipboardManager();
// 复制时间戳
async function copyTimestamp() {
const timestamp = Date.now().toString();
if (await clipboardManager.copy(timestamp)) {
showNotification('时间戳已复制: ' + timestamp);
}
}
// 复制日期
async function copyDate() {
const date = new Date().toLocaleString('zh-CN');
if (await clipboardManager.copy(date)) {
showNotification('日期已复制: ' + date);
}
}
// 复制UUID
async function copyUUID() {
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
if (await clipboardManager.copy(uuid)) {
showNotification('UUID已复制: ' + uuid);
}
}
// 复制随机字符串
async function copyRandomString() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const length = 16;
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
if (await clipboardManager.copy(result)) {
showNotification('随机字符串已复制: ' + result);
}
}
// 复制自定义文本
async function copyCustomText() {
const text = document.getElementById('customText').value;
if (!text) {
showNotification('请输入要复制的内容');
return;
}
if (await clipboardManager.copy(text)) {
showNotification('内容已复制');
}
}
// 粘贴自定义文本
async function pasteCustomText() {
const text = await clipboardManager.paste();
if (text) {
document.getElementById('customText').value = text;
showNotification('内容已粘贴');
} else {
showNotification('粘贴失败,请检查权限');
}
}
// 清空自定义文本
function clearCustomText() {
document.getElementById('customText').value = '';
}
// 从历史记录复制
async function copyFromHistory(index) {
const item = clipboardManager.history[index];
if (item && await clipboardManager.copy(item.text)) {
showNotification('内容已复制');
}
}
// 从历史记录删除
function deleteFromHistory(index) {
clipboardManager.deleteFromHistory(index);
showNotification('已删除');
}
// 显示通知
function showNotification(message) {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
</script>
</body>
</html>
// 推荐:检查权限并处理
async function copyWithPermission(text) {
try {
// 检查权限
const permission = await navigator.permissions.query({ name: 'clipboard-write' });
if (permission.state === 'granted' || permission.state === 'prompt') {
await navigator.clipboard.writeText(text);
return true;
}
return false;
} catch (err) {
// 降级处理
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 {
document.execCommand('copy');
return true;
} catch (err) {
return false;
} finally {
document.body.removeChild(textarea);
}
}
// 推荐:完善的错误处理
async function safeCopy(text) {
// 检查API支持
if (!navigator.clipboard) {
throw new Error('浏览器不支持剪贴板API');
}
// 检查参数
if (typeof text !== 'string') {
throw new Error('参数必须是字符串');
}
try {
await navigator.clipboard.writeText(text);
console.log('复制成功');
return true;
} catch (err) {
console.error('复制失败:', err);
// 根据错误类型处理
if (err.name === 'NotAllowedError') {
alert('请授权剪贴板访问权限');
} else if (err.name === 'NotFoundError') {
alert('剪贴板不可用');
} else {
alert('复制失败,请重试');
}
return false;
}
}
// 推荐:提供反馈
async function copyWithFeedback(text) {
const btn = event.target;
const originalText = btn.textContent;
// 显示加载状态
btn.disabled = true;
btn.textContent = '复制中...';
try {
await navigator.clipboard.writeText(text);
// 显示成功状态
btn.textContent = '已复制 ✓';
btn.classList.add('success');
setTimeout(() => {
btn.textContent = originalText;
btn.classList.remove('success');
}, 2000);
} catch (err) {
btn.textContent = '复制失败';
btn.classList.add('error');
setTimeout(() => {
btn.textContent = originalText;
btn.classList.remove('error');
}, 2000);
} finally {
btn.disabled = false;
}
}
东巴文点评:剪贴板API使用要注重权限处理和用户反馈,提供良好的用户体验。
问题1:以下哪个方法用于将文本写入剪贴板?
A. navigator.clipboard.write() B. navigator.clipboard.writeText() C. document.execCommand('copy') D. 以上都可以
<details> <summary>点击查看答案</summary>答案:B
东巴文解释:navigator.clipboard.writeText()用于将纯文本写入剪贴板。navigator.clipboard.write()用于写入多种格式(如文本、图片)。document.execCommand('copy')是旧方法,已不推荐使用。
问题2:读取剪贴板内容需要什么权限?
A. clipboard-write B. clipboard-read C. clipboard D. 不需要权限
<details> <summary>点击查看答案</summary>答案:B
东巴文解释:读取剪贴板需要clipboard-read权限,写入剪贴板需要clipboard-write权限。可以通过navigator.permissions.query()检查权限状态。
任务:创建一个代码片段管理器,支持保存、复制、删除代码片段。
<details> <summary>点击查看参考答案</summary><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>代码片段管理器 - 东巴文</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #1e1e1e;
color: #d4d4d4;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
padding: 30px 0;
}
.header h1 {
font-size: 36px;
color: #4ec9b0;
margin-bottom: 10px;
}
.header p {
color: #808080;
}
.panel {
background: #252526;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
border: 1px solid #3c3c3c;
}
.panel h2 {
color: #4ec9b0;
margin-bottom: 15px;
font-size: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #9cdcfe;
}
input[type="text"],
select {
width: 100%;
padding: 10px;
background: #1e1e1e;
border: 1px solid #3c3c3c;
border-radius: 5px;
color: #d4d4d4;
font-size: 14px;
}
textarea {
width: 100%;
min-height: 150px;
padding: 15px;
background: #1e1e1e;
border: 1px solid #3c3c3c;
border-radius: 5px;
color: #d4d4d4;
font-family: 'Courier New', monospace;
font-size: 14px;
resize: vertical;
}
textarea:focus {
outline: none;
border-color: #007acc;
}
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
padding: 10px 20px;
background: #0e639c;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
button:hover {
background: #1177bb;
}
button.success {
background: #4ec9b0;
}
button.success:hover {
background: #6fd9c3;
}
button.danger {
background: #f14c4c;
}
button.danger:hover {
background: #f36c6c;
}
.snippets-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 15px;
margin-top: 20px;
}
.snippet-card {
background: #1e1e1e;
border: 1px solid #3c3c3c;
border-radius: 5px;
padding: 15px;
transition: border-color 0.3s;
}
.snippet-card:hover {
border-color: #007acc;
}
.snippet-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.snippet-title {
font-weight: bold;
color: #4ec9b0;
}
.snippet-language {
padding: 3px 8px;
background: #3c3c3c;
border-radius: 3px;
font-size: 12px;
color: #ce9178;
}
.snippet-code {
background: #252526;
padding: 10px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 13px;
max-height: 150px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
.snippet-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #3c3c3c;
}
.snippet-time {
font-size: 12px;
color: #808080;
}
.snippet-actions {
display: flex;
gap: 5px;
}
.snippet-actions button {
padding: 5px 10px;
font-size: 12px;
}
.empty-state {
text-align: center;
padding: 40px;
color: #808080;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
background: #4ec9b0;
color: #1e1e1e;
border-radius: 5px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
transform: translateX(200%);
transition: transform 0.3s;
z-index: 1000;
}
.notification.show {
transform: translateX(0);
}
.search-box {
margin-bottom: 20px;
}
.search-box input {
width: 100%;
padding: 12px;
background: #1e1e1e;
border: 1px solid #3c3c3c;
border-radius: 5px;
color: #d4d4d4;
font-size: 14px;
}
.search-box input:focus {
outline: none;
border-color: #007acc;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📝 代码片段管理器</h1>
<p>保存、管理和快速复制代码片段</p>
</div>
<div class="panel">
<h2>添加新片段</h2>
<div class="form-group">
<label>片段名称</label>
<input type="text" id="snippetName" placeholder="例如:React组件模板">
</div>
<div class="form-group">
<label>编程语言</label>
<select id="snippetLanguage">
<option value="javascript">JavaScript</option>
<option value="html">HTML</option>
<option value="css">CSS</option>
<option value="python">Python</option>
<option value="java">Java</option>
<option value="other">其他</option>
</select>
</div>
<div class="form-group">
<label>代码内容</label>
<textarea id="snippetCode" placeholder="粘贴代码内容..."></textarea>
</div>
<div class="button-group">
<button class="success" onclick="saveSnippet()">保存片段</button>
<button onclick="clearForm()">清空表单</button>
</div>
</div>
<div class="panel">
<h2>我的片段</h2>
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索片段..." oninput="filterSnippets()">
</div>
<div class="snippets-grid" id="snippetsGrid">
<div class="empty-state">暂无保存的片段</div>
</div>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
// 代码片段管理器
class SnippetManager {
constructor() {
this.snippets = this.loadSnippets();
this.renderSnippets();
}
loadSnippets() {
const saved = localStorage.getItem('codeSnippets');
return saved ? JSON.parse(saved) : [];
}
saveSnippets() {
localStorage.setItem('codeSnippets', JSON.stringify(this.snippets));
}
addSnippet(name, language, code) {
const snippet = {
id: Date.now(),
name: name,
language: language,
code: code,
time: new Date().toISOString()
};
this.snippets.unshift(snippet);
this.saveSnippets();
this.renderSnippets();
return snippet;
}
deleteSnippet(id) {
this.snippets = this.snippets.filter(s => s.id !== id);
this.saveSnippets();
this.renderSnippets();
}
async copySnippet(id) {
const snippet = this.snippets.find(s => s.id === id);
if (snippet) {
try {
await navigator.clipboard.writeText(snippet.code);
return true;
} catch (err) {
console.error('复制失败:', err);
return false;
}
}
return false;
}
filterSnippets(keyword) {
if (!keyword) {
return this.snippets;
}
const lowerKeyword = keyword.toLowerCase();
return this.snippets.filter(s =>
s.name.toLowerCase().includes(lowerKeyword) ||
s.code.toLowerCase().includes(lowerKeyword) ||
s.language.toLowerCase().includes(lowerKeyword)
);
}
renderSnippets(filteredList = null) {
const grid = document.getElementById('snippetsGrid');
const snippets = filteredList || this.snippets;
if (snippets.length === 0) {
grid.innerHTML = '<div class="empty-state">暂无保存的片段</div>';
return;
}
grid.innerHTML = snippets.map(snippet => `
<div class="snippet-card">
<div class="snippet-header">
<div class="snippet-title">${this.escapeHtml(snippet.name)}</div>
<div class="snippet-language">${snippet.language}</div>
</div>
<div class="snippet-code">${this.escapeHtml(snippet.code)}</div>
<div class="snippet-footer">
<div class="snippet-time">${new Date(snippet.time).toLocaleString()}</div>
<div class="snippet-actions">
<button onclick="copySnippet(${snippet.id})">复制</button>
<button class="danger" onclick="deleteSnippet(${snippet.id})">删除</button>
</div>
</div>
</div>
`).join('');
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// 初始化管理器
const snippetManager = new SnippetManager();
// 保存片段
function saveSnippet() {
const name = document.getElementById('snippetName').value.trim();
const language = document.getElementById('snippetLanguage').value;
const code = document.getElementById('snippetCode').value.trim();
if (!name || !code) {
showNotification('请填写片段名称和代码内容');
return;
}
snippetManager.addSnippet(name, language, code);
clearForm();
showNotification('片段已保存');
}
// 清空表单
function clearForm() {
document.getElementById('snippetName').value = '';
document.getElementById('snippetLanguage').value = 'javascript';
document.getElementById('snippetCode').value = '';
}
// 复制片段
async function copySnippet(id) {
if (await snippetManager.copySnippet(id)) {
showNotification('代码已复制到剪贴板');
} else {
showNotification('复制失败,请检查权限');
}
}
// 删除片段
function deleteSnippet(id) {
if (confirm('确定要删除这个片段吗?')) {
snippetManager.deleteSnippet(id);
showNotification('片段已删除');
}
}
// 搜索片段
function filterSnippets() {
const keyword = document.getElementById('searchInput').value;
const filtered = snippetManager.filterSnippets(keyword);
snippetManager.renderSnippets(filtered);
}
// 显示通知
function showNotification(message) {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
</script>
</body>
</html>
</details>
东巴文(db-w.cn) - 让编程学习更有趣、更高效!