Hexo Light主題新增程式碼複製功能
注意
本文由 Claude Sonnet 4.5 語言模型產生。
適用於使用 highlight.js 的 hexo-theme-light 主題。
本教學使用 CSS
::before顯示 Font Awesome 圖示,無需載入完整的 Font Awesome CSS。已針對 Firefox、Brave、Safari 進行跨瀏覽器測試與優化。
前置準備
確認你的主題符合以下條件:
- ✅ 使用 highlight.js 進行程式碼語法高亮。
- ✅ 主題內建 Font Awesome 字體檔案(在
source/css/font/目錄)。
步驟 1:下載 clipboard.js
下載 clipboard.js 並放到主題的 js 資料夾:
- 前往 https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js。
- 右鍵另存新檔,存為
clipboard.min.js。 - 將檔案放到
themes/hexo-theme-light/source/js/目錄。
步驟 2:建立 clipboard.use.js
在 themes/hexo-theme-light/source/js/ 目錄下建立 clipboard.use.js 檔案,內容如下:
// 添加複製按鈕的函數
function addCopyButtons() {
const codeBlocks = document.querySelectorAll('pre code');
codeBlocks.forEach(function(code) {
const pre = code.parentElement;
// 確保父元素真的是 pre
if (!pre || pre.tagName.toLowerCase() !== 'pre') return;
// 如果已經有複製按鈕就跳過
if (pre.querySelector('.copy-btn')) return;
// 確保 pre 有相對定位
const prePosition = window.getComputedStyle(pre).position;
if (prePosition !== 'relative' && prePosition !== 'absolute' && prePosition !== 'fixed') {
pre.style.position = 'relative';
}
// 建立複製按鈕(不需要放 HTML 內容,用 CSS ::before 顯示圖示)
const btn = document.createElement('button');
btn.className = 'copy-btn';
btn.type = 'button';
btn.title = '複製程式碼';
// 插入到 pre 的最前面
pre.insertBefore(btn, pre.firstChild);
});
}
// 初始化 Clipboard
function initClipboard() {
// 確保 ClipboardJS 有成功載入
if (typeof ClipboardJS === 'undefined') return;
const clipboard = new ClipboardJS('.copy-btn', {
target: function(trigger) {
return trigger.parentElement.querySelector('code');
}
});
clipboard.on('success', function(e) {
e.clearSelection();
const btn = e.trigger;
// 只需要加上 class,CSS 會自動切換圖示
btn.classList.add('copy-success');
setTimeout(function() {
btn.classList.remove('copy-success');
}, 2000);
});
clipboard.on('error', function(e) {
const btn = e.trigger;
// 只需要加上 class,CSS 會自動切換圖示
btn.classList.add('copy-error');
setTimeout(function() {
btn.classList.remove('copy-error');
}, 2000);
});
}
// 當頁面載入完成後執行
document.addEventListener('DOMContentLoaded', function() {
addCopyButtons();
if (document.querySelectorAll('pre code').length === 0) {
setTimeout(addCopyButtons, 200);
}
initClipboard();
});
window.addEventListener('load', function() {
addCopyButtons();
});
步驟 3:修改 after_footer.ejs
編輯 themes/hexo-theme-light/layout/_partial/after_footer.ejs,調整 JS 載入順序:
<%- js('js/jquery-3.4.1.min.js') %>
<%- js('js/jquery.imagesloaded.min.js') %>
<%- js('js/gallery.js') %>
<%- js('js/highlight.min.js') %>
<script>hljs.highlightAll();</script>
<%- js('js/clipboard.min.js') %>
<%- js('js/clipboard.use.js') %>
<% if (theme.fancybox){ %>
<%- css('fancybox/jquery.fancybox.min.css') %>
<%- js('fancybox/jquery.fancybox.min.js') %>
<script>
(function($){
$('.fancybox').fancybox();
})(jQuery);
</script>
<% } %>
重點:確保 highlight.min.js 和 hljs.highlightAll() 在 clipboard.min.js 和 clipboard.use.js 之前載入。
步驟 4:建立 CSS 樣式檔案
4.1 建立自定義樣式資料夾
在 themes/hexo-theme-light/source/css/ 目錄下建立 _custom 資料夾:
source/css/_custom/
4.2 建立 copy-code.styl
在 _custom 資料夾中建立 copy-code.styl 檔案,內容如下:
// 程式碼區塊複製按鈕
// 特性:平時隱藏、懸停顯示、Flexbox 置中、Safari 微調修正
// 已測試:Firefox、Brave、Safari
article
pre
position: relative
// 觸發顯示機制:滑鼠移到程式碼區塊時按鈕才出現
&:hover .copy-btn
opacity: 1
.copy-btn
position: absolute
top: 5px
right: 5px
// 核心佈局:使用 Flexbox 處理水平垂直置中
display: flex
justify-content: center
align-items: center
padding: 0
// 外觀設定
width: 24px
height: 24px
background: rgba(255, 255, 255, 1)
border: 1px solid #ddd
border-radius: 3px
cursor: pointer
font-size: 12px
color: #666
z-index: 10
line-height: normal
outline: none
font-family: FontAwesome
// 動畫效果
opacity: 0
transition: opacity 0.2s ease, background 0.2s ease, color 0.2s ease, transform 0.1s ease
// 圖示設定與 Safari 微調
&:before
content: '\f0ea' // fa-clipboard 圖示
display: inline-block
transform: translateY(1px) // 向下微調 1px 修正 Safari 偏高問題
// 懸停狀態
&:hover
background: rgba(255, 255, 255, 1)
color: #333
border-color: #999
// 點擊狀態 (注意:必須同時保留 scale 和 translateY)
&:active
transform: scale(0.95) translateY(1px)
// 複製成功 (強制顯示,黑色打勾)
&.copy-success
color: #333
opacity: 1
&:before
content: '\f00c' // fa-check 圖示
// 自動繼承 translateY(1px),不需重寫
// 複製失敗 (強制顯示,紅色叉叉)
&.copy-error
color: #f44336
opacity: 1
&:before
content: '\f00d' // fa-times 圖示
4.3 引入自定義樣式
編輯 themes/hexo-theme-light/source/css/style.styl,在檔案最後加入:
// 自定義樣式
@import '_custom/copy-code'
步驟 5:重新生成部落格
hexo clean
hexo g
hexo s
測試功能
- 開啟瀏覽器訪問你的部落格。
- 找到任何包含程式碼區塊的文章。
- 將滑鼠移到程式碼區塊上,複製按鈕應該會淡入顯示。
- 點擊按鈕,應該會顯示黑色打勾圖示並成功複製程式碼。
- 貼到文字編輯器驗證內容是否正確。
- 滑鼠移開程式碼區塊,按鈕應該會淡出消失。
效果如下:

