第 2 章 从 R Markdown 到 HTML 文档

因为 Markdown 技术在设计之初的输出格式就是 HTML,所以 HTML 文档不仅仅是最常用 R Markdown 输出格式,同时也拥有最丰富的功能。

前面提过,R Markdown 生成 HTML 文档的过程有一个中间步骤,就是 Markdown + HTML 模板。HTML 模板包括预定义的文档结构、 CSS 样式表和 JavaScript 动态网页功能等,所以最终渲染得到的 HTML 文档的一些功能可能依赖于特定 HTML 模板才能实现。

默认情况下,R Markdown 的 HTML 文档使用 rmarkdown::html_document 模板。除此之外, rmarkdown 之外的其它软件包也提供了各种不同类型的 HTML 模板文件(如 bookdown::html_document2pagedown::html_paged 等)。因此,在这一部分我们先从 R Markdown 到 html_document 文档开始。

要创建一个 html_document,只需要在 R Markdown 的开头加入 YAML 格式的元数据。

---
title: "文档标题"
author: "作者"
date: "创建时间"
output: html_document
---

在元数据中,还可以加入各种各样设置,从而改变文档的格式。

2.1 目录和标题

2.1.1 显示目录

在开头的元数据中,使用 toc: true 可以打开文档的目录。目录会从 HTML 的标题自动生成,目录的层级由 toc_depth 的值确定(默认为 3)。例如:

---
title: "标题"
output:
  html_document:
    toc: true
    toc_depth: 2
---

在上面的例子中,目录中将包含 HTML 文档中的一级标题和二级标题。

2.1.2 悬停目录

当把 toc_float 设为 true 的时候,目录会在侧边栏悬停。这样你就可以随时借助目录在一篇比较长的文档中跳转了。

---
title: "标题"
output:
  html_document:
    toc: true
    toc_float: true
---

通过改变 toc_float 的选项,可以控制侧边栏的折叠和动画。其中:

  • collapsed (默认为 true)控制文档第一次打开时目录是否被折叠。如果为 true 则只显示高级别的标题(二级标题及以上元素);
  • smooth_scroll (默认为 true)控制页面滚动时,标题是否会随之变化。
---
title: "标题"
output:
  html_document:
    toc: true
    toc_float:
      collapsed: false
      smooth_scroll: false
---

2.1.3 显示标题编号

使用 number_sections 可以在标题开头加上编号。一级标题编号为 “1 一级标题”,二级标题为“1.1 二级标题”。

---
title: "标题"
output:
  html_document:
    toc: true
    number_sections: true
---

2.1.4 使用标签页

在 HTML 文档中使用标签页非常简单,只需要在标题后附加 {.tabset} 标签即可。

## Quarterly Results {.tabset}

### By Product

(tab content)

### By Region

(tab content)

在此基础上,添加相关的额外标签还可以控制标签的样式和行为。其中:

  • .tabset-fade 将为标签切换过程加入淡入淡出的动画效果;
  • .tabset-pills 将为标签文字加上预设的 “pill”样式(图 2.1)。
标签的默认样式及“pill”样式

图 2.1: 标签的默认样式及“pill”样式

2.2 主题和样式

2.2.1 可选主题

为了满足颜值党的差异化需求,html_document 自带了多个不同的主题。这些主题来自于 Bootswatch。可用的主题名称包括 defaultceruleanjournalflatlydarklyreadablespacelabunitedcosmolumenpapersandstonesimplexyeti 等等。

主题使用 theme 参数来指定,例如:

---
title: "标题"
output:
  html_document:
    theme: united
---

如果设置为 theme: null,那么将不会应用任何主题,此时你可以通过指定自定义的 CSS 样式表来进行格式化。

---
title: "标题"
output:
  html_document:
    theme: null
    css: style.css
---

2.2.2 代码高亮

代码高亮也有可选的多种样式,包括 defaulttangopygmentskatemonchromeespressozenburnhaddockbreezedarktextmate 等。

在元数据中,使用 highlight 参数指定代码高亮样式。

---
title: "标题"
output:
  html_document:
    highlight: tango
---

跟上面的 theme 属性一样,highlight 也可以设为 null,这样的话代码将不显示高亮。

2.2.3 自定义样式表

不论有没有应用主题和代码高亮,你都可以使用 css 参数指定附加样式表。附加样式表中的定义可以被应用到特定元素上去。

---
title: "标题"
output:
  html_document:
    theme: null
    highlight: null
    css: styles.css
---

styley.css 中,假设我们定义了两个新样式如下:

#nextsteps {
   color: blue;
}

.emphasized {
   font-size: 1.2em;
}

