阿里云主机折上折
  • 微信号
Current Site:Index > Copy all text with one click: navigator.clipboard.writeText(document.body.innerText);

Copy all text with one click: navigator.clipboard.writeText(document.body.innerText);

Author:Chuan Chen 阅读数:55013人阅读 分类: JavaScript

Copy webpage body content to clipboard

The code navigator.clipboard.writeText(document.body.innerText) quickly copies the current webpage's body text to the clipboard. It combines the Clipboard API from the Web API with DOM operations to implement a common yet practical feature.

Clipboard API Basics

The Clipboard API provides asynchronous read/write capabilities for clipboard content. Compared to the deprecated document.execCommand('copy') method, it is more modern and secure. Main methods include:

  • writeText(): Writes plain text to the clipboard
  • readText(): Reads plain text from the clipboard
  • write(): Writes arbitrary data (e.g., images)
  • read(): Reads arbitrary data
// Basic usage example
async function copyToClipboard(text) {
  try {
    await navigator.clipboard.writeText(text);
    console.log('Content copied to clipboard');
  } catch (err) {
    console.error('Copy failed:', err);
  }
}

document.body.innerText Explained

document.body.innerText retrieves the visible text content of the <body> element and all its children, automatically ignoring HTML tags while preserving text formatting. The similar textContent property retains all whitespace characters, while innerHTML returns a string containing HTML tags.

// Comparing different properties
const body = document.body;
console.log(body.innerText);  // Plain text
console.log(body.textContent); // Includes text from hidden elements
console.log(body.innerHTML);  // Includes HTML tags

Practical Application Scenarios

  1. Browser Bookmark Tool: Create a bookmarklet (javascript:) to copy current page content with a click
javascript:navigator.clipboard.writeText(document.body.innerText);alert('Page content copied');
  1. Browser Extension: Add a "Copy Page Text" option to the right-click menu
// Chrome extension background.js
chrome.contextMenus.create({
  id: "copyPageText",
  title: "Copy Page Text",
  contexts: ["page"]
});

chrome.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === "copyPageText") {
    chrome.scripting.executeScript({
      target: {tabId: tab.id},
      function: () => {
        navigator.clipboard.writeText(document.body.innerText)
          .then(() => alert('Copy successful'))
          .catch(err => alert('Copy failed: ' + err));
      }
    });
  }
});
  1. Web Tool: Add a "Copy Full Text" button to article pages
<button id="copyFullText">Copy Full Text</button>

<script>
document.getElementById('copyFullText').addEventListener('click', async () => {
  try {
    await navigator.clipboard.writeText(document.body.innerText);
    alert('Full text copied');
  } catch (err) {
    alert(`Copy failed: ${err}`);
  }
});
</script>

Permissions and Security Restrictions

The Clipboard API is subject to strict browser security restrictions:

  1. HTTPS Requirement: May be unavailable in non-secure contexts (HTTP)
  2. User Gesture: Some browsers require triggering within user interactions (e.g., clicks)
  3. Permissions API: May require requesting clipboard-write permission
// Check clipboard permissions
navigator.permissions.query({name: 'clipboard-write'}).then(result => {
  if (result.state === 'granted' || result.state === 'prompt') {
    console.log('Can write to clipboard');
  } else {
    console.log('No clipboard write permission');
  }
});

Error Handling and Fallback Solutions

Due to browser compatibility issues, proper fallback handling is needed:

async function copyPageText() {
  const text = document.body.innerText;
  
  try {
    // Modern browser approach
    if (navigator.clipboard) {
      await navigator.clipboard.writeText(text);
      return;
    }
    
    // Legacy browser fallback
    const textarea = document.createElement('textarea');
    textarea.value = text;
    textarea.style.position = 'fixed';  // Prevent scrolling
    document.body.appendChild(textarea);
    textarea.select();
    
    try {
      const successful = document.execCommand('copy');
      if (!successful) throw new Error('Copy failed');
    } finally {
      document.body.removeChild(textarea);
    }
  } catch (err) {
    console.error('Copy failed:', err);
    // Can provide manual copy option
    prompt('Please manually copy the following', text);
  }
}

Performance Optimization Suggestions

Optimizations needed for large documents:

  1. Text Preprocessing: Remove excess whitespace and duplicate content
