1. 为什么博客需要代码块折叠功能
作为一个技术博主,我经常需要在文章里插入大段代码示例。最初我直接贴完整代码,但很快发现这带来三个严重问题:
- 长代码块会打断阅读节奏,读者需要不停滚动页面
- 移动端浏览时,代码块可能超出屏幕宽度
- 包含多个代码示例的文章会变得异常冗长
去年我的博客数据分析显示:文章平均阅读完成率只有43%,而含有超过3个代码块的文章完成率骤降到28%。这促使我开始研究代码块折叠方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 实现代码块折叠的四种技术方案对比
2.1 纯CSS方案
最简单的实现方式,只需要添加几行CSS:
css复制details > pre {
max-height: 200px;
overflow: hidden;
transition: max-height 0.3s ease;
}
details[open] > pre {
max-height: none;
}
优点:
- 零JavaScript依赖
- 性能最佳
- 支持系统原生动画
缺点:
- 无法保存折叠状态
- 动画效果有限
- 兼容性要求HTML5的details标签
2.2 JavaScript + localStorage方案
我的博客最终采用的方案,核心逻辑:
javascript复制document.querySelectorAll('.code-block').forEach(block => {
const id = block.dataset.id;
const savedState = localStorage.getItem(`code-fold-${id}`);
if (savedState === 'folded') {
block.classList.add('folded');
}
block.querySelector('.toggle').addEventListener('click', () => {
block.classList.toggle('folded');
localStorage.setItem(`code-fold-${id}`,
block.classList.contains('folded') ? 'folded' : 'expanded');
});
});
实现细节:
- 为每个代码块生成唯一ID
- 初始化时读取本地存储状态
- 点击切换时更新DOM和存储状态
2.3 第三方库方案
常见的选择包括:
- CodeMirror
- PrismJS插件
- Highlight.js的折叠插件
选型建议:
- 如果已在使用这些库,直接使用插件最方便
- 否则会引入不必要的依赖
- 插件通常有更丰富的功能(如行号、语言识别)
2.4 服务端渲染方案
对于静态站点生成器(如Hugo、Jekyll):
go复制// Hugo的shortcode实现
{{ $id := .Get "id" | default (md5 .Inner) }}
<div class="code-block" data-id="{{ $id }}">
<button class="toggle">Toggle</button>
{{ highlight .Inner .Type }}
</div>
优势:
- 完全静态,无需客户端JS
- 更好的SEO表现
- 可与构建流程深度集成
3. 我的完整实现方案
3.1 HTML结构设计
html复制<article class="post">
<div class="code-block" data-id="unique-hash">
<button class="toggle" aria-expanded="false">
<span class="icon">▶</span>
<span class="text">Show Code</span>
</button>
<pre><code class="language-javascript">
// 你的代码...
</code></pre>
</div>
</article>
关键设计点:
- 使用data-id保证唯一性
- ARIA属性增强可访问性
- 双状态按钮图标(▶/▼)
3.2 CSS样式优化
css复制.code-block {
margin: 1.5em 0;
border-radius: 6px;
overflow: hidden;
}
.code-block.folded pre {
max-height: 120px;
mask-image: linear-gradient(to bottom, black 50%, transparent);
}
.toggle {
background: #f5f7fa;
border: none;
width: 100%;
padding: 8px;
text-align: left;
cursor: pointer;
}
.toggle:hover {
background: #ebeff5;
}
.toggle .icon {
display: inline-block;
transition: transform 0.2s;
}
.code-block.folded .icon {
transform: rotate(0);
}
.code-block:not(.folded) .icon {
transform: rotate(90deg);
}
视觉优化技巧:
- 渐变遮罩替代生硬的截断
- 平滑的图标旋转动画
- 悬停状态反馈
3.3 JavaScript增强
javascript复制// 生成基于内容的稳定hash
function getCodeBlockId(code) {
return btoa(
encodeURIComponent(code)
.replace(/%([0-9A-F]{2})/g, (_, p1) =>
String.fromCharCode('0x' + p1))
).substring(0, 12);
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.code-block').forEach(block => {
const code = block.querySelector('pre').textContent;
const id = block.dataset.id || getCodeBlockId(code);
block.dataset.id = id;
const toggle = block.querySelector('.toggle');
const savedState = localStorage.getItem(`code-fold-${id}`);
if (savedState === 'folded' ||
(savedState === null && code.split('\n').length > 15)) {
block.classList.add('folded');
toggle.setAttribute('aria-expanded', 'false');
}
toggle.addEventListener('click', () => {
block.classList.toggle('folded');
const isFolded = block.classList.contains('folded');
toggle.setAttribute('aria-expanded', String(!isFolded));
localStorage.setItem(`code-fold-${id}`, isFolded ? 'folded' : 'expanded');
});
});
});
高级功能:
- 自动折叠超过15行的代码块
- 基于内容生成稳定ID
- 完整的ARIA状态管理
4. 性能优化与异常处理
4.1 防抖与批量操作
当页面包含大量代码块时:
javascript复制function processCodeBlocks() {
const blocks = document.querySelectorAll('.code-block');
const chunkSize = 10;
for (let i = 0; i < blocks.length; i += chunkSize) {
setTimeout(() => {
const chunk = Array.from(blocks)
.slice(i, i + chunkSize);
initCodeBlocks(chunk);
}, 0);
}
}
4.2 localStorage异常处理
javascript复制function safeGetStorage(key) {
try {
return localStorage.getItem(key);
} catch (e) {
console.warn('LocalStorage access failed:', e);
return null;
}
}
4.3 备用CSS方案
当JavaScript不可用时:
css复制.no-js .code-block pre {
max-height: none !important;
}
5. 不同博客平台的适配方案
5.1 WordPress实现
php复制// 在主题的functions.php中添加
function add_code_folding() {
wp_enqueue_script('code-folding',
get_template_directory_uri() . '/js/code-folding.js',
[], '1.0', true);
wp_add_inline_style('code-folding-css', '
.code-block.folded pre {
max-height: 150px;
}
');
}
add_action('wp_enqueue_scripts', 'add_code_folding');
5.2 Hexo插件方案
创建hexo-code-folding插件:
javascript复制hexo.extend.filter.register('after_post_render', function(data) {
data.content = data.content.replace(
/<pre><code.*?>([\s\S]*?)<\/code><\/pre>/g,
(match, code) => `
<div class="code-block">
<button class="toggle">Toggle Code</button>
${match}
</div>
`
);
return data;
});
5.3 通用Markdown处理器方案
对于任何Markdown转HTML的流程:
javascript复制marked.setOptions({
highlight: function(code, lang) {
const highlighted = hljs.highlight(lang, code).value;
return `<div class="code-block">
<button class="toggle">${lang} Code</button>
<pre><code class="hljs ${lang}">${highlighted}</code></pre>
</div>`;
}
});
6. 用户体验优化实践
6.1 视觉反馈增强
css复制.code-block.folded pre::after {
content: "⋯⋯";
position: absolute;
bottom: 0;
left: 0;
right: 0;
text-align: center;
background: linear-gradient(to bottom, transparent, rgba(255,255,255,0.8));
padding: 10px 0;
}
6.2 快捷键支持
javascript复制document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'BODY' && e.altKey && e.code === 'KeyC') {
document.querySelectorAll('.code-block').forEach(block => {
block.classList.toggle('folded');
});
}
});
6.3 折叠状态记忆策略
优化方案:
- 使用sessionStorage替代localStorage减少持久化压力
- 设置过期时间自动清除旧记录
- 对小型代码块不启用记忆功能
javascript复制const ONE_DAY = 86400000;
function saveFoldState(id, state) {
const data = {
state,
timestamp: Date.now()
};
localStorage.setItem(`code-fold-${id}`, JSON.stringify(data));
}
function loadFoldState(id) {
const raw = localStorage.getItem(`code-fold-${id}`);
if (!raw) return null;
try {
const data = JSON.parse(raw);
if (Date.now() - data.timestamp > ONE_DAY) {
localStorage.removeItem(`code-fold-${id}`);
return null;
}
return data.state;
} catch {
return null;
}
}
7. 实际效果与数据分析
实施代码折叠功能后,我的博客关键指标变化:
| 指标 | 前 | 后 | 变化 |
|---|---|---|---|
| 平均阅读时间 | 2.1min | 3.4min | +62% |
| 移动端完成率 | 31% | 49% | +58% |
| 代码示例点击率 | - | 78% | - |
| 文章分享率 | 12% | 18% | +50% |
关键发现:
- 读者更愿意与展开的代码块交互
- 折叠状态默认隐藏长代码能降低跳出率
- 移动端用户受益最明显
8. 进阶功能探索
8.1 智能折叠策略
基于代码复杂度的自动折叠:
javascript复制function calculateComplexity(code) {
const lines = code.split('\n');
let score = 0;
// 统计各种复杂度指标
score += lines.filter(l => l.includes('for(')).length * 3;
score += lines.filter(l => l.includes('if(')).length * 2;
score += lines.filter(l => l.includes('=>')).length;
score += lines.length * 0.5;
return score;
}
// 使用
const complexity = calculateComplexity(code);
if (complexity > 30) {
block.classList.add('folded');
}
8.2 代码差异对比
集成diff功能:
html复制<div class="code-diff">
<div class="code-block" data-mode="diff">
<button class="toggle">Show Changes</button>
<pre><code class="language-diff">
// 差异代码...
</code></pre>
</div>
</div>
8.3 与代码沙箱集成
点击直接在CodePen/JSFiddle运行:
javascript复制block.querySelector('.run-code').addEventListener('click', () => {
const code = block.querySelector('pre').textContent;
const form = document.createElement('form');
form.method = 'POST';
form.action = 'https://codepen.io/pen/define';
form.target = '_blank';
form.innerHTML = `<input type="hidden" name="data" value="${
JSON.stringify({
js: code,
editors: '100'
})
}">`;
document.body.appendChild(form);
form.submit();
form.remove();
});
