剪贴板API

剪贴板API概述

剪贴板API(Clipboard API)是现代浏览器提供的接口,允许Web应用以异步方式读写剪贴板内容。相比传统的document.execCommand()方法,新的剪贴板API更强大、更安全、更灵活。

东巴文(db-w.cn) 认为:剪贴板API让Web应用能够更好地与系统剪贴板交互,提升用户体验,但需要特别注意权限和安全性。

剪贴板API特点

核心特点

特点 说明
异步操作 使用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);
    }
}

权限管理

权限API

// 查询剪贴板权限
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;
    }
}

东巴文点评:剪贴板读取权限需要用户明确授权,通常在用户点击或交互时请求更容易成功。

ClipboardEvent事件

复制事件

// 监听复制事件
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>

传统方法对比

execCommand方法(已废弃)

// 传统复制方法(不推荐)
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。但为了兼容性,可以提供降级方案。

最佳实践

1. 提供降级方案

// 推荐:提供降级方案
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;
    }
}

2. 用户交互触发

// 推荐:在用户交互时触发
document.getElementById('copyBtn').addEventListener('click', async () => {
    await navigator.clipboard.writeText('文本内容');
});

// 不推荐:自动复制
window.addEventListener('load', async () => {
    await navigator.clipboard.writeText('文本内容'); // 可能失败
});

3. 错误处理

// 推荐:完善的错误处理
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');
        }
    }
}

4. 安全考虑

// 推荐:避免复制敏感信息
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()

<details> <summary>点击查看答案</summary>

答案:B

东巴文解释navigator.clipboard.writeText()用于复制文本到剪贴板,返回Promise。writeText是Clipboard API的标准方法名。

</details>

问题2:剪贴板读取权限的状态不包括以下哪个?

A. granted B. denied C. prompt D. allowed

<details> <summary>点击查看答案</summary>

答案:D

东巴文解释:权限状态有三种:granted(已授权)、denied(已拒绝)、prompt(需要询问)。没有allowed这个状态。

</details>

实践任务

任务:创建一个简单的笔记应用,支持复制笔记内容、粘贴文本和图片,并显示操作历史。

<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="在这里输入笔记内容...&#10;&#10;支持粘贴文本和图片(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) - 让编程学习更有趣、更高效!