function getCleanText() {
  return document.body.innerText
    .replace(/\s+/g, ' ')  // Merge multiple spaces
    .trim();              // Remove leading/trailing spaces
}
  1. Chunked Copying: Process very large documents in chunks
async function copyLargeContent() {
  const chunks = splitText(document.body.innerText, 10000); // 10k characters per chunk
  
  for (const chunk of chunks) {
    await navigator.clipboard.writeText(chunk);
    // Add delay to avoid browser limits
    await new Promise(resolve => setTimeout(resolve, 100));
  }
}

function splitText(text, size) {
  const chunks = [];
  for (let i = 0; i < text.length; i += size) {
    chunks.push(text.substring(i, i + size));
  }
  return chunks;
}
  1. Selective Copying: Copy only specific areas instead of entire body
// Copy only article tag content
const articleText = document.querySelector('article').innerText;
navigator.clipboard.writeText(articleText);

User Experience Enhancements

  1. Visual Feedback: Show success prompt instead of using alert
<div id="copyFeedback" style="display:none; position:fixed; bottom:20px; right:20px; background:#4CAF50; color:white; padding:10px; border-radius:4px;">
  Text copied!
</div>

<script>
async function copyWithFeedback() {
  try {
    await navigator.clipboard.writeText(document.body.innerText);
    const feedback = document.getElementById('copyFeedback');
    feedback.style.display = 'block';
    setTimeout(() => feedback.style.display = 'none', 2000);
  } catch (err) {
    console.error('Copy failed:', err);
  }
}
</script>
  1. Copy Progress Display: Show progress for large documents
<progress id="copyProgress" value="0" max="100" style="width:100%"></progress>

<script>
async function copyWithProgress() {
  const text = document.body.innerText;
  const chunkSize = 10000;
  const totalChunks = Math.ceil(text.length / chunkSize);
  const progress = document.getElementById('copyProgress');
  progress.max = totalChunks;
  
  for (let i = 0; i < totalChunks; i++) {
    const chunk = text.substring(i * chunkSize, (i + 1) * chunkSize);
    await navigator.clipboard.writeText(chunk);
    progress.value = i + 1;
    await new Promise(resolve => setTimeout(resolve, 50));
  }
}
</script>

Advanced Application Examples

  1. Copy as Markdown: Convert HTML content to Markdown before copying
function htmlToMarkdown(html) {
  // Simple conversion example
  return html
    .replace(/<h[1-6]>(.*?)<\/h[1-6]>/g, '\n# $1\n')
    .replace(/<p>(.*?)<\/p>/g, '\n$1\n')
    .replace(/<a href="(.*?)">(.*?)<\/a>/g, '[$2]($1)')
    .replace(/<strong>(.*?)<\/strong>/g, '**$1**')
    .replace(/<em>(.*?)<\/em>/g, '*$1*')
    .replace(/<[^>]+>/g, '');
}

async function copyAsMarkdown() {
  const markdown = htmlToMarkdown(document.body.innerHTML);
  await navigator.clipboard.writeText(markdown);
}
  1. Copy with Source Information: Add source URL to copied text
async function copyWithSource() {
  const text = `${document.body.innerText}\n\nSource: ${window.location.href}`;
  await navigator.clipboard.writeText(text);
}
  1. Multilingual Support: Show different prompts based on user language
async function copyWithI18n() {
  try {
    await navigator.clipboard.writeText(document.body.innerText);
    const lang = navigator.language || 'en';
    const messages = {
      'zh-CN': '文本已复制',
      'en': 'Text copied',
      'ja': 'テキストをコピーしました'
    };
    alert(messages[lang] || messages['en']);
  } catch (err) {
    console.error('Copy failed:', err);
  }
}

Debugging and Testing

Testing clipboard functionality requires attention to:

  1. Console Testing: Run directly in console
// Test in browser console
document.body.innerText.length  // First check text length
navigator.clipboard.writeText('Test text').then(() => console.log('Success'))
  1. Unit Testing: Use testing frameworks like Jest
// Mock navigator.clipboard
beforeEach(() => {
  global.navigator.clipboard = {
    writeText: jest.fn().mockResolvedValue(undefined)
  };
});