则可以通过下列方式应用这个样式:

## 这里强调一下下一步 {#nextsteps .emphasized}

如果要应用多个 CSS 配置文件,可以这样写:

output:
  html_document:
    css: ["style-1.css", "style-2.css"]

2.2.4 通过 CSS 代码块定义样式

除了像上面那样导入一个预定义的样式表,你还可以直接在 R Markdown 中添加新的 CSS 定义。这种方法非常适合于个别定义。如果你的 CSS 代码很多,或者想要在其它 R Markdown 中使用同样的定义,可能放在自定义的样式表文件中更加合适(参见 2.2.3)。

---
title: "对代码块使用自定义的 CSS 样式"
output: html_document
---

首先定义一个新样式 `watch-out`

```{css, echo=FALSE}
.watch-out {
  background-color: lightpink;
  border: 3px solid red;
  font-weight: bold;
}
```

使用代码块属性 `class.source` 可以将新样式指定给这一区块。


```{r class.source="watch-out"}
mtcars[1:5, "mpg"]
```
一个具有浅红色背景和深红色边框的代码块

图 2.2: 一个具有浅红色背景和深红色边框的代码块

2.2.5 使用丰富多样的内置 CSS 样式

大多数时候,你并不需要自己定义 CSS 样式,因为 HTML 内置的主题中已经包含了丰富且高级的内置样式。

默认情况下,R Markdown 输出的 HTML 文档中,已经内嵌了 Bootstrap 框架,因此可以使用一系列预定义的 CSS 样式。其中,可用的背景样式就包括 "bg-primary""bg-success""bg-info""bg-warning""bg-danger" 等。

只需要像上面那样,在代码块属性中使用 class.source 标签,就可以应用这些预定义的 CSS 样式。

---
title: 改变代码块的样式
output: html_document
---

当你对一个数据框取子集的时候,其输出跟选取的列的数目有关。
如果选取了 2 个以上的列,则输出仍然是一个数据库;
如果选取了 1 个列,则输出的结果将会是一个向量。
因此,我们对这个操作应用了 `bg-danger``bg-warning` 的样式。


```{r class.source="bg-danger", class.output="bg-warning"}
mtcars[1:5, "mpg"]
```

为了确保始终能够得到数据框,则需要添加 `drop = FALSE` 参数。
为了显示这个结果,我们应用了 `bg-success` 样式。

```{r df-drop-ok, class.source="bg-success"}
mtcars[1:5, "mpg", drop = FALSE]
```

以上内容生成 HTML 文档后的样子如下图所示:

在代码块上应用内置 CSS 样式

图 2.3: 在代码块上应用内置 CSS 样式

2.2.6 代码折叠

Knitr 的代码块参数 echo = TRUE 时,R 代码将会输出到最终生成的文档中。 如果你不需要显示源代码,可以直接设为 echo = FALSE。 如果你既想要保留代码但又让其默认不显示, 则可以通过 code_folding: hide 参数来实现。

---
title: "Habits"
output:
  html_document:
    code_folding: hide
---

code_folding: hide 将折叠所有的代码块,用户可以通过点击来查看折叠的代码。如果想让部分代码块在一开始就显示,则可以在代码块选项中使用 class.source = 'fold-show'

---
title: "代码的折叠和显示"
output:
  html_document:
    code_folding: hide
---

```{r}
1  # 折叠的
```

```{r class.source = 'fold-show'}
2  # 显示的
```

```{r}
3  # 还是折叠的
```
代码块的折叠和显示

图 2.4: 代码块的折叠和显示

这种操作也可以反向进行,如下配置即可:

---
output:
  html_document:
    code_folding: show
---

```{r}
1  # 代码默认是显示的
```

```{r class.source = 'fold-hide'}
2  # 这一块代码将被折叠
```

如图 2.4 所示,在页面和每一个代码块的右上方有一个按钮。页面右上方的按钮可以控制全部代码块的显示和隐藏,代码块右上方的按钮则可以控制对应代码块的显示和隐藏。

2.2.7 设置代码块内容可滚动

如果你想限制代码块的高度,特别是代码执行过程中输出内容的高度,还可以从相应内容的 CSS 样式上着手,即通过 class-outputclass-source 来定义内容的高度。

下面是一个例子:

---
title: 可以滚动的代码和输出
output: html_document
---

```{css, echo=FALSE}
pre {
  max-height: 300px;
  overflow-y: auto;
}

pre[class] {
  max-height: 100px;
}
```

我们首先定义了上述 CSS 规则,用来限制代码块的高度。

