Markdownにコピーボタンをつけるには?コードを簡単にコピーできる方法

Web

ブログやドキュメントをMarkdown(マークダウン)で書いているとき、
「コードスニペットをそのままコピーできたら便利なのに」
「読者がワンクリックでコードをコピーできるようにしたい」
と思ったことはありませんか?

Markdown自体にはコピーボタンをつける機能はありませんが、表示プラットフォームや追加ツールを使えば簡単に実現できます。

この記事では、Markdownにコピーボタンをつける方法を用途別にわかりやすく解説します。

スポンサーリンク

Markdownとコピーボタンの基本知識

Markdownの制限事項

まず知っておいてほしいのは、Markdownはあくまで文章をマークアップ(構造化)するシンプルな記法なので、コピーボタンのようなJavaScriptによる動きのあるUIは標準ではつきません。

Markdownの基本的なコードブロック記法

```javascript
function hello() {
    console.log("Hello, World!");
}

この記法だけでは、シンプルなコードブロックが表示されるだけで、コピー機能は付きません。

### コピーボタンが実現される仕組み

コピーボタンは以下の要素で実現されます:

1. **HTML構造**:ボタン要素とコードブロック
2. **CSS**:ボタンのデザインと配置
3. **JavaScript**:クリップボードへのコピー機能
4. **表示プラットフォーム**:これらを統合して提供

## プラットフォーム別のコピーボタン対応

### GitHub

#### 標準機能
GitHubのREADMEやWikiでは、通常のMarkdownコードブロックに自動的にコピーボタンは付きません。

#### Gist(推奨)
GitHubのGistを使用すると、右上に自動的にコピーボタンが表示されます。

**使用方法**:
1. [gist.github.com](https://gist.github.com) にアクセス
2. コードを入力
3. 公開設定を選択
4. 「Create public gist」をクリック

**Gistの埋め込み方法**:
```markdown
<script src="https://gist.github.com/username/gist-id.js"></script>

GitHub Pages

Jekyll(GitHub Pagesの標準)でコピーボタンを追加する方法:

  1. _includes/copy-button.htmlを作成:
<button class="copy-button" onclick="copyToClipboard('{{ include.target }}')">
  コピー
</button>
  1. JavaScriptを追加:
function copyToClipboard(elementId) {
    const element = document.getElementById(elementId);
    const text = element.textContent;
    navigator.clipboard.writeText(text);
}

GitLab

自動コピーボタン

