前言
在撰寫技術文章時,常會使用程式碼區塊展示範例程式碼。為了提升讀者體驗,讓他們能快速複製程式碼,本文示範如何在 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
});