```{r}
# 假如这里有 N 多行的代码
if (1 + 1 == 2) {
  # 然后再打印一个非常长的数据
  print(mtcars)
  # 如果不够长的话,再加上这行注释可能就够了
}
```

现在添加一个新的 CSS 类 `scroll-100`,以用来限制代码块的输出高度为 100 像素。
然后,将这个类赋值给代码块中的 `class.output` 参数。


```{css, echo=FALSE}
.scroll-100 {
  max-height: 100px;
  overflow-y: auto;
  background-color: inherit;
}
```

```{r, class.output="scroll-100"}
print(mtcars)
```

因为代码块是位于 <pre class="sourcecode"> 标签内的,所以 pre[class] 操作符将限制代码块的高度为不超过 100 像素。 而将 class.output 设为 scroll-100 则限制输出部分的高度为不超过 100 像素。

最终效果如下图所示:

可以滚动的代码块

图 2.5: 可以滚动的代码块

2.3 图片和数据框

2.3.1 设置图片的属性

下列参数可以调整生成的 HTML 文档中图片的属性:

  • fig_widthfig_height 指定图片显示时的宽和高(默认为 7 × 5,单位英寸)。
  • fig_retina 开启视网膜屏幕优化(默认为 2,设为 null 时关闭优化)。
  • fig_caption 控制是否渲染图注。
  • dev 设置图片输出设备,默认为 png。你可以设置多个图片输出设备。
---
title: "标题"
output:
  html_document:
    fig_width: 7
    fig_height: 6
    fig_caption: true
    dev: c("png","pdf")
---

在代码框属性中,你仍然可以通过 fig.heightfig.widthfig.asp 等参数来指定生成图片的高度、宽度和宽高比。

2.3.2 插入外源图片

虽然在 R Markdown 中可以使用 Markdown 语法和 HTML 语法插入图片,但是我们推荐使用 knitr::include_graphics() 来插入外源图片。例如:

```{r}
knitr::include_graphics("https://r-project.org/Rlogo.png")
```

使用这种方法插入外源图片,可以方便的添加图注(使用 fig.cap 参数),以及设置图片的宽高等属性。

插入外源图片的推荐方式

图 2.6: 插入外源图片的推荐方式

2.3.3 输出数据框

通过 df_print 参数,你可以调整数据框的输出格式。可用的参数如表 2.1 所示。

表 2.1: html_document 可以被设置的 df_print 参数及其对应的含义。
取值 说明
default 调用 print.data.frame 的通用方法
kable 使用 knitr::kable 函数
tibble 使用 tibble::print.tbl_df 函数
paged 使用 rmarkdown::paged_table 来创建一个分页的表格

2.3.4 分页打印数据框时的附加参数

若把 df_print 参数设为 paged,数据框将支持行列的分页,效果如图 2.7 所示。

---
title: "Motor Trend Car Road Tests"
output:
  html_document:
    df_print: paged
---

```{r}
mtcars
```
HTML文档中分页显示的数据框

图 2.7: HTML文档中分页显示的数据框

2.2 列出了这种情况下可以使用的附加参数。

表 2.2: 分页 HTML 表格的附加可用参数。
参数名 说明
max.print 显示的总行数
rows.print 一页显示的行数
cols.print 一页显示的列数
cols.min.print 最少显示几列
pages.print 下方显示几个页面导航的链接
paged.print 设为 FALSE 则不输出分页的表格
rownames.print 设为 FALSE 时不显示行的名称

这些参数可以在代码块中使用。

```{r cols.print=3, rows.print=3}
mtcars
```

2.4 组件和内容

2.4.1 理解 HTML 文档的依赖关系

前面已经提过,R Markdown 输出的 HTML 文档时,依赖于软件包提供的 HTML 模板。 实际上,HTML 文档的样式和功能很大程度上依赖于一些 CSS 样式和 JavaScript 库的支持。 这里面包括 BootstrapJQuery 等优秀的开源项目。

默认情况下,R Markdown 输出的 HTML 文档是一个单一的 .html 文件。这是由 self_contained: true 控制的。.html 文件中,会使用 data: 存储包括 JavaScript、CSS、图片和视频在内的全部资料。这样的一个封装,使得用户可以像分享 PDF 或 Word 文档那样分享文件,同时享受超链接、动态效果等一系列 HTML 特性。

如果你指定 self_contained: false,那么 HTML 文档将会将自身的依赖文件单独存放。

---
title: "Habits"
output:
  html_document:
    self_contained: false
---

默认情况下,在 .html 文件同一目录会生成同名的 _files 文件夹,存放相应的依赖文件。下面是默认情况下一份 HTML 文档所包含的依赖文件。

