第6章:布局与包含
第6章:布局与包含
6.1 布局系统概述
Jekyll 的布局(Layout)系统是页面渲染的骨架。布局定义了页面的外层 HTML 结构,内容则被注入到布局的 {{ content }} 位置。
┌─────────────────────────────┐
│ _layouts/default.html │
│ ┌─────────────────────────┐│
│ │ <html> ││
│ │ <head>...</head> ││
│ │ <body> ││
│ │ ┌─────────────────────┐││
│ │ │ {{ content }} │││ ← 页面内容注入位置
│ │ │ (from page/post) │││
│ │ └─────────────────────┘││
│ │ </body> ││
│ │ </html> ││
│ └─────────────────────────┘│
└─────────────────────────────┘
渲染顺序
1. 读取页面文件 (e.g., _posts/2025-01-15-hello.md)
2. 解析 Front Matter,确定 layout
3. 将 Markdown → HTML → {{ content }}
4. 将 content 注入 layout 模板
5. 如果 layout 指定了父 layout,继续向上嵌套
6. 输出最终 HTML
6.2 创建基本布局
布局文件存放
所有布局文件放在 _layouts/ 目录下:
_layouts/
├── default.html # 默认布局
├── post.html # 文章布局
├── page.html # 页面布局
├── home.html # 首页布局
└── docs.html # 文档布局
default.html 布局
<!DOCTYPE html>
<html lang="{{ site.lang | default: 'zh-CN' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% if page.title %}{{ page.title }} - {{ site.title }}{% else %}{{ site.title }}{% endif %}</title>
<meta name="description" content="{{ page.description | default: site.description | strip_html | truncatewords: 50 }}">
<link rel="stylesheet" href="{{ '/assets/css/main.css' | relative_url }}">
{% seo %}
{% feed_meta %}
</head>
<body>
{% include header.html %}
<main class="container">
{{ content }}
</main>
{% include footer.html %}
<script src="{{ '/assets/js/main.js' | relative_url }}"></script>
</body>
</html>
post.html 布局
---
layout: default
---
<article class="post">
<header class="post-header">
<h1 class="post-title">{{ page.title }}</h1>
<div class="post-meta">
<time datetime="{{ page.date | date_to_xmlschema }}">
{{ page.date | date: "%Y年%m月%d日" }}
</time>
{% if page.author %}
<span class="author">{{ page.author }}</span>
{% endif %}
{% if page.categories.size > 0 %}
<span class="categories">
{% for cat in page.categories %}
<a href="{{ '/categories/' | append: cat | relative_url }}">{{ cat }}</a>{% unless forloop.last %}、{% endunless %}
{% endfor %}
</span>
{% endif %}
</div>
{% if page.tags.size > 0 %}
<div class="post-tags">
{% for tag in page.tags %}
<a href="{{ '/tags/' | append: tag | relative_url }}" class="tag">{{ tag }}</a>
{% endfor %}
</div>
{% endif %}
</header>
<div class="post-content">
{{ content }}
</div>
<footer class="post-footer">
{% include share.html %}
{% include related-posts.html %}
{% if page.comments != false %}
{% include comments.html %}
{% endif %}
</footer>
</article>
page.html 布局
---
layout: default
---
<div class="page">
{% if page.title %}
<h1 class="page-title">{{ page.title }}</h1>
{% endif %}
<div class="page-content">
{{ content }}
</div>
</div>
home.html 布局
---
layout: default
---
<div class="home">
<section class="hero">
<h1>{{ site.title }}</h1>
<p>{{ site.description }}</p>
</section>
<section class="post-list">
<h2>最新文章</h2>
{% for post in site.posts limit: 5 %}
<article class="post-card">
<h3><a href="{{ post.url | relative_url }}">{{ post.title }}</a></h3>
<time>{{ post.date | date: "%Y-%m-%d" }}</time>
<p>{{ post.excerpt | strip_html | truncatewords: 30 }}</p>
</article>
{% endfor %}
</section>
</div>
6.3 布局继承(Layout Inheritance)
布局可以嵌套使用,形成继承链。子布局通过 Front Matter 指定父布局。
继承链示例
default.html
└── post.html
└── featured-post.html
<!-- _layouts/default.html -->
<!DOCTYPE html>
<html>
<head><title>{{ page.title }}</title></head>
<body>
<header>Site Header</header>
<main>{{ content }}</main>
<footer>Site Footer</footer>
</body>
</html>
<!-- _layouts/post.html -->
---
layout: default
---
<article>
<h1>{{ page.title }}</h1>
<div class="meta">{{ page.date | date: "%Y-%m-%d" }}</div>
<div class="content">{{ content }}</div>
</article>
<!-- _layouts/featured-post.html -->
---
layout: post
---
<div class="featured-badge">⭐ 精选文章</div>
{{ content }}
<div class="featured-cta">
<a href="/subscribe">订阅获取更多精选内容</a>
</div>
# _posts/2025-01-15-important.md
---
title: "重要文章"
layout: featured-post
---
文章内容...
渲染结果
<!DOCTYPE html>
<html>
<head><title>重要文章</title></head>
<body>
<header>Site Header</header>
<main>
<article>
<h1>重要文章</h1>
<div class="meta">2025-01-15</div>
<div class="content">
<div class="featured-badge">⭐ 精选文章</div>
<p>文章内容...</p>
<div class="featured-cta">
<a href="/subscribe">订阅获取更多精选内容</a>
</div>
</div>
</article>
</main>
<footer>Site Footer</footer>
</body>
</html>
6.4 包含文件(Includes)
_includes/ 目录存放可复用的模板片段,通过 {% include %} 标签引入。
基本语法
<!-- 简单包含 -->
{% include header.html %}
<!-- 传递参数 -->
{% include image.html src="/images/photo.jpg" alt="照片" width="800" %}
<!-- 动态文件名 -->
{% include {{ page.sidebar }} %}
header.html 示例
<!-- _includes/header.html -->
<header class="site-header">
<div class="container">
<a href="{{ '/' | relative_url }}" class="site-title">{{ site.title }}</a>
<nav class="site-nav">
{% for item in site.data.navigation %}
<a href="{{ item.url | relative_url }}"
{% if page.url == item.url %}class="active"{% endif %}>
{{ item.title }}
</a>
{% endfor %}
</nav>
</div>
</header>
footer.html 示例
<!-- _includes/footer.html -->
<footer class="site-footer">
<div class="container">
<p>© {{ site.time | date: "%Y" }} {{ site.title }}</p>
<nav>
<a href="{{ '/privacy' | relative_url }}">隐私政策</a>
<a href="{{ '/sitemap.xml' | relative_url }}">站点地图</a>
</nav>
</div>
</footer>
传递参数的 include
<!-- _includes/image.html -->
{% if include.src %}
<figure class="{% if include.align %}align-{{ include.align }}{% endif %}">
<img
src="{{ include.src | relative_url }}"
alt="{{ include.alt | default: '' }}"
{% if include.width %}width="{{ include.width }}"{% endif %}
{% if include.height %}height="{{ include.height }}"{% endif %}
loading="lazy"
>
{% if include.caption %}
<figcaption>{{ include.caption }}</figcaption>
{% endif %}
</figure>
{% endif %}
<!-- 使用 -->
{% include image.html
src="/images/blog/jekyll-banner.jpg"
alt="Jekyll 横幅"
width="800"
align="center"
caption="Jekyll 静态站点生成器" %}
条件 include
<!-- 根据页面配置决定是否包含 -->
{% if page.toc %}
{% include toc.html content=content %}
{% endif %}
{% if page.mermaid %}
{% include mermaid.html %}
{% endif %}
{% if jekyll.environment == "production" %}
{% include analytics.html %}
{% endif %}
6.5 include 变量作用域
<!-- _includes/author-card.html -->
{% comment %}
include.author 是传递进来的参数
page.* 和 site.* 仍然可以访问
{% endcomment %}
{% assign author_data = site.data.authors[include.author] %}
{% if author_data %}
<div class="author-card">
<img src="{{ author_data.avatar }}" alt="{{ author_data.name }}">
<h4>{{ author_data.name }}</h4>
<p>{{ author_data.bio }}</p>
</div>
{% endif %}
<!-- 在页面中使用 -->
{% include author-card.html author=page.author %}
注意事项:
- include 中的
assign变量不会影响包含者的变量 - include 中可以直接访问
page、site、content等全局对象 - 传递参数使用
name=value语法,多个参数用空格分隔
6.6 多布局策略
按内容类型划分
_layouts/
├── default.html # 基础 HTML 骨架
├── home.html # 首页(继承 default)
├── post.html # 文章(继承 default)
├── page.html # 静态页面(继承 default)
├── docs.html # 文档页面(继承 default)
├── archive.html # 归档页面(继承 default)
├── 404.html # 404 页面(继承 default)
└── minimal.html # 最小布局(继承 default,无导航栏)
docs.html 文档布局
---
layout: default
---
<div class="docs-layout">
<aside class="docs-sidebar">
<nav class="docs-nav">
{% assign sorted_docs = site.docs | sort: "order" %}
{% for doc in sorted_docs %}
<a href="{{ doc.url | relative_url }}"
{% if page.url == doc.url %}class="active"{% endif %}>
{{ doc.title }}
</a>
{% endfor %}
</nav>
</aside>
<main class="docs-content">
<h1>{{ page.title }}</h1>
{{ content }}
{% if page.prev_url or page.next_url %}
<nav class="docs-pagination">
{% if page.prev_url %}
<a href="{{ page.prev_url | relative_url }}" class="prev">← {{ page.prev_title }}</a>
{% endif %}
{% if page.next_url %}
<a href="{{ page.next_url | relative_url }}" class="next">{{ page.next_title }} →</a>
{% endif %}
</nav>
{% endif %}
</main>
</div>
6.7 页面结构设计模式
模式1:经典博客
┌─────────────────────────────────────────┐
│ header.html │
│ Logo Navigation Search RSS │
├─────────────────────┬───────────────────┤
│ │ │
│ 主内容区 │ 侧边栏 │
│ {{ content }} │ ├── 最新文章 │
│ │ ├── 分类列表 │
│ 文章列表 │ ├── 标签云 │
│ 分页导航 │ └── 关于我 │
│ │ │
├─────────────────────┴───────────────────┤
│ footer.html │
│ 版权 链接 社交媒体 │
└─────────────────────────────────────────┘
模式2:文档站点
┌─────────────────────────────────────────┐
│ header.html │
├──────────┬──────────────────────────────┤
│ │ │
│ 侧边栏 │ 文档内容 │
│ 导航 │ ├── 标题 │
│ 目录 │ ├── 正文 │
│ 搜索 │ ├── 代码示例 │
│ │ ├── 上/下篇导航 │
│ │ └── 编辑链接 │
│ │ │
├──────────┴──────────────────────────────┤
│ footer.html │
└─────────────────────────────────────────┘
模式3:落地页
┌─────────────────────────────────────────┐
│ ┌─────────────────────────────────────┐│
│ │ Hero Section ││
│ │ 大标题 + CTA 按钮 ││
│ └─────────────────────────────────────┘│
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 特性1│ │ 特性2│ │ 特性3│ │
│ └──────┘ └──────┘ └──────┘ │
│ ┌─────────────────────────────────────┐│
│ │ 详细介绍 ││
│ └─────────────────────────────────────┘│
│ ┌─────────────────────────────────────┐│
│ │ CTA Section ││
│ └─────────────────────────────────────┘│
└─────────────────────────────────────────┘
6.8 高级技巧
条件性 CSS 类
<body class="page-{{ page.layout }} {% if page.sidebar %}has-sidebar{% endif %}">
<!-- 根据页面类型加载不同样式 -->
{% case page.layout %}
{% when "post" %}
<link rel="stylesheet" href="{{ '/assets/css/post.css' | relative_url }}">
{% when "docs" %}
<link rel="stylesheet" href="{{ '/assets/css/docs.css' | relative_url }}">
{% when "home" %}
<link rel="stylesheet" href="{{ '/assets/css/home.css' | relative_url }}">
{% endcase %}
动态 include
<!-- 根据页面配置选择不同的侧边栏 -->
{% assign sidebar = page.sidebar | default: "default-sidebar" %}
{% include sidebar/{{ sidebar }}.html %}
布局中的面包屑导航
<!-- _includes/breadcrumb.html -->
<nav aria-label="breadcrumb" class="breadcrumb">
<ol>
<li><a href="{{ '/' | relative_url }}">首页</a></li>
{% if page.layout == "post" %}
<li><a href="{{ '/blog' | relative_url }}">博客</a></li>
{% if page.categories.size > 0 %}
{% for cat in page.categories %}
<li><a href="{{ '/categories/' | append: cat | relative_url }}">{{ cat }}</a></li>
{% endfor %}
{% endif %}
<li aria-current="page">{{ page.title }}</li>
{% elsif page.layout == "docs" %}
<li><a href="{{ '/docs' | relative_url }}">文档</a></li>
<li aria-current="page">{{ page.title }}</li>
{% else %}
<li aria-current="page">{{ page.title }}</li>
{% endif %}
</ol>
</nav>
6.9 业务场景:多语言文档站点
# _config.yml
collections:
docs_en:
output: true
permalink: /en/docs/:name/
docs_zh:
output: true
permalink: /zh/docs/:name/
<!-- _layouts/doc.html -->
---
layout: default
---
{% assign lang = page.url | slice: 1, 2 %}
<div class="doc-layout" lang="{{ lang }}">
<aside class="doc-sidebar">
{% if lang == "zh" %}
{% for doc in site.docs_zh sort: "order" %}
<a href="{{ doc.url }}">{{ doc.title }}</a>
{% endfor %}
{% else %}
{% for doc in site.docs_en sort: "order" %}
<a href="{{ doc.url }}">{{ doc.title }}</a>
{% endfor %}
{% endif %}
</aside>
<main>
<h1>{{ page.title }}</h1>
{{ content }}
</main>
</div>
6.10 扩展阅读
本章小结
| 要点 | 说明 |
|---|---|
| 布局 | _layouts/ 中的 HTML 模板,通过 {{ content }} 注入内容 |
| 继承 | 子布局通过 Front Matter 的 layout 指定父布局 |
| 包含 | _includes/ 中的可复用片段,支持参数传递 |
| 作用域 | include 可以访问 page、site 等全局对象 |
| 设计模式 | 根据站点类型选择经典博客、文档站、落地页等模式 |
下一章:文章管理