Jekyll 主题 Minimal Mistakes 布局(Layouts)文件源码分析
Minimal Mistakes 提供了很多布局文件,为了更好地使用它们,这篇文章简单分析了几个比较常见的布局文件源码。
我们知道,Jekyll Theme 的 Layout 文件会统一放在 _layouts
目录下,每个 html 文件分别对应一种布局。
首先来看所有布局文件的基类 —— _layouts/default.html
:
default
作为所有布局文件的基类,_layouts/default.html
定义了生成 html 文件的基本框架。
我们从第一个标签 <html>
开始分析:
<html lang="{{ site.locale | slice: 0,2 | default: "en" }}" class="no-js">
可以看出,<html>
标签中给 lang
属性赋值,取值来源于 _config.yml
的全局配置 locale
。
然后是 <head>
标签:
<head>
{% include head.html %}
{% include head/custom.html %}
</head>
其中_includes/head.html
做了以下几件事:
- seo
- atom feed
- viewport
- stylesheet =>
/assets/css/main.css
- fontawesome
同时也支持引入自定义 js:
{% if site.head_scripts %}
{% for script in site.head_scripts %}
<script src="{{ script | relative_url }}"></script>
{% endfor %}
{% endif %}
第二个 include 文件 _includes/head/custom.html
是留给我们自定义用的。
继续往下看,<body>
标签:
{% include_cached skip-links.html %}
{% include_cached masthead.html %}
其中 _includes/skip-links.html
用于页面内快速跳转:
<nav class="skip-links">
<ul>
<li><a href="#site-nav" class="screen-reader-shortcut">{{ site.data.ui-text[site.locale].skip_primary_nav | default: 'Skip to primary navigation' }}</a></li>
<li><a href="#main" class="screen-reader-shortcut">{{ site.data.ui-text[site.locale].skip_content | default: 'Skip to content' }}</a></li>
<li><a href="#footer" class="screen-reader-shortcut">{{ site.data.ui-text[site.locale].skip_footer | default: 'Skip to footer' }}</a></li>
</ul>
</nav>
支持三个锚点:
#site-nav
#main
#footer
<body>
标签内的第二个 include 文件 _includes/masthead.html
是页面吊顶,我们逐行来分析。
首先是展示网站 logo,其路径来源于全局配置 site.logo
:
{% capture logo_path %}{{ site.logo }}{% endcapture %}
用一个 <a>
标签包裹的 <img>
做展示:
{% unless logo_path == empty %}
<a class="site-logo" href="{{ '/' | relative_url }}"><img src="{{ logo_path | relative_url }}" alt="{{ site.masthead_title | default: site.title }}"></a>
{% endunless %}
logo 的右边是 title
和 subtitle
,也是来自于全局配置:
<a class="site-title" href="{{ '/' | relative_url }}">
{{ site.masthead_title | default: site.title }}
{% if site.subtitle %}<span class="site-subtitle">{{ site.subtitle }}</span>{% endif %}
</a>
然后是导航条:
<ul class="visible-links">
{%- for link in site.data.navigation.main -%}
<li class="masthead__menu-item">
<a href="{{ link.url | relative_url }}"{% if link.description %} title="{{ link.description }}"{% endif %}>{{ link.title }}</a>
</li>
{%- endfor -%}
</ul>
很遗憾,不支持多级导航。
接下来是搜索按钮:
{% if site.search == true %}
<button class="search__toggle" type="button">
<span class="visually-hidden">{{ site.data.ui-text[site.locale].search_label | default: "Toggle search" }}</span>
<i class="fas fa-search"></i>
</button>
{% endif %}
其中 visually-hidden
这个 css 类比较好玩,看下源码:
.visually-hidden,
.screen-reader-text,
.screen-reader-text span,
.screen-reader-shortcut {
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
height: 1px !important;
width: 1px !important;
border: 0 !important;
overflow: hidden;
}
body:hover .visually-hidden a,
body:hover .visually-hidden input,
body:hover .visually-hidden button {
display: none !important;
}
可以看出,以上 CSS 是是一种比较 tricky 的做法,应该是做 SEO 用的。
最后是自适应布局中的 Toggle menu
:
<button class="greedy-nav__toggle hidden" type="button">
<span class="visually-hidden">{{ site.data.ui-text[site.locale].menu_label | default: "Toggle menu" }}</span>
<div class="navicon"></div>
</button>
控制 Toggle menu
自适应布局的源码位于 assets/js/plugins/jquery.greedy-navigation.js
。
以上便是 _includes/masthead.html
的源码,回到 _layouts/default.html
继续往下看:
子类布局占位:
<div class="initial-content">
{{ content }}
</div>
后面要分析的布局 _includes/splash.html
, 其文件内容就是通过 {{ content }}
变量来占位。
搜索:
{% if site.search == true %}
<div class="search-content">
{% include_cached search/search_form.html %}
</div>
{% endif %}
支持三种第三方搜索插件:
- lunr
- algolia
对应的 js 在 _includes/scripts.html
有定义。
footer:
<div id="footer" class="page__footer">
<footer>
{% include footer/custom.html %}
{% include_cached footer.html %}
</footer>
</div>
其中 _includes/footer/custom.html
用于自定义。
_includes/footer.html
用于展示如下配置:
site.data.ui-text[site.locale].follow_label
site.footer.links
link.icon
link.label
link.url
site.atom_feed
site.time
&site.name || site.title
&site.data.ui-text[site.locale].powered_by
scripts:
{% include scripts.html %}
_includes/scripts.html
中包含了以下内容:
1)自定义 js,默认使用 /assets/js/main.min.js
:
{% if site.footer_scripts %}
{% for script in site.footer_scripts %}
<script src="{{ script | relative_url }}"></script>
{% endfor %}
{% else %}
<script src="{{ '/assets/js/main.min.js' | relative_url }}"></script>
{% endif %}
2)搜索
{% if site.search == true or page.layout == "search" %}
{%- assign search_provider = site.search_provider | default: "lunr" -%}
{%- case search_provider -%}
{%- when "lunr" -%}
{% include_cached search/lunr-search-scripts.html %}
{%- when "google" -%}
{% include_cached search/google-search-scripts.html %}
{%- when "algolia" -%}
{% include_cached search/algolia-search-scripts.html %}
{%- endcase -%}
{% endif %}
3)埋点
{% include analytics.html %}
4)评论
{% include /comments-providers/scripts.html %}
5)js 脚本
{% if site.after_footer_scripts %}
{% for script in site.after_footer_scripts %}
<script src="{{ script | relative_url }}"></script>
{% endfor %}
{% endif %}
最后总结一下 _layouts/default.html
的代码结构:
- html
- head
- include
head.html
- include
seo.html
- site.atom_feed
- viewport
/assets/css/main.css
- fontawesome
- site.head_scripts
- include
- include
head/custom.html
- include
- body
- include_cached
skip-links.html
#site-nav
#main
#footer
- include_cached
masthead.html
- site.logo
-
site.masthead site.title - site.subtitle
- site.data.navigation.main
- site.search
- Toggle menu
{{ content }}
- include_cached
search/search_form.html
- lunr
- algolia
- include_cached
- footer
- include
footer/custom.html
- site.data.ui-text[site.locale].follow_label
- site.footer.links
- site.atom_feed
- copyright
- include_cached
footer.html
- include
- include
scripts.html
- site.footer_scripts
- site.search
- include
analytics.html
- google-universal
- google-gtag
- custom
- include
/comments-providers/scripts.html
- diques
- discourse
- staticman
- staticman_v2
- utterances
- giscus
- custom
- site.after_footer_scripts
- head
splash
splash
继承自 default
,适用于 landing page。
Minimal Mistakes 官网首页便是使用了这个布局。
下面简单分析下源码。
主题源码的 docs/_pages/home.md
文件中,Front Matter 定义了一个 header
变量:
header:
overlay_color: "#5e616c"
overlay_image: /assets/images/mm-home-page-feature.jpg
actions:
- label: "<i class='fas fa-download'></i> Install now"
url: "/docs/quick-start-guide/"
这个变量在布局源码文件 _layouts/splash.html
中会被用到:
{% if page.header.overlay_color or page.header.overlay_image or page.header.image %}
{% include page__hero.html %}
{% elsif page.header.video.id and page.header.video.provider %}
{% include page__hero_video.html %}
{% endif %}
以上代码可以看出,这里既可以展示图片,也可以展示视频。
- 展示图片的
include
文件是page_hero.html
。
它用到的参数有:
- page.header.overlay_image => overlay_img_path
- page.header.overlay_color
- page.header.overlay_filter => overlay_filter
- gradient
- rgba
- page.header.image_description => image_description
- page.header.show_overlay_excerpt
- page.header.cta_url
- page.header.actions
- label
- url
- page.header.image
- page.header.caption
展示视频的 include
文件是 _includes/page__hero-video.html
:
{% assign video = page.header.video %}
{% include video id=video.id provider=video.provider danmaku=video.danmaku %}
这里进一步引用了 _include/video
这个模块:
{% capture video_id %}{{ include.id }}{% endcapture %}
{% capture video_provider %}{{ include.provider }}{% endcapture %}
{% capture video_danmaku %}{{ include.danmaku | default: 0 }}{% endcapture %}
{% capture video_src %}
{% case video_provider %}
{% when "vimeo" %}
https://player.vimeo.com/video/{{ video_id }}?dnt=true
{% when "youtube" %}
https://www.youtube-nocookie.com/embed/{{ video_id }}
{% when "google-drive" %}
https://drive.google.com/file/d/{{ video_id }}/preview
{% when "bilibili" %}
https://player.bilibili.com/player.html?bvid={{ video_id }}&page=1&as_wide=1&high_quality=1&danmaku={{ video_danmaku }}
{% endcase %}
{% endcapture %}
{% assign video_src = video_src | strip %}
<!-- Courtesy of embedresponsively.com -->
{% unless video_src == "" %}
<div class="responsive-video-container">
<iframe src="{{ video_src }}" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowfullscreen></iframe>
</div>
{% endunless %}
代码很简单,四个视频平台:
- vimeo
- youtube
- google-drive
- bilibili
继续分析 _layouts/splash.html
的剩余代码:
<div id="main" role="main">
<article class="splash" itemscope itemtype="https://schema.org/CreativeWork">
{% if page.title %}<meta itemprop="headline" content="{{ page.title | markdownify | strip_html | strip_newlines | escape_once }}">{% endif %}
{% if page.excerpt %}<meta itemprop="description" content="{{ page.excerpt | markdownify | strip_html | strip_newlines | escape_once }}">{% endif %}
{% if page.date %}<meta itemprop="datePublished" content="{{ page.date | date_to_xmlschema }}">{% endif %}
{% if page.last_modified_at %}<meta itemprop="dateModified" content="{{ page.last_modified_at | date_to_xmlschema }}">{% endif %}
<section class="page__content" itemprop="text">
{{ content }}
</section>
</article>
</div>
这部分代码只是给 article
元素中添加了四个 meta
:
Page Variable | Item Property |
---|---|
page.title | headline |
page.excerpt | description |
page.date | datePublished |
page.last_modified_at | dateModified |
对页面视觉没有作用,应该只是为了 SEO。
_includes/splash.html
本身的代码比较简单,我们再回过头去看使用了这个布局的文件 docs/_pages/home.md
。
Front Matter 中除了 header
变量,还有一个 feature_row
数组:
feature_row:
- image_path: /assets/images/mm-customizable-feature.png
alt: "customizable"
title: "Super customizable"
excerpt: "Everything from the menus, sidebars, comments, and more can be configured or set with YAML Front Matter."
url: "/docs/configuration/"
btn_class: "btn--primary"
btn_label: "Learn more"
可以看出,数组元素的属性挺多的,我们来看下它是如何被展示的。
在 docs/_pages/home.md
文件的末尾通过如下代码引入了 _includes/feature_row
文件:
{% include feature_row %}
我们来分析一下 _includes/feature_row
的源码:
{% if include.id %}
{% assign feature_row = page[include.id] %}
{% else %}
{% assign feature_row = page.feature_row %}
{% endif %}
第一个 assign
没看懂,问了一下 ChatGPT,回答如下:
In Jekyll, the code `{% assign feature_row = page[include.id] %}` is using the html templating language to assign a value to the `feature_row` variable.
Let's break down the code:
- `{% assign ... %}` is a html tag used for variable assignment.
- `feature_row` is the name of the variable being assigned.
- `page` is a special object in Jekyll that represents the current page being processed.
- `[include.id]` is accessing a value from the `page` object using square brackets and `include.id`.
In this case, `include.id` is likely a variable or parameter that holds a specific identifier or key. By using `page[include.id]`, the code is retrieving a value associated with that identifier from the `page` object and assigning it to the `feature_row` variable.
The purpose and usage of the `feature_row` variable depend on the specific context and how it is used later in the Jekyll template or layout.
也就是说,page
实际上是 Javascript 的一个 Object,page[include.id]
的意思是:取 page
对象中属性名为 include.id
的那个变量。
进一步说,我们可以在 docs/_pages/home.md
文件中定义一个名称为 features
的数组,然后通过如下代码传参给 _includes/feature_row
:
{% include feature_row id="features" %}
继续分析 _includes/feature_row
源码。
通过遍历 feature_row
,获取数组元素 f
:
{% for f in feature_row %}
在 for 循环体内,依次展示每个元素的属性:
1)image_path
{% if f.image_path %}
<div class="archive__item-teaser">
<img src="{{ f.image_path | relative_url }}"
alt="{% if f.alt %}{{ f.alt }}{% endif %}">
{% if f.image_caption %}
<span class="archive__item-caption">{{ f.image_caption | markdownify | remove: "<p>" | remove: "</p>" }}</span>
{% endif %}
</div>
{% endif %}
2)title
{% if f.title %}
<h2 class="archive__item-title">{{ f.title }}</h2>
{% endif %}
3)excerpt
{% if f.excerpt %}
<div class="archive__item-excerpt">
{{ f.excerpt | markdownify }}
</div>
{% endif %}
4)url
{% if f.url %}
<p><a href="{{ f.url | relative_url }}" class="btn {{ f.btn_class }}">{{ f.btn_label | default: site.data.ui-text[site.locale].more_label | default: "Learn More" }}</a></p>
{% endif %}
以上便是 _includes/feature_row
的源码,至此,_layouts/splash.html
也分析完了。
留下评论