+---anchor-sections-1.0
+---bootstrap-3.3.5
|   +---css
|   |   \---fonts
|   +---fonts
|   +---js
|   \---shim
+---header-attrs-2.5
+---highlightjs-9.12.0
+---jquery-1.11.3
\---navigation-1.1

库文件夹的位置可以由 lib_dir: xxx 指定,例如:

---
title: "Habits"
output:
  html_document:
    self_contained: false
    lib_dir: libs
---

依赖文件的内容会随配置变化,例如当在 YAML 配置中加入 df_print: paged 之后,依赖文件中会多一个 pagedtable-1.1 的子文件夹出来。

显然,存放依赖的子文件夹依据 库名 + 版本号 的规则命名。要弄清楚库文件的全部特性,可能要对库本身有相当的理解才行。而 R Markdown 则是把最常用的功能整合提供给了我们。

当文档的内容比较少,同时又有多个类似的文档的时候,库文件所占的存储空间可能比你自己编写的内容还要大得多。这种情况下,将库文件统一存储在指定的 libs 文件夹,可以实现库文件公用。

另外,有些库文件不常用,或者文件太大,还可以通过库文件的服务器调用。例如下面要讲的 MathJax 库。

2.4.2 MathJax 库的配置

HTML 文档需要 MathJax 脚本来渲染 Latex 和 MathML 公式,调用 MathJax 的方式则可以通过 mathjax 参数来调整。

  • "default":默认配置,会通过 HTTPS 链接从 RStudio 的 CDN 网络服务器上调用;

  • "local":与 self_contained: false 联合使用时,会将 MathJax 库文件保存在本地目录中;

  • 设置一个 URL 链接,指向可用的 MathJax 库文件地址;

  • null:完全不使用 MathJax。

例如,使用 MathJax 的本地拷贝可以如下配置:

---
title: "Habits"
output:
  html_document:
    mathjax: local
    self_contained: false
---

为 MathJax 配置一个新的可用来源。

---
title: "Habits"
output:
  html_document:
    mathjax: "http://example.com/MathJax.js"
---

不使用 MathJax。

---
title: "Habits"
output:
  html_document:
    mathjax: null
---

2.4.3 是否保留 Markdown

knitr 处理 R Markdown 文件时,会先生成一个 Markdown 文件(*.md),随后再由 Pandoc 转换成 HTML 文档。如果需要保留这个 Markdown 文件,可以使用 keep_md 选项。

---
title: "Habits"
output:
  html_document:
    keep_md: true
---

2.4.4 使用自定义的 HTML 模板

使用 template 选项,可以配置 Pandoc 转换时使用的模板。

---
title: "Habits"
output:
  html_document:
    template: another_template.html
---

Pandoc 模板遵循特定的格式,有关的进一步信息可以在 Pandoc 模板 页面获得。

下面是一个 HTML 模板的示例:

<html>
  <head>
    <title>$title$</title>
    $for(css)$
    <link rel="stylesheet" href="$css$" type="text/css" />
    $endfor$
  </head>
  <body>
  $body$
  </body>
</html>

这其中包括一些变量,如 $title$$body$ 等。这些变量由 Pandoc 定义,完整的变量列表参见这里

这样的 HTML 模板使得高度定制化的输出成为可能。例如,你可以在 <head> 区域加入任意的 CSS 样式, JavaScript 代码,以及其它的开源库。 另外,还可以定义一些新变量来控制文档的格式化。例如,定义一个布尔值 draft 来确定文档是一个草稿还是最终版本。

<head>
<style type="text/css">
.logo {
  float: right;
}
</style>
</head>

<body>
<div class="logo">
$if(draft)$
<!-- use draft.png to show that this is a draft -->
<img src="images/draft.png" alt="Draft mode" />
$else$
<!-- insert the formal logo if this is final -->
<img src="images/logo.png" alt="Final version" />
$endif$
</div>

$body$
</body>

draft 在 YAML 元数据中赋值。

---
title: "An Important Report"
draft: true
output: 
  html_document:
    template: my-template.html
---

说明rmarkdown 软件包默认使用自带的 HTML 模板,一些方面与 Pandoc 默认的模板存在差异。如果有 template: null 的话,则 Pandoc 的模板会被使用。

2.4.5 包含其它文件

使用 includes 选项,可以在 HTML 文档的不同位置嵌入其它的 HTML 格式内容。可选的位置包括在 HTML 文档的 headerbody 前/后等。

---
title: "Habits"
output:
  html_document:
    includes:
      in_header: header.html
      before_body: doc_prefix.html
      after_body: doc_suffix.html
---

