はじめに
はてなブログにおいて、コードブロックの利便性を高めるためには「コピーボタン」を導入することが効果的だ。
この記事では、Markdown記法で記述された記事にコードブロックのコピーボタンを追加する方法について、初心者でも分かりやすく手順を解説する。
手順
1. デザインの設定
まず、はてなブログの「デザイン設定」を開き、「カスタマイズ」タブをクリックする。
2. CSSの設定
以下の手順でCSSを設定する。 1. 「デザインCSS」を選択する。 2. 以下のコードをコピーし、入力欄に貼り付ける。
/* コードブロックのコンテナ */ .code-block-container { position: relative; background: #1D2020; font-size: 14px; font-weight: 500; color: #FFFFFF; overflow: visible; border-radius: 6px; margin-bottom: 16px !important; } /* コピーボタン */ .copy-button, .gist-copy-button { position: absolute; top: 5px; right: 5px; padding: 4px 8px; font-size: 12px; font-weight: 500; background-color: rgba(255, 255, 255, 0.8); color: #333; border: 1px solid #d1d5db; border-radius: 4px; cursor: pointer; z-index: 1000; transition: all 0.2s ease; opacity: 1; /* デフォルトで表示 */ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } /* アクティブ時やホバー時 */ .code-block-container.active .copy-button, .gist.active .gist-copy-button, .copy-button:hover, .gist-copy-button:hover { opacity: 0.9; /* 完全に表示 */ background-color: #ffffff; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } /* クリック時のスタイル */ .copy-button:active, .gist-copy-button:active { background-color: #e5e7eb; }
これでコードブロックの見た目とコピーボタンのスタイルが適用される。
3. フッターHTMLの設定
「カスタマイズ」タブの「フッターHTML」に以下のJavaScriptを追加する。これにより、コードブロックにコピーボタンが動的に追加される。
<script> /** * コードブロックに言語名のブロックを追加する関数 * @param {HTMLElement} container - コードブロックのコンテナ要素 * @param {HTMLElement} block - コードブロック */ function addFileNameBlock(container, block) { const language = block.getAttribute('data-lang'); if (language && !container.querySelector('.file-name-block')) { const fileNameBlock = document.createElement('div'); fileNameBlock.className = 'file-name-block'; fileNameBlock.textContent = language; container.insertBefore(fileNameBlock, container.firstChild); } } /** * 通常のコードブロックにコピーボタンを追加する関数 */ function addCopyButtons() { document.querySelectorAll('pre:not(.gist pre)').forEach((block) => { if (!block.closest('.code-block-container')) { const container = document.createElement('div'); container.className = 'code-block-container'; block.parentNode.insertBefore(container, block); container.appendChild(block); } const container = block.closest('.code-block-container'); // ファイル名ブロックの追加 addFileNameBlock(container, block); // コピーボタンがまだない場合のみ追加 if (!container.querySelector('.copy-button')) { const copyButton = document.createElement('button'); copyButton.className = 'copy-button'; copyButton.textContent = 'Copy'; container.insertBefore(copyButton, container.firstChild); // コピーボタンのクリックイベント copyButton.addEventListener('click', (e) => { e.stopPropagation(); const codeElement = block.querySelector('code') || block; const code = codeElement.innerText.trim(); copyTextToClipboard(code, copyButton); }); // コンテナのクリックでアクティブ状態を切り替え container.addEventListener('click', () => { container.classList.toggle('active'); }); } }); } /** * Gistにコピーボタンを追加する関数 */ function addGistCopyButtons() { console.log('Adding copy buttons to Gists'); document.querySelectorAll('.gist').forEach((gist) => { if (!gist.querySelector('.gist-copy-button')) { const copyButton = document.createElement('button'); copyButton.className = 'gist-copy-button'; copyButton.textContent = 'Copy'; gist.appendChild(copyButton); // Gistのコピーボタンのクリックイベント copyButton.addEventListener('click', (e) => { e.stopPropagation(); const codeElement = gist.querySelector('.js-file-line-container'); if (codeElement) { const code = codeElement.innerText.trim(); copyTextToClipboard(code, copyButton); } else { console.error('Could not find code element in Gist'); updateButtonText(copyButton, false); } }); // Gistのクリックでアクティブ状態を切り替え gist.addEventListener('click', () => { gist.classList.toggle('active'); }); } }); } /** * テキストをクリップボードにコピーする関数 * @param {string} text - コピーするテキスト * @param {HTMLElement} button - コピーボタン要素 */ function copyTextToClipboard(text, button) { if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text) .then(() => updateButtonText(button, true)) .catch((err) => { console.error('Clipboard write failed:', err); fallbackCopyTextToClipboard(text, button); }); } else { fallbackCopyTextToClipboard(text, button); } } /** * クリップボードへのコピーのフォールバック方法 * @param {string} text - コピーするテキスト * @param {HTMLElement} button - コピーボタン要素 */ function fallbackCopyTextToClipboard(text, button) { const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { const successful = document.execCommand('copy'); updateButtonText(button, successful); } catch (err) { console.error('Fallback: Unable to copy', err); updateButtonText(button, false); } finally { document.body.removeChild(textArea); } } /** * コピーボタンのテキストを更新する関数 * @param {HTMLElement} button - コピーボタン要素 * @param {boolean} successful - コピー操作が成功したかどうか */ function updateButtonText(button, successful) { button.textContent = successful ? '✅Copied' : '❎Copy Failure'; setTimeout(() => { button.textContent = 'Copy'; }, 2000); } /** * コピーボタンの初期化関数 */ function initializeCopyButtons() { addCopyButtons(); addGistCopyButtons(); } // 初期化のイベント登録 document.addEventListener('DOMContentLoaded', initializeCopyButtons); window.addEventListener('load', initializeCopyButtons); // 動的に追加されたコードブロックに対応するためのMutationObserver const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes && mutation.addedNodes.length > 0) { initializeCopyButtons(); } }); }); observer.observe(document.body, { childList: true, subtree: true, }); </script>
4. 確認と微調整
ブログをプレビューして、コードブロックに「Copy」ボタンが表示されているか確認する。
まとめ
本記事では、はてなブログにコードブロックのコピーボタンを追加する方法を紹介した。 この設定を導入することで、読者は簡単にコードをコピーできるようになる。