test('Copy body text', async () => {
  document.body.innerHTML = '<div>Test content</div>';
  await copyPageText();
  expect(navigator.clipboard.writeText).toHaveBeenCalledWith('Test content');
});
  1. Error Scenario Testing: Simulate permission denials etc.
// Test error handling
test('Handle clipboard rejection', async () => {
  navigator.clipboard.writeText = jest.fn().mockRejectedValue(new Error('Permission denied'));
  console.error = jest.fn();
  
  await copyPageText();
  expect(console.error).toHaveBeenCalled();
});

Browser Compatibility Considerations

Different browsers implement the Clipboard API differently:

  1. Chrome: Requires HTTPS or localhost
  2. Firefox: Supported since version 63
  3. Safari: Partial support, may require user gesture
  4. Mobile Browsers: May have additional restrictions
// Compatibility check function
function canUseClipboard() {
  return !!navigator.clipboard && 
    (location.protocol === 'https:' || location.hostname === 'localhost' || location.protocol === 'file:');
}

if (!canUseClipboard()) {
  console.warn('Clipboard API may be unavailable in current environment');
  // Show fallback UI
}

Real Project Integration

In real projects, can be encapsulated as reusable modules:

// clipboard.js
export default {
  /**
   * Copy text to clipboard
   * @param {string} text - Text to copy
   * @param {object} options - Options
   * @param {boolean} options.fallback - Whether to enable fallback
   * @returns {Promise<void>}
   */
  async copy(text, { fallback = true } = {}) {
    try {
      if (navigator.clipboard) {
        return await navigator.clipboard.writeText(text);
      }
      
      if (!fallback) {
        throw new Error('Clipboard API not available');
      }
      
      // Fallback solution
      const textarea = document.createElement('textarea');
      textarea.value = text;
      textarea.style.position = 'fixed';
      document.body.appendChild(textarea);
      textarea.select();
      
      try {
        if (!document.execCommand('copy')) {
          throw new Error('execCommand failed');
        }
      } finally {
        document.body.removeChild(textarea);
      }
    } catch (err) {
      console.error('Copy failed:', err);
      throw err;
    }
  },

  /**
   * Copy page body content
   * @param {HTMLElement} [element=document.body] - Element to copy
   * @returns {Promise<void>}
   */
  async copyPageContent(element = document.body) {
    return this.copy(element.innerText);
  }
};

// Usage example
import clipboard from './clipboard.js';

document.getElementById('copyBtn').addEventListener('click', async () => {
  try {
    await clipboard.copyPageContent();
    showToast('Copy successful');
  } catch {
    showToast('Copy failed, please manually select text to copy');
  }
});

Related Technology Extensions

  1. Selection API: APIs related to text selection
// Get user-selected text
function getSelectedText() {
  return window.getSelection().toString();
}

// Copy selected text
document.getElementById('copySelected').addEventListener('click', () => {
  const selectedText = getSelectedText();
  if (selectedText) {
    navigator.clipboard.writeText(selectedText);
  }
});
  1. Drag and Drop API: Implement drag-and-drop copying
<div id="dragSource" draggable="true">Drag me to copy text</div>

<script>
document.getElementById('dragSource').addEventListener('dragstart', (e) => {
  e.dataTransfer.setData('text/plain', document.body.innerText);
  e.dataTransfer.effectAllowed = 'copy';
});
</script>
  1. SharedWorker: Share clipboard state across multiple tabs
// worker.js
const ports = new Set();

onconnect = (e) => {
  const port = e.ports[0];
  ports.add(port);
  
  port.onmessage = (e) => {
    if (e.data.type === 'clipboard-update') {
      // Broadcast to all tabs
      for (const p of ports) {
        p.postMessage({ type: 'clipboard-notify', text: e.data.text });
      }
    }
  };
};

// Main page
const worker = new SharedWorker('worker.js');

// Notify other tabs when copying
async function copyAndNotify(text) {
  await navigator.clipboard.writeText(text);
  worker.port.postMessage({ type: 'clipboard-update', text });
}

// Receive clipboard updates from other tabs
worker.port.onmessage = (e) => {
  if (e.data.type === 'clipboard-notify') {
    console.log('Other tab updated clipboard:', e.data.text);
  }
};

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.