这种方式可以很方便的为文档加入一些第三方功能和公用的元件。例如在 in_header 中导入预定义的 CSS 样式表和 Javascript 脚本,在 before_body 中加入导航栏,在 after_body 中加入一个底栏等。

下面的例子中,即添加了一个简单的底栏。将其中内容保存到一个 HTML 文件中,放在 after_body 后面即可。

<div class="footer">Copyright &copy; R Markdown 指南 2021</div>

除了以上几种常用的位置,你还可以在任意地方插入 HTML 内容。实现这一功能的途径至少有两种。

一是使用 htmltools::includeHTML() 方法:

```{r, echo=FALSE, results='asis'}
htmltools::includeHTML('file.html')
```

二是使用 xfun::file_string() 方法:

```{r, echo=FALSE, results='asis'}
xfun::file_string('file.html')
```

需要注意的是,导入的 HTML 文件必须是 HTML 片段,而不能是一个完整的 HTML 文档。完整的 HTML 文件中有 <html> 标签,解析时会造成错误。比如下面就是一个无效的例子:

<html>
  <head>  </head>

  <body>
  Parent HTML file.
  
  <!-- htmltools::includeHTML() below -->
    <html>
      <head>  </head>
      <body>
      Child HTML file.
      </body>
    </html>
  <!-- included above -->

  </body>
</html>

2.4.6 生成 HTML 片段

HTML 片段是一个不完整的 HTML 文件,这种片段适合用于嵌入其它的网页或者内容管理系统(如博客)中。HTML 片段也不自带主题和代码高亮,而通过继承的方式使用其嵌入文件或系统的设置。这样的 HTML 片段,也非常适合用于包含在其它文件中(参见 2.4.5)。

---
output: html_fragment
---

下面是一个 HTML 片段的例子:




<p>HTML 片段可以有标题和代码区域,但是不包含任何 CSS 定义。</p>
<pre class="r"><code>head(mtcars)</code></pre>
<pre><code>##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1</code></pre>

将这些内容保存为一个 HTML 文件,导入此处,则效果如下所示:

HTML 片段可以有标题和代码区域,但是不包含任何 CSS 定义。

head(mtcars)
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

HTML 片段的内容到此结束。

2.4.7 使用自定义的浏览器图标

通过在 in_header 部位导入 HTML 内容,可以为 HTML 文档设定一个自定义的浏览器图标。

将下面的内容保存到一个名为 header.html 的文档中。

<link rel="shortcut icon" href="{path to favicon file}" />

然后通过 includes 导入文件的内容,则可以改变浏览当前文档时的浏览器图标。

output:
  html_document:
    includes:
      in_header: header.html

2.4.8 共用 YAML 配置文件

当前目录中的 _output.yml 文件是一个配置文件,其中的设置可以被目录下所有的 R Markdown 文档公用。需要注意的是,该文件中的内容不需要使用---output 标签。

如下所示:

html_document:
  self_contained: false
  theme: united
  highlight: textmate

而不应该写成下面这种样子:

---
output:
  html_document:
    self_contained: false
    theme: united
    highlight: textmate
---

2.4.9 嵌入 Rmd 源文件

当你分享一个 R Markdown 生成的 HTML 文档给他人的时候,对方可能还想找你索取 .Rmd 源文件。在头文件中配置 code_download 参数可以在 HTML 文档中嵌入源文件。

output:
  html_document:
    code_download: true

打开 code_download 选项后,页面中会出现一个下载按钮,点击下载按钮即可获得源文件。

2.4.10 嵌入其它文件

嵌入 .Rmd 源文件可能还不足以重现 R Markdown 中结果,通常还会需要原始数据等其它内容。要将这些内容嵌入 HTML 文档中,也是很容易实现的。

这一功能通过 xfun 软件包(R-xfun?) 实现,依赖于 htmltoolsmime 软件包。要使用这一功能,首先确保这两个软件包可用。

xfun::pkg_load2(c('htmltools', 'mime'))

现在,就可以随意嵌入各种文件了。

```{r echo=FALSE}
# 不但可以嵌入一个文件
xfun::embed_file('source.Rmd')

# 还可以嵌入多个文件
xfun::embed_files(c('source.Rmd', 'data.csv'))

# 甚至一个目录也不在话下
xfun::embed_dir('data/', text = 'Download full data')
```

不仅如此,你还可以编程技巧嵌入所需的文件。

# 嵌入当前目录下所有的 Rmd 和 csv 文件
xfun::embed_files(list.files('.', '[.](Rmd|csv)$'))

说明:当嵌入多个文件时,其工作原理是:首先将这些文件压缩成 Zip 格式,然后将 Zip 文件嵌入到 HTML 文档中。