GitLabでは、コードブロック(“`)に対して自動的にコピーボタンが表示されます。

特徴

  • 設定不要で自動的に表示
  • 言語指定に関係なく表示
  • モバイルでも動作

GitLab Pages

GitLab Pagesでも同様の機能が利用できます。

Bitbucket

標準機能

Bitbucketも GitLab と同様に、コードブロックの右上に自動的にコピーボタンが表示されます。

その他のプラットフォーム

Notion

  • コードブロックに自動的にコピーボタンが付く
  • 言語指定をするとシンタックスハイライトも適用

Discord

  • コードブロックの右上にコピーボタンが自動表示
  • ただし、Markdownファイルを直接アップロードする場合は対象外

静的サイトジェネレーター別の実装方法

Hugo

Prism.jsを使用した方法

1. 設定ファイル(config.yaml)

markup:
  highlight:
    noClasses: false
    lineNos: true

2. テーマにPrism.jsを追加

<!-- head.html -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/plugins/toolbar/prism-toolbar.min.css" rel="stylesheet" />

3. JavaScriptファイルを追加

<!-- footer.html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/plugins/autoloader/prism-autoloader.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/plugins/toolbar/prism-toolbar.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script>

カスタムコピーボタンの実装

1. ショートコードを作成(layouts/shortcodes/copycode.html)

<div class="code-container">
  <pre><code class="language-{{ .Get 0 }}">{{ .Inner }}</code></pre>
  <button class="copy-btn" onclick="copyCode(this)">コピー</button>
</div>

2. 使用方法

{{< copycode "javascript" >}}
function hello() {
    console.log("Hello, World!");
}
{{< /copycode >}}

Jekyll

jekyll-prism プラグインを使用

1. Gemfileに追加

gem 'jekyll-prism'

2. _config.ymlに設定追加

plugins:
  - jekyll-prism

prism:
  plugins:
    - copy-to-clipboard
    - toolbar

手動実装

1. _includes/copy-button.htmlを作成

<div class="code-block-wrapper">
  {{ content }}
  <button class="copy-button" data-clipboard-target="#code-{{ include.id }}">
    <i class="fas fa-copy"></i> コピー
  </button>
</div>

2. clipboard.jsを導入

<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.8/clipboard.min.js"></script>
<script>
  new ClipboardJS('.copy-button');
</script>

Docusaurus

標準機能

Docusaurusでは、デフォルトでコピーボタンが付きます。

docusaurus.config.js

module.exports = {
  themeConfig: {
    prism: {
      additionalLanguages: ['php', 'ruby', 'java'],
    },
  },
};

カスタマイズ方法

src/css/custom.css

.theme-code-block .copy-button {
  background-color: var(--ifm-color-primary);
  border: none;
  border-radius: 4px;
  color: white;
  padding: 4px 8px;
  font-size: 12px;
}

Next.js

react-syntax-highlighterとの組み合わせ

1. 必要なパッケージをインストール

npm install react-syntax-highlighter react-copy-to-clipboard

2. コンポーネント作成

import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { useState } from 'react';

const CodeBlock = ({ language, children }) => {
  const [copied, setCopied] = useState(false);

  const handleCopy = () => {
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <div className="code-block-container">
      <div className="code-header">
        <span>{language}</span>
        <CopyToClipboard text={children} onCopy={handleCopy}>
          <button className="copy-button">
            {copied ? 'コピー済み!' : 'コピー'}
          </button>
        </CopyToClipboard>
      </div>
      <SyntaxHighlighter language={language}>
        {children}
      </SyntaxHighlighter>
    </div>
  );
};

エディタとプレビューツール

Visual Studio Code

Markdown Preview Enhanced

インストール方法

  1. VS Codeの拡張機能を開く
  2. “Markdown Preview Enhanced”を検索
  3. インストール

機能

  • コードブロック右上に自動的にコピーボタンが表示
  • 様々なプログラミング言語に対応
  • リアルタイムプレビュー

設定カスタマイズ

settings.json

{
  "markdown-preview-enhanced.codeBlockTheme": "github.css",
  "markdown-preview-enhanced.previewTheme": "github-light.css"
}

Typora

標準機能

Typoraでは、プレビューモードでコードブロックにカーソルを合わせるとコピーボタンが表示されます。

Mark Text

特徴

  • リアルタイム編集モード
  • コードブロックのコピーボタン
  • 軽量で高速

自作実装の方法

HTMLとJavaScriptを直接使用

基本的な実装

<div class="code-container">
  <pre><code id="code-example">
npm install express
const app = require('express')();
app.listen(3000);
  </code></pre>
  <button onclick="copyToClipboard('code-example')" class="copy-btn">
    コピー
  </button>
</div>

<script>
function copyToClipboard(elementId) {
  const element = document.getElementById(elementId);
  const text = element.textContent;
  
  navigator.clipboard.writeText(text).then(() => {
    // コピー成功の通知
    const button = event.target;
    const originalText = button.textContent;
    button.textContent = 'コピー完了!';
    setTimeout(() => {
      button.textContent = originalText;
    }, 2000);
  });
}
</script>

<style>
.code-container {
  position: relative;
  background-color: #f6f8fa;
  border-radius: 6px;
  padding: 16px;
  margin: 16px 0;
}

.copy-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  background: #0969da;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 12px;
  cursor: pointer;
}

.copy-btn:hover {
  background: #0860ca;
}
</style>

より高度な実装

class CodeCopyButton {
  constructor() {
    this.init();
  }

  init() {
    // すべてのコードブロックを検索
    const codeBlocks = document.querySelectorAll('pre code');
    
    codeBlocks.forEach((block, index) => {
      this.addCopyButton(block, index);
    });
  }

  addCopyButton(codeBlock, index) {
    const container = codeBlock.parentElement;
    container.style.position = 'relative';
    
    const button = document.createElement('button');
    button.className = 'copy-code-button';
    button.textContent = 'コピー';
    button.dataset.code = codeBlock.textContent;
    
    button.addEventListener('click', (e) => {
      this.copyCode(e.target);
    });
    
    container.appendChild(button);
  }

  async copyCode(button) {
    try {
      await navigator.clipboard.writeText(button.dataset.code);
      this.showSuccess(button);
    } catch (err) {
      console.error('コピーに失敗しました:', err);
      this.showError(button);
    }
  }

  showSuccess(button) {
    const originalText = button.textContent;
    button.textContent = '✓ コピー完了';
    button.classList.add('success');
    
    setTimeout(() => {
      button.textContent = originalText;
      button.classList.remove('success');
    }, 2000);
  }

  showError(button) {
    const originalText = button.textContent;
    button.textContent = '✗ エラー';
    button.classList.add('error');
    
    setTimeout(() => {
      button.textContent = originalText;
      button.classList.remove('error');
    }, 2000);
  }
}

// ページ読み込み後に初期化
document.addEventListener('DOMContentLoaded', () => {
  new CodeCopyButton();
});

WordPress での実装

プラグインを使用

推奨プラグイン

  • Enlighter – Customizable Syntax Highlighter
  • Code Block Pro
  • Syntax Highlighter Evolved

手動実装

functions.php に追加

function add_copy_button_script() {
    wp_enqueue_script(
        'copy-code-button',
        get_template_directory_uri() . '/js/copy-button.js',
        array(),
        '1.0.0',
        true
    );
}
add_action('wp_enqueue_scripts', 'add_copy_button_script');

デザインとユーザビリティの考慮

ボタンデザインのベストプラクティス

視認性の確保

.copy-button {
  /* 背景と十分なコントラストを確保 */
  background-color: #0066cc;
  color: white;
  
  /* ホバー効果で操作可能であることを示す */
  transition: background-color 0.2s ease;
}

.copy-button:hover {
  background-color: #0052a3;
}

レスポンシブ対応

@media (max-width: 768px) {
  .copy-button {
    font-size: 14px;
    padding: 6px 10px;
  }
}

アクセシビリティの配慮

ARIA属性の追加

<button 
  class="copy-button" 
  aria-label="コードをクリップボードにコピー"
  title="コピー">
  📋
</button>

キーボード操作への対応

button.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    copyCode(e.target);
  }
});

トラブルシューティング

よくある問題と解決方法

問題1:clipboard APIが動作しない

原因:HTTPSでない環境では clipboard API が制限される

解決方法

function fallbackCopyTextToClipboard(text) {
  const textArea = document.createElement("textarea");
  textArea.value = text;
  document.body.appendChild(textArea);
  textArea.focus();
  textArea.select();
  
  try {
    document.execCommand('copy');
  } catch (err) {
    console.error('Fallback: Could not copy text');
  }
  
  document.body.removeChild(textArea);
}

function copyToClipboard(text) {
  if (!navigator.clipboard) {
    fallbackCopyTextToClipboard(text);
    return;
  }
  
  navigator.clipboard.writeText(text);
}

問題2:ボタンが表示されない

原因:CSSの優先度やJavaScriptの実行タイミング

解決方法

// DOMContentLoadedで確実に実行
document.addEventListener('DOMContentLoaded', function() {
  // 少し遅延させて確実に要素が存在してから実行
  setTimeout(() => {
    initCopyButtons();
  }, 100);
});

問題3:コピーしたテキストに余計な文字が含まれる

原因:HTML要素や空白の処理

解決方法

function getCleanText(element) {
  // HTMLタグを除去し、余計な空白を整理
  return element.textContent
    .replace(/^\s+|\s+$/g, '') // 前後の空白を削除
    .replace(/\n\s+/g, '\n');  // 行頭の余計なスペースを削除
}

まとめ

各方法の特徴比較

方法難易度カスタマイズ性メンテナンス性
プラットフォーム標準
静的サイトジェネレーター
手動実装最高
プラグイン使用

推奨アプローチ

初心者の場合

  1. GitLab/Bitbucket:設定不要で自動的にコピーボタンが付く
  2. Docusaurus:ドキュメントサイトなら最適
  3. VS Code + MPE:ローカルでの執筆・プレビューに便利

中級者の場合

  1. Hugo + Prism.js:高いカスタマイズ性
  2. Jekyll + プラグイン:GitHub Pagesとの相性が良い
  3. Next.js + カスタムコンポーネント:Reactベースの開発

上級者の場合

  1. 完全自作実装:要件に完全に合わせられる
  2. 既存ツールの拡張:独自機能の追加
  3. ライブラリ開発:再利用可能なソリューション

コメント

タイトルとURLをコピーしました