剪贴板API(Clipboard API)是现代浏览器提供的接口,允许Web应用以异步方式读写剪贴板内容。相比传统的document.execCommand()方法,新的剪贴板API更强大、更安全、更灵活。
东巴文(db-w.cn) 认为:剪贴板API让Web应用能够更好地与系统剪贴板交互,提升用户体验,但需要特别注意权限和安全性。
| 特点 | 说明 |
|---|---|
| 异步操作 | 使用Promise,不阻塞主线程 |
| 权限管理 | 需要用户授权,更安全 |
| 多种格式 | 支持文本、图片、HTML等 |
| 现代API | 替代过时的execCommand方法 |
// 检查剪贴板API支持
if (navigator.clipboard) {
console.log('支持剪贴板API');
} else {
console.log('不支持剪贴板API,使用降级方案');
}
// 检查特定方法
if (navigator.clipboard.write) {
console.log('支持写入多种格式');
}
if (navigator.clipboard.read) {
console.log('支持读取剪贴板');
}
// 方法一:writeText(简单文本)
async function copyText(text) {
try {
await navigator.clipboard.writeText(text);
console.log('文本已复制到剪贴板');
} catch (err) {
console.error('复制失败:', err);
}
}
// 使用示例
copyText('Hello, 东巴文!');
// 方法二:write(多种格式)
async function copyMultipleFormats() {
try {
const textBlob = new Blob(['纯文本内容'], { type: 'text/plain' });
const htmlBlob = new Blob(['<b>富文本内容</b>'], { type: 'text/html' });
const data = [
new ClipboardItem({
'text/plain': textBlob,
'text/html': htmlBlob
})
];
await navigator.clipboard.write(data);
console.log('多格式内容已复制');
} catch (err) {
console.error('复制失败:', err);
}
}
// 复制图片到剪贴板
async function copyImage(imageUrl) {
try {
// 获取图片
const response = await fetch(imageUrl);
const blob = await response.blob();
// 写入剪贴板
const data = [new ClipboardItem({ [blob.type]: blob })];
await navigator.clipboard.write(data);
console.log('图片已复制到剪贴板');
} catch (err) {
console.error('复制图片失败:', err);
}
}
// 复制Canvas内容
async function copyCanvas(canvas) {
try {
const blob = await new Promise(resolve => {
canvas.toBlob(resolve, 'image/png');
});
const data = [new ClipboardItem({ 'image/png': blob })];
await navigator.clipboard.write(data);
console.log('Canvas内容已复制');
} catch (err) {
console.error('复制Canvas失败:', err);
}
}
// 读取文本
async function pasteText() {
try {
const text = await navigator.clipboard.readText();
console.log('剪贴板文本:', text);
return text;
} catch (err) {
console.error('读取失败:', err);
return '';
}
}
// 使用示例
document.getElementById('pasteBtn').addEventListener('click', async () => {
const text = await pasteText();
document.getElementById('output').value = text;
});
// 读取剪贴板内容
async function readClipboard() {
try {
const clipboardItems = await navigator.clipboard.read();
for (const item of clipboardItems) {
console.log('可用类型:', item.types);
// 读取文本
if (item.types.includes('text/plain')) {
const blob = await item.getType('text/plain');
const text = await blob.text();
console.log('文本内容:', text);
}
// 读取HTML
if (item.types.includes('text/html')) {
const blob = await item.getType('text/html');
const html = await blob.text();
console.log('HTML内容:', html);
}
// 读取图片
if (item.types.includes('image/png')) {
const blob = await item.getType('image/png');
const url = URL.createObjectURL(blob);
console.log('图片URL:', url);
}
}
} catch (err) {
console.error('读取剪贴板失败:', err);
}
}
// 查询剪贴板权限
async function checkClipboardPermission(type = 'read') {
try {
const result = await navigator.permissions.query({
name: `clipboard-${type}`
});
console.log(`${type}权限状态:`, result.state);
// state: 'granted' | 'denied' | 'prompt'
return result.state;
} catch (err) {
console.error('查询权限失败:', err);
return 'unknown';
}
}
// 使用示例
checkClipboardPermission('read');
checkClipboardPermission('write');
// 请求剪贴板读取权限
async function requestClipboardRead() {
try {
const permission = await navigator.permissions.query({
name: 'clipboard-read'
});
if (permission.state === 'granted') {
return true;
}
// 尝试读取,会触发权限请求
await navigator.clipboard.readText();
return true;
} catch (err) {
console.error('权限被拒绝:', err);
return false;
}
}
东巴文点评:剪贴板读取权限需要用户明确授权,通常在用户点击或交互时请求更容易成功。
// 监听复制事件
document.addEventListener('copy', function(e) {
// 阻止默认行为
e.preventDefault();
// 自定义复制内容
const selection = document.getSelection();
const selectedText = selection.toString();
// 设置剪贴板数据
e.clipboardData.setData('text/plain', selectedText);
e.clipboardData.setData('text/html', `<b>${selectedText}</b>`);
console.log('自定义复制完成');
});
// 监听剪切事件
document.addEventListener('cut', function(e) {
e.preventDefault();
const selection = document.getSelection();
const selectedText = selection.toString();
// 复制到剪贴板
e.clipboardData.setData('text/plain', selectedText);
// 删除选中内容
if (e.target.isContentEditable || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
document.execCommand('delete');
}
console.log('剪切完成');
});
// 监听粘贴事件
document.addEventListener('paste', function(e) {
// 阻止默认粘贴
e.preventDefault();
// 获取剪贴板数据
const items = e.clipboardData.items;
for (const item of items) {
// 处理文本
if (item.type === 'text/plain') {
item.getAsString(text => {
console.log('粘贴文本:', text);
// 插入文本
document.execCommand('insertText', false, text);
});
}
// 处理图片
if (item.type.startsWith('image/')) {
const file = item.getAsFile();
const url = URL.createObjectURL(file);
console.log('粘贴图片:', url);
// 插入图片
const img = document.createElement('img');
img.src = url;
document.execCommand('insertHTML', false, img.outerHTML);
}
}
});
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>剪贴板API综合示例 - 东巴文</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
h1 {
text-align: center;
color: #333;
}
.section {
margin: 20px 0;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.section h2 {
margin-top: 0;
color: #667eea;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
/* 文本区域样式 */
textarea {
width: 100%;
min-height: 100px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
font-family: monospace;
resize: vertical;
}
/* 按钮样式 */
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
margin: 5px;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.btn-success {
background: #28a745;
color: white;
}
.btn-success:hover {
background: #218838;
}
.btn-warning {
background: #ffc107;
color: #333;
}
.btn-warning:hover {
background: #e0a800;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-danger:hover {
background: #c82333;
}
/* 按钮组 */
.btn-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 10px 0;
}
/* 状态提示 */
.status {
padding: 10px;
margin: 10px 0;
border-radius: 5px;
display: none;
}
.status.success {
background: #d4edda;
color: #155724;
display: block;
}
.status.error {
background: #f8d7da;
color: #721c24;
display: block;
}
/* 图片预览 */
.image-preview {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 10px 0;
}
.image-preview img {
max-width: 200px;
max-height: 200px;
border-radius: 5px;
border: 2px solid #ddd;
}
/* Canvas */
canvas {
border: 1px solid #ddd;
border-radius: 5px;
display: block;
margin: 10px auto;
background: white;
}
/* 可编辑区域 */
.editable {
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
min-height: 100px;
background: white;
}
.editable:focus {
outline: none;
border-color: #667eea;
}
/* 权限状态 */
.permission-status {
display: flex;
gap: 20px;
margin: 10px 0;
}
.permission-item {
flex: 1;
padding: 15px;
background: #f9f9f9;
border-radius: 5px;
text-align: center;
}
.permission-item.granted {
background: #d4edda;
}
.permission-item.denied {
background: #f8d7da;
}
/* 代码块 */
pre {
background: #f9f9f9;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
border-left: 3px solid #667eea;
}
code {
font-family: 'Courier New', monospace;
background: #f0f0f0;
padding: 2px 5px;
border-radius: 3px;
}
pre code {
background: none;
padding: 0;
}
</style>
</head>
<body>
<h1>剪贴板API综合示例</h1>
<!-- 权限状态 -->
<div class="section">
<h2>权限状态</h2>
<div class="permission-status">
<div class="permission-item" id="readPermission">
<strong>读取权限</strong>
<p id="readStatus">检查中...</p>
</div>
<div class="permission-item" id="writePermission">
<strong>写入权限</strong>
<p id="writeStatus">检查中...</p>
</div>
</div>
<button class="btn btn-primary" onclick="checkPermissions()">
刷新权限状态
</button>
</div>
<!-- 文本操作 -->
<div class="section">
<h2>文本操作</h2>
<div class="status" id="textStatus"></div>
<h3>复制文本</h3>
<textarea id="copyText" placeholder="输入要复制的文本...">欢迎访问东巴文(db-w.cn)!</textarea>
<div class="btn-group">
<button class="btn btn-primary" onclick="copyTextToClipboard()">
复制文本
</button>
<button class="btn btn-success" onclick="copyRichText()">
复制富文本
</button>
</div>
<h3>粘贴文本</h3>
<textarea id="pasteText" placeholder="点击按钮粘贴文本..."></textarea>
<div class="btn-group">
<button class="btn btn-primary" onclick="pasteTextFromClipboard()">
粘贴文本
</button>
<button class="btn btn-warning" onclick="clearPasteText()">
清空
</button>
</div>
</div>
<!-- 图片操作 -->
<div class="section">
<h2>图片操作</h2>
<div class="status" id="imageStatus"></div>
<h3>复制Canvas内容</h3>
<canvas id="drawCanvas" width="300" height="200"></canvas>
<div class="btn-group">
<button class="btn btn-primary" onclick="drawOnCanvas()">
绘制图案
</button>
<button class="btn btn-success" onclick="copyCanvasToClipboard()">
复制Canvas
</button>
</div>
<h3>粘贴图片</h3>
<p>使用 Ctrl+V 或点击按钮粘贴剪贴板中的图片</p>
<div class="image-preview" id="imagePreview"></div>
<button class="btn btn-primary" onclick="pasteImageFromClipboard()">
粘贴图片
</button>
</div>
<!-- 拖放和粘贴区域 -->
<div class="section">
<h2>可编辑区域(支持粘贴)</h2>
<div class="editable" contenteditable="true" id="editableArea">
<p>这是一个可编辑区域,支持:</p>
<ul>
<li>粘贴文本(Ctrl+V)</li>
<li>粘贴图片(Ctrl+V)</li>
<li>粘贴HTML内容</li>
</ul>
</div>
<div class="btn-group">
<button class="btn btn-primary" onclick="clearEditableArea()">
清空内容
</button>
<button class="btn btn-success" onclick="copyEditableContent()">
复制全部内容
</button>
</div>
</div>
<!-- 剪贴板历史 -->
<div class="section">
<h2>操作历史</h2>
<div id="historyLog" style="max-height: 200px; overflow-y: auto; background: #f9f9f9; padding: 10px; border-radius: 5px;">
<p style="color: #999;">暂无操作记录</p>
</div>
<button class="btn btn-danger" onclick="clearHistory()">
清空历史
</button>
</div>
<script>
// 历史记录
const historyLog = [];
// 添加历史记录
function addHistory(action, detail) {
const time = new Date().toLocaleTimeString();
historyLog.unshift({ time, action, detail });
updateHistoryDisplay();
}
// 更新历史显示
function updateHistoryDisplay() {
const historyEl = document.getElementById('historyLog');
if (historyLog.length === 0) {
historyEl.innerHTML = '<p style="color: #999;">暂无操作记录</p>';
return;
}
historyEl.innerHTML = historyLog.map(log => `
<div style="padding: 5px 0; border-bottom: 1px solid #eee;">
<strong>[${log.time}]</strong> ${log.action}
${log.detail ? `<br><small style="color: #666;">${log.detail}</small>` : ''}
</div>
`).join('');
}
// 清空历史
function clearHistory() {
historyLog.length = 0;
updateHistoryDisplay();
}
// 显示状态
function showStatus(elementId, message, isSuccess) {
const statusEl = document.getElementById(elementId);
statusEl.textContent = message;
statusEl.className = 'status ' + (isSuccess ? 'success' : 'error');
setTimeout(() => {
statusEl.className = 'status';
}, 3000);
}
// 检查权限
async function checkPermissions() {
// 检查读取权限
const readEl = document.getElementById('readPermission');
const readStatusEl = document.getElementById('readStatus');
try {
const readPermission = await navigator.permissions.query({
name: 'clipboard-read'
});
readStatusEl.textContent = readPermission.state;
readEl.className = 'permission-item ' + readPermission.state;
readPermission.addEventListener('change', () => {
readStatusEl.textContent = readPermission.state;
readEl.className = 'permission-item ' + readPermission.state;
});
} catch (err) {
readStatusEl.textContent = '不支持';
readEl.className = 'permission-item denied';
}
// 检查写入权限
const writeEl = document.getElementById('writePermission');
const writeStatusEl = document.getElementById('writeStatus');
try {
const writePermission = await navigator.permissions.query({
name: 'clipboard-write'
});
writeStatusEl.textContent = writePermission.state;
writeEl.className = 'permission-item ' + writePermission.state;
writePermission.addEventListener('change', () => {
writeStatusEl.textContent = writePermission.state;
writeEl.className = 'permission-item ' + writePermission.state;
});
} catch (err) {
writeStatusEl.textContent = '不支持';
writeEl.className = 'permission-item denied';
}
addHistory('检查权限', '读取和写入权限状态已更新');
}
// 复制文本到剪贴板
async function copyTextToClipboard() {
const text = document.getElementById('copyText').value;
if (!text) {
showStatus('textStatus', '请输入要复制的文本', false);
return;
}
try {
await navigator.clipboard.writeText(text);
showStatus('textStatus', '文本已复制到剪贴板!', true);
addHistory('复制文本', `长度: ${text.length} 字符`);
} catch (err) {
showStatus('textStatus', '复制失败: ' + err.message, false);
addHistory('复制失败', err.message);
}
}
// 复制富文本
async function copyRichText() {
const text = document.getElementById('copyText').value;
try {
const textBlob = new Blob([text], { type: 'text/plain' });
const htmlBlob = new Blob([`<b style="color: #667eea;">${text}</b>`], { type: 'text/html' });
const data = [new ClipboardItem({
'text/plain': textBlob,
'text/html': htmlBlob
})];
await navigator.clipboard.write(data);
showStatus('textStatus', '富文本已复制到剪贴板!', true);
addHistory('复制富文本', `内容: ${text}`);
} catch (err) {
showStatus('textStatus', '复制失败: ' + err.message, false);
addHistory('复制失败', err.message);
}
}
// 从剪贴板粘贴文本
async function pasteTextFromClipboard() {
try {
const text = await navigator.clipboard.readText();
document.getElementById('pasteText').value = text;
showStatus('textStatus', '文本已粘贴!', true);
addHistory('粘贴文本', `长度: ${text.length} 字符`);
} catch (err) {
showStatus('textStatus', '粘贴失败: ' + err.message, false);
addHistory('粘贴失败', err.message);
}
}
// 清空粘贴文本
function clearPasteText() {
document.getElementById('pasteText').value = '';
addHistory('清空', '粘贴文本框已清空');
}
// 在Canvas上绘制
function drawOnCanvas() {
const canvas = document.getElementById('drawCanvas');
const ctx = canvas.getContext('2d');
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制背景
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#667eea');
gradient.addColorStop(1, '#764ba2');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制文字
ctx.fillStyle = 'white';
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('东巴文 db-w.cn', canvas.width / 2, canvas.height / 2);
// 绘制装饰
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = 2;
ctx.strokeRect(10, 10, canvas.width - 20, canvas.height - 20);
addHistory('绘制Canvas', '图案已绘制');
}
// 复制Canvas到剪贴板
async function copyCanvasToClipboard() {
const canvas = document.getElementById('drawCanvas');
try {
const blob = await new Promise(resolve => {
canvas.toBlob(resolve, 'image/png');
});
const data = [new ClipboardItem({ 'image/png': blob })];
await navigator.clipboard.write(data);
showStatus('imageStatus', 'Canvas内容已复制到剪贴板!', true);
addHistory('复制Canvas', '图片已复制到剪贴板');
} catch (err) {
showStatus('imageStatus', '复制失败: ' + err.message, false);
addHistory('复制失败', err.message);
}
}
// 从剪贴板粘贴图片
async function pasteImageFromClipboard() {
try {
const clipboardItems = await navigator.clipboard.read();
const previewEl = document.getElementById('imagePreview');
for (const item of clipboardItems) {
for (const type of item.types) {
if (type.startsWith('image/')) {
const blob = await item.getType(type);
const url = URL.createObjectURL(blob);
const img = document.createElement('img');
img.src = url;
previewEl.appendChild(img);
showStatus('imageStatus', '图片已粘贴!', true);
addHistory('粘贴图片', `类型: ${type}, 大小: ${blob.size} bytes`);
}
}
}
} catch (err) {
showStatus('imageStatus', '粘贴失败: ' + err.message, false);
addHistory('粘贴失败', err.message);
}
}
// 清空可编辑区域
function clearEditableArea() {
document.getElementById('editableArea').innerHTML = '<p>可编辑区域已清空</p>';
addHistory('清空', '可编辑区域已清空');
}
// 复制可编辑内容
async function copyEditableContent() {
const content = document.getElementById('editableArea').innerHTML;
try {
const textContent = document.getElementById('editableArea').innerText;
const textBlob = new Blob([textContent], { type: 'text/plain' });
const htmlBlob = new Blob([content], { type: 'text/html' });
const data = [new ClipboardItem({
'text/plain': textBlob,
'text/html': htmlBlob
})];
await navigator.clipboard.write(data);
addHistory('复制内容', '可编辑区域内容已复制');
alert('内容已复制!');
} catch (err) {
addHistory('复制失败', err.message);
alert('复制失败: ' + err.message);
}
}
// 监听粘贴事件
document.getElementById('editableArea').addEventListener('paste', function(e) {
const items = e.clipboardData.items;
for (const item of items) {
// 处理图片
if (item.type.startsWith('image/')) {
e.preventDefault();
const file = item.getAsFile();
const url = URL.createObjectURL(file);
const img = document.createElement('img');
img.src = url;
img.style.maxWidth = '100%';
this.appendChild(img);
addHistory('粘贴图片', '通过Ctrl+V粘贴');
}
}
});
// 全局粘贴事件
document.addEventListener('paste', function(e) {
addHistory('粘贴事件', '检测到粘贴操作');
});
// 全局复制事件
document.addEventListener('copy', function(e) {
addHistory('复制事件', '检测到复制操作');
});
// 全局剪切事件
document.addEventListener('cut', function(e) {
addHistory('剪切事件', '检测到剪切操作');
});
// 初始化
window.addEventListener('DOMContentLoaded', function() {
// 检查权限
checkPermissions();
// 绘制初始Canvas
drawOnCanvas();
addHistory('初始化', '页面加载完成');
});
</script>
</body>
</html>
// 传统复制方法(不推荐)
function oldCopyText() {
const input = document.createElement('input');
input.value = '要复制的文本';
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
}
// 传统粘贴方法(不推荐)
function oldPasteText() {
const input = document.getElementById('input');
input.focus();
document.execCommand('paste'); // 大多数浏览器已禁用
}
| 特性 | Clipboard API | execCommand |
|---|---|---|
| 异步 | ✅ Promise | ❌ 同步阻塞 |
| 权限 | ✅ 权限管理 | ❌ 无权限控制 |
| 图片支持 | ✅ 原生支持 | ❌ 需要变通 |
| 安全性 | ✅ 更安全 | ❌ 安全问题 |
| 状态 | ✅ 现代标准 | ❌ 已废弃 |
东巴文点评:execCommand已被废弃,应该使用新的剪贴板API。但为了兼容性,可以提供降级方案。
// 推荐:提供降级方案
async function copyText(text) {
// 优先使用Clipboard API
if (navigator.clipboard) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
console.error('Clipboard API失败:', err);
}
}
// 降级到execCommand
try {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
return true;
} catch (err) {
console.error('降级方法失败:', err);
return false;
}
}
// 推荐:在用户交互时触发
document.getElementById('copyBtn').addEventListener('click', async () => {
await navigator.clipboard.writeText('文本内容');
});
// 不推荐:自动复制
window.addEventListener('load', async () => {
await navigator.clipboard.writeText('文本内容'); // 可能失败
});
// 推荐:完善的错误处理
async function safeCopy(text) {
try {
await navigator.clipboard.writeText(text);
showMessage('复制成功!', 'success');
} catch (err) {
if (err.name === 'NotAllowedError') {
showMessage('权限被拒绝,请允许访问剪贴板', 'error');
} else if (err.name === 'NotFoundError') {
showMessage('剪贴板不可用', 'error');
} else {
showMessage('复制失败: ' + err.message, 'error');
}
}
}
// 推荐:避免复制敏感信息
async function copyUserInfo(user) {
// 不要复制密码等敏感信息
const safeInfo = {
name: user.name,
email: user.email
// 不包含password
};
await navigator.clipboard.writeText(JSON.stringify(safeInfo));
}
问题1:以下哪个方法用于复制文本到剪贴板?
A. navigator.clipboard.copyText()
B. navigator.clipboard.writeText()
C. navigator.clipboard.setText()
D. navigator.clipboard.saveText()
答案:B
东巴文解释:navigator.clipboard.writeText()用于复制文本到剪贴板,返回Promise。writeText是Clipboard API的标准方法名。
问题2:剪贴板读取权限的状态不包括以下哪个?
A. granted B. denied C. prompt D. allowed
<details> <summary>点击查看答案</summary>答案:D
东巴文解释:权限状态有三种:granted(已授权)、denied(已拒绝)、prompt(需要询问)。没有allowed这个状态。
任务:创建一个简单的笔记应用,支持复制笔记内容、粘贴文本和图片,并显示操作历史。
<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>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
h1 {
text-align: center;
color: #333;
}
.note-container {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.note-input {
width: 100%;
min-height: 200px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
resize: vertical;
font-family: inherit;
}
.note-input:focus {
outline: none;
border-color: #667eea;
}
.btn-group {
display: flex;
gap: 10px;
margin-top: 15px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.btn-success {
background: #28a745;
color: white;
}
.btn-success:hover {
background: #218838;
}
.history {
margin-top: 20px;
padding: 15px;
background: #f9f9f9;
border-radius: 5px;
max-height: 200px;
overflow-y: auto;
}
.history h3 {
margin-top: 0;
}
.history-item {
padding: 8px 0;
border-bottom: 1px solid #eee;
font-size: 14px;
}
.history-item:last-child {
border-bottom: none;
}
.toast {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
background: #333;
color: white;
border-radius: 5px;
opacity: 0;
transition: opacity 0.3s;
z-index: 1000;
}
.toast.show {
opacity: 1;
}
.toast.success {
background: #28a745;
}
.toast.error {
background: #dc3545;
}
</style>
</head>
<body>
<h1>简单笔记应用</h1>
<div class="note-container">
<textarea class="note-input" id="noteInput" placeholder="在这里输入笔记内容... 支持粘贴文本和图片(Ctrl+V)"></textarea>
<div class="btn-group">
<button class="btn btn-primary" onclick="copyNote()">复制笔记</button>
<button class="btn btn-success" onclick="pasteToNote()">粘贴</button>
<button class="btn" style="background: #dc3545; color: white;" onclick="clearNote()">清空</button>
</div>
<div class="history">
<h3>操作历史</h3>
<div id="historyList"></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
const history = [];
// 显示提示
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = 'toast show ' + type;
setTimeout(() => {
toast.className = 'toast';
}, 2000);
}
// 添加历史
function addHistory(action) {
const time = new Date().toLocaleTimeString();
history.unshift({ time, action });
updateHistory();
}
// 更新历史显示
function updateHistory() {
const historyList = document.getElementById('historyList');
historyList.innerHTML = history.map(item => `
<div class="history-item">
<strong>[${item.time}]</strong> ${item.action}
</div>
`).join('');
}
// 复制笔记
async function copyNote() {
const note = document.getElementById('noteInput').value;
if (!note) {
showToast('笔记为空,无法复制', 'error');
return;
}
try {
await navigator.clipboard.writeText(note);
showToast('笔记已复制到剪贴板', 'success');
addHistory('复制笔记');
} catch (err) {
showToast('复制失败: ' + err.message, 'error');
}
}
// 粘贴到笔记
async function pasteToNote() {
try {
const text = await navigator.clipboard.readText();
const noteInput = document.getElementById('noteInput');
const start = noteInput.selectionStart;
const end = noteInput.selectionEnd;
noteInput.value = noteInput.value.substring(0, start) +
text +
noteInput.value.substring(end);
noteInput.selectionStart = noteInput.selectionEnd = start + text.length;
noteInput.focus();
showToast('文本已粘贴', 'success');
addHistory('粘贴文本');
} catch (err) {
showToast('粘贴失败: ' + err.message, 'error');
}
}
// 清空笔记
function clearNote() {
if (confirm('确定要清空笔记吗?')) {
document.getElementById('noteInput').value = '';
showToast('笔记已清空', 'success');
addHistory('清空笔记');
}
}
// 监听粘贴事件
document.getElementById('noteInput').addEventListener('paste', function(e) {
const items = e.clipboardData.items;
for (const item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault();
const file = item.getAsFile();
const reader = new FileReader();
reader.onload = function(e) {
const imgText = `[图片: ${file.name}]\n`;
this.value += imgText;
showToast('图片信息已添加', 'success');
addHistory('粘贴图片');
}.bind(this);
reader.readAsDataURL(file);
}
}
});
// 初始化
addHistory('应用启动');
</script>
</body>
</html>
</details>
东巴文(db-w.cn) - 让编程学习更有趣、更高效!