測試複製失敗情境
開啟瀏覽器開發者工具,在 Console 輸入:
(function() {
// 選取第一個 pre 和第一個 copy-btn
const firstPre = document.querySelector('pre');
const firstBtn = document.querySelector('.copy-btn');
if (!firstPre || !firstBtn) {
console.warn('頁面上找不到程式碼區塊或複製按鈕喔!');
return;
}
// 滑鼠移到程式碼區塊(觸發 hover)
firstPre.dispatchEvent(new MouseEvent('mouseenter'));
// 點擊按鈕
firstBtn.click();
// 手動觸發失敗狀態
setTimeout(function() {
firstBtn.classList.remove('copy-success');
firstBtn.classList.add('copy-error');
}, 100);
// 2秒後恢復
setTimeout(function() {
firstBtn.classList.remove('copy-error');
firstPre.dispatchEvent(new MouseEvent('mouseleave'));
}, 2000);
})();
模擬複製失敗流程(顯示紅色叉叉)。
自訂選項
更換圖示
修改 copy-code.styl 中的 Unicode:
.copy-btn
&:before
content: '\f0c5' // 改用 fa-copy 圖示
調整按鈕大小
.copy-btn
width: 28px // 調整寬度
height: 28px // 調整高度
font-size: 14px // 調整圖示大小
調整按鈕位置
.copy-btn
top: 8px // 距離頂部
right: 8px // 距離右邊
調整淡入淡出速度
.copy-btn
transition: opacity 0.3s ease, ... // 0.2s 改成 0.3s(更慢)
調整提示文字
在 clipboard.use.js 中修改按鈕建立時的 title 屬性:
const btn = document.createElement('button');
btn.className = 'copy-btn';
btn.type = 'button';
btn.title = '點擊複製';
調整成功/失敗顯示時間
在 clipboard.use.js 中修改 setTimeout 的延遲時間:
// 成功時
setTimeout(function() {
btn.classList.remove('copy-success');
}, 3000); // 這裡改 3000
// 失敗時
setTimeout(function() {
btn.classList.remove('copy-error');
}, 3000); // 這裡改 3000
永久顯示按鈕(不要懸停效果)
移除 opacity: 0 和懸停觸發:
.copy-btn
// opacity: 0 ← 註解或刪除這行
// 也要註解或刪除這段
// &:hover .copy-btn
// opacity: 1
調整按鈕顏色(深色模式)
.copy-btn
background: rgba(0, 0, 0, 0.7) // 深色背景
color: #fff // 白色圖示
border-color: rgba(255, 255, 255, 0.3)
檔案結構總覽
完成後的檔案結構:
themes/hexo-theme-light/
├── source/
│ ├── css/
│ │ ├── _custom/
│ │ │ └── copy-code.styl ← 新建
│ │ ├── font/
│ │ │ ├── fontawesome-webfont.eot ← 主題內建
│ │ │ ├── fontawesome-webfont.svg ← 主題內建
│ │ │ ├── fontawesome-webfont.ttf ← 主題內建
│ │ │ └── fontawesome-webfont.woff ← 主題內建
│ │ └── style.styl ← 修改(加入 import)
│ └── js/
│ ├── clipboard.min.js ← 新建(下載)
│ └── clipboard.use.js ← 新建
└── layout/
└── _partial/
└── after_footer.ejs ← 修改(調整載入順序)