WordPress 單篇文章中為程式碼區塊加入右上角複製按鈕

前言

在撰寫技術文章時,常會使用程式碼區塊展示範例程式碼。為了提升讀者體驗,讓他們能快速複製程式碼,本文示範如何在 WordPress 單篇文章頁面中,為所有 pre , code 程式碼區塊動態加入一個右上角的「Copy」按鈕。此功能適用於 Gutenberg、Markdown、Prism.js 或 Highlight.js 等常見的程式碼高亮方案。

此做法適合熟悉 WordPress 主題或外掛開發的工程師,想要自訂前端行為並強化內容互動性。

為什麼要這樣設計?

透過 WordPress 的 wp_footer action,我們能在頁面底部注入必要的 CSS 與 JavaScript,確保只在前台且單篇文章頁面執行,避免管理後台或非文章頁面產生不必要的負擔。

此外,使用 JavaScript 動態包裝 pre 元素並加入按鈕,能兼容多種程式碼呈現方式,且避免重複包裝,確保效能與穩定性。

核心程式碼解析

1. 僅在單篇文章頁面執行

if ( ! is_singular( 'post' ) || is_admin() ) {
    return;
}

這段檢查確保腳本只在前台的單篇文章頁面執行,避免影響其他頁面或後台。

2. CSS 樣式定義

.code-copy-btn {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 10;
    padding: 4px 10px;
    font-size: 12px;
    border-radius: 6px;
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    cursor: pointer;
    opacity: 0.75;
    transition: background 0.2s ease, opacity 0.2s ease;
}
.code-copy-btn:hover {
    background: rgba(0, 0, 0, 0.85);
    opacity: 1;
}
.code-copy-btn:disabled {
    cursor: default;
    opacity: 0.6;
}

這些樣式將按鈕定位在程式碼區塊右上角,並提供滑鼠懸停與禁用狀態的視覺反饋。

3. 動態包裝與按鈕建立

document.querySelectorAll('pre > code').forEach(function (codeBlock) {
    const pre = codeBlock.parentNode;
    if (pre.parentNode.classList.contains('code-block-wrapper')) return;

    const wrapper = document.createElement('div');
    wrapper.className = 'code-block-wrapper';
    pre.parentNode.insertBefore(wrapper, pre);
    wrapper.appendChild(pre);

    const btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'code-copy-btn';
    btn.textContent = 'Copy';

    btn.addEventListener('click', function () {
        const text = codeBlock.innerText;
        navigator.clipboard.writeText(text).then(function () {
            btn.textContent = 'Copied!';
            btn.disabled = true;
            setTimeout(function () {
                btn.textContent = 'Copy';
                btn.disabled = false;
            }, 1500);
        });
    });

    wrapper.appendChild(btn);
});

這段程式碼會搜尋所有 pre code 結構,為每個程式碼區塊包裹一個容器,並在右上角加入按鈕。按鈕點擊時會將程式碼文字複製到剪貼簿,並短暫顯示 “Copied!” 提示。

實務應用與優化建議

  • 可依需求調整按鈕文字或樣式,讓 UI 更符合網站風格。
  • 若網站使用 AJAX 載入文章內容,需確保此腳本在新內容載入後重新執行。
  • 為提升相容性,可加入對 navigator.clipboard 支援性的檢查,並提供替代方案。
  • 若程式碼區塊非常長,複製整段可能影響使用者體驗,可考慮限制複製範圍或加入分段複製功能。

常見問題與注意事項

  • 本方案依賴現代瀏覽器的 Clipboard API,舊版瀏覽器可能無法正常複製。
  • 若網站有其他 JavaScript 操作程式碼區塊,請注意避免衝突。
  • 按鈕的絕對定位需確保父容器有相對定位,否則位置會錯亂。

完整程式碼

<?php
/**
 * 單篇 post 頁面:全站 
<pre><code> 右上角加入 Copy 按鈕
 * 適用 Gutenberg / Markdown / Prism / Highlight.js
 */

add_action( 'wp_footer', function () {

    // ✅ 只在前台「單篇文章(post)」執行
    if ( ! is_singular( 'post' ) || is_admin() ) {
        return;
    }
    ?>

<style>
        .code-block-wrapper {
            position: relative;
        }

        .code-copy-btn {
            position: absolute;
            top: 8px;
            right: 8px;
            z-index: 10;
            padding: 4px 10px;
            font-size: 12px;
            line-height: 1.4;
            border-radius: 6px;
            border: none;
            background: rgba(0, 0, 0, 0.65);
            color: #fff;
            cursor: pointer;
            transition: background 0.2s ease, opacity 0.2s ease;
            opacity: 0.75;
        }

        .code-copy-btn:hover {
            background: rgba(0, 0, 0, 0.85);
            opacity: 1;
        }

        .code-copy-btn:disabled {
            cursor: default;
            opacity: 0.6;
        }

        .code-block-wrapper pre {
            margin: 0;
        }
    </style>

    <script>
    document.addEventListener('DOMContentLoaded', function () {

        document.querySelectorAll('pre > code').forEach(function (codeBlock) {

            const pre = codeBlock.parentNode;

            // 已處理過就跳過(避免 AJAX / 重複包)
            if (pre.parentNode.classList.contains('code-block-wrapper')) {
                return;
            }

            // 建立包裝容器
            const wrapper = document.createElement('div');
            wrapper.className = 'code-block-wrapper';

            pre.parentNode.insertBefore(wrapper, pre);
            wrapper.appendChild(pre);

            // 建立 Copy 按鈕
            const btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'code-copy-btn';
            btn.textContent = 'Copy';

            btn.addEventListener('click', function () {
                const text = codeBlock.innerText;

                navigator.clipboard.writeText(text).then(function () {
                    btn.textContent = 'Copied!';
                    btn.disabled = true;

                    setTimeout(function () {
                        btn.textContent = 'Copy';
                        btn.disabled = false;
                    }, 1500);
                });
            });

            wrapper.appendChild(btn);
        });

    });
    </script>
    <?php
});