背景

在之前我的文章中,概括地介绍了修改 R Markdown模板的思路。本文希望基于一个创作 R Markdown文档的例子 (在电脑上看效果比较好),对常用的报告模板 Prettydoc 进行分析。读者可以根据自己的程度和需求,选择是停留在对模板的颜色、字体大小的程度,或是往前走一点——自己发明创造,进行阅读。

改造原理及意义

意义

其实自定义一份模板起初没什么特殊含义,但最近深感创作侵权违法成本太低,举证困难,却苦于没有方法。自定义模板可以设置水印,可以设置选择失效,防止直接复制粘贴。虽然对于会写网页的人来说,这样的设防约等于没设,但是增加一些抄袭成本还是挺有意义的。

初步尝试的失败

最起初的想法是自己写一个模板,然后不断调 CSS 和 HTML 的位置和情况,甚至可以不依托 Prettydoc 定义出自己的模板。但毕竟自己不是前端工程师,没有如此深厚的前端功力。之后的想法是利用已经写好的博客模板,直接迁移到 Prettydoc 上,我自身的博客在 Jekyll 的基础上进行开发,也改造过一些模板,于是我从 Jekyll 模板 http://jekyllthemes.org/ 网站上试图下载一些下来,并进行修改。最后下载下来的模板由于其自身依赖的复杂性,十改九失败。即使改造成功,也存在着没调整好标签定位所导致网页变形的情况。 不得不承认,在自己的前端水平限制下,无法在较短的时间内完成如此的工程。最后还是退而求其次,从原有的 Prettydoc 模板出发,进行改动。即使是 Prettydoc 的模板,起初也是 github 中各种高手实现的,邱怡轩做的 Prettydoc 也是将这些模板综合起来,形成了一套完整的生成报告的流程。

改造所需要的知识

改造过程中最不需要的就是 R 的知识,这可能是很少有人愿意去改的原因,但其实整个过程不难。 一般的静态网页可以分为三个部分,分别用三种语言完成。这三个部分可以对应于人骨骼、皮囊和以血液为代表的动力系统。骨骼对应的是 HTML 所写的文本,HTML的知识可以自行搜索,概括地说就是文本及其定位。皮囊对应的是 CSS 给网页带来的层层渲染,最简单的例如颜色、字体、位置等。血液之类的动力其来源是 JavaScript ,其作用是时整个网页“动”起来,或者是可以相应某些动作。

回到Prettydoc生成的文档,自定义的第一步还是需要从 R Markdown 的链接生成命令开始,下面截取了一小部分:

/.../pandoc/pandoc ... temp.utf8.md ... --output temp.html ... --template /.../prettydoc/resources/templates/cayman.html ... --css temp_files/style.css 

这里解读编译命令需要参考上一篇文章。为了照顾读者的关注点,我将不重要的部分都用三个点代替。

而模板所在的位置,就在上面 prettydoc 的 templates 文件夹下,即--template 后面的路径。

找到路径后发现往上翻一下,有四个文件夹,css、fonts、images、templates,注意没有 js 的文件夹,但并不代表无法将js链接进模板中。最朴素的想法是,将 js 代码直接写到 html 里,虽然这并不是一种很好的编程风格。可以先从原理出发,把这些问题一一记下,之后再一一解决。

  • 问题一 如何单独写自定义的js并将其引用?
  • 问题二 到底prettydoc是如何将这些文件链接起来形成文档的?

先解决第二个问题,观察源 R Markdown文件所在的目录,然后点击 Knitr 生成 HTML 可以发现,生成时会先产生一个临时文件夹和一个临时的Markdown文件,所以原理其实也就是把需要的文件都链接起来,然后利用pandoc把该转变的语言都转变了,再删除临时文件即可。整个事件都在底层完成了。链接的是什么文件呢?再回到模板的目录,不难发现,也就五个模板,对应五种主题。由于网页一定需要 CSS 文件,所以再看 CSS 文件夹,除了字体文件夹,其余的十个CSS文件分别对应五种主题,每个主题都有一个开发版 CSS 和压缩版 CSS。

根据面向对象的思想(类似于多态),主题不可能被写死,否则不利于 Prettydoc 作者哪天心血来潮的时候,想自己加个主题还要重新编译自己的 R 包。基于这样的猜想,自定义主题就是复制一下他写的 HTML 文件和 CSS 文件,按照一样的形式,一对二,并将名字改成一致的,即可“抄袭”原有的模板,开始自定义主题。 这里可以说是整个改造 Prettydoc 包能推进下去的核心原理。

这时候,再仔细把每个主题的HTML打开,很容易发现,其中的内容是几乎一样的。这给改造节约了很多时间。改来改去,用的标签都是差不多且在HTML中已经写好,会变的无非就是CSS中的样式而已。CSS变来变去,骨骼是不变的,无非是对应标题的位置放在左边还是右边的区别,配色好不好看,字体好不好看的区别而已。

但这里的改造不一定就遵循原作的风格,骨骼一样可以变,也就是可以增加新的标签去记录信息,例如可以在左上角建立一个统计功能,例子中的左上角就是对文档中漂浮点的统计。

正式改造

下面以 Cayman 为例,改造一下模板。选择改这个模板的原因很朴素,Cayman的 CSS 代码最短,改造起来最容易,其造型也比较容易为人接受。

改造模板的第一步是将原有的 HTML文件和 CSS 文件都复制一下,然后重新命名为自己喜欢的名字。改完名字后,做一个测试,将 yaml 中的 theme 后面的主题换成自己改的名字,之后生成文档(做测试的时候最好不要复制修改命名 Cayman ,因为默认找不到主题文件时,会使用 Cayman 作为主题,这样就不确定复制是否有效)。

假定读者都找到了模板的位置,并进行了复制和改名,生成也成功了,下面来了第三个问题。

  • 问题三 CSS的代码还是茫茫多,如何才能改?

其实这个问题对会使用 CSS 的人来说应该不是问题,但毕竟不是人人都会,这里可以简单提一下,详细理解还是需要参考讲解 CSS 的网站,本文无法穷尽。一般标签有两种方式定位,一种是 class,一种是 id。在 CSS 中定义 class 的属性,会对所有带同一个class名字的标签都进行渲染。id 是唯一标识,仅会渲染唯一的带有那个 id 的标签。也就是说 CSS 中一个个的大括号中的就是定义着某个类或者某个 id 的属性,找到想改的标签下对应的属性做修改即可。

不写网页的人对标签位置的寻找也许不太清楚。寻找内容所对应的标签其实很简单。利用浏览器右键检查,就会打开开发者工具,自动定位到鼠标所在位置的标签名字。

一般正常人想修改的就是背景颜色和字体颜色而已。检查后可以根据开发者工具中 style 颜色的具体情况,返回到 CSS 文件中,修改对应的颜色即可。

  • 问题四 有两个CSS文件,到底修改哪一个?

其实两个CSS文件的内容是一致的,但实际上 R Markdown用的是\*.min.css。原因也很简单,\*.css的文件是正常的开发文件,\*.min.css是利用 CSS 工具手动压缩后的文件。因为后者删掉了很多冗余字符,可以使生成的文档更小。

下面就 Cayman 简单修改一下:

最醒目的颜色是 Cayman 最上面的一长条的颜色框,其颜色是渐变的,寻找一下渐变并带有 head 的属性,可以找到:

.page-header {
  color: #fff;
  text-align: center;
  background-color: #159957;
  background-image: linear-gradient(120deg, #155799, #159957);
  padding: 1.5rem 2rem;
}

渐变色显然就是background-image这里的颜色。不想随便试颜色最好的方法是上网去找喜欢的配色方案,之后可以得到以下 Head:

简单调头部的背景色

除此以外,还可以修改渐变颜色的方向,也可以将背景换成图片。但这些都仅仅是限制于表面的修改,最多能让文档的展示从表面上看上去与众不同。更深层次的修改,也就是添加 JavaScript 才是让报告更上一个台阶的关键。也是下面要谈的重点。

让文档动起来

要引入 JavaScript 就必须先回答问题一,最朴素的想法是在模板外的目录下创建 js 文件夹,按照 CSS 文件的引用情况,有可能会自动将 js 文件夹复制过去,但实际上,这样的想法还是太天真。只要在生成 temp_files 文件夹时,将其拖拽出原来的文件夹(只有在 R Markdown将临时文件删除前拖出去才能看),就可以发现,在生成临时文件时,并没有生成 js 文件夹,自然直接放 js 文件夹不可行。转念再想,至少复制了 images 文件夹下的所有东西,那么复制 images 文件夹不也可以吗?基于这个朴素的想法和路径的尝试,在将 script.js 文件放进 images 文件夹后,在 HTML 中的引用路径就可以如下:

<script  src="temp_files/images/script.js"></script>

引用其实只要找到路径即可,若是临时的,特殊的不属于特定模板的 js 文件,也可以用更简单的方法引用,即在 R Markdown 文件所在目录下放置 script.js 文件,那么引用的路径就可以是:

<script  src="script.js"></script>

或者是

<script  src="./script.js"></script>

这些都是比较取巧的解决方法,更为合理的解决方案还是应该从制作这个包的过程中,就将这个需求考虑进去。

起初可以加 JavaScript 之后,我停留在比较低级的阶段——无非加点水印之类的,知道后来在读 Graph Embedding 代码时,发现了一个有意思的博客https://zhuo931077127.github.io/。其背景的粒子效果特别炫酷,于是有了据为己有的贪念。上网一搜粒子动画,就找到了一个制作此类动画的 JavaScript 库——https://github.com/VincentGarreau/particles.js

之后的制作过程也就比较简单了,在https://codepen.io/VincentGarreau/pen/pnlso上,先编辑好代码,然后将 HTML 、 CSS 、 JavaScript 的部分加到相应的部分中。例如 HTML 部分:

<div id="particles-js">
<section class="main-content" id="scro">
$body$
<span class="js-count-particles">--</span>
</section>
</div>

其中,JavaScript 部分直接生成 script.js 文件,并在 HTML 中添加 particle.min.js 等的引用。 CSS 部分则稍微需要一些调整,这也是困扰我两天的问题。

  • 问题五 canvas 和 div 同时存在时,会“井水不犯河水”,如何做到github上那位用户的效果?
  • 问题六 生成的canvas太短,仅够遮住第一页,但section标签里的内容太长怎么办?

问题五很好解决,将其中的一个position改成固定即可。问题六确实困扰了我很久,但在反复观察别人的博客后,领悟到了一句话:风动还是云动?运动是相对的。起初我想的都是怎么让 canvas 不断填充div,哪怕复制重复也可以,但效果并不好。其实博客中,根本是 canvas 没有动,动的是div标签。那么解决方法就很简单了,即给section加上一个新id(参看上一个代码段,section有一个idscro),并在 CSS 中增加:

#scro{
overflow-y:scroll
}
::-webkit-scrollbar {
display: none;
}

其作用是让这个 div 中的内容滚动播放。但由于 canvas 的位置是 absolute ,canvas就不会随滚轮向下走。后面的 display: none;是不显示滚轮。

最后可以得到的效果可以参看例子

此外,利用canvas写报告还是存在一个硬伤,在打印pdf时,canvas是不能随着文档的展开而展开的,这也是需要不断探索解决方案的,动静之间,不可兼得。

但用 JavaScript 添加动画并不一定要 canvas。这里是想暗示,所有用 JavaScript 可以实现的效果都可以加到文档里。当把这些文档抽离出来,形成类似电子相册的东西时,就得到了博客。改造 Prettydoc 的一切技巧和解决思路都可以原模原样搬到自己的博客上。站在我的肩膀上,希望读者能创造出更精彩的模板,并跳出 Prettydoc 的框架。我可以假想有这样一个 R 包,以拖拽、绘画的方式在网页服务器上定制自己的模板,再通过命令反过来将生成的模板添加到这个 R 包中。那么毫无疑问 R Markdown 会绽放出更多不寻常的可能性。

小结

有时候并不是能力做不到,在互联网的帮助下,基本有问题就能找到方法解决,只是方法的好坏而已。探索时最大的难题是没有想法,失去了灵感,做的工作也就是如工匠般的简单搬运。直接改 Prettydoc 的文件有违我的本意,虽然这是最容易自定义的方法。其实修改一个包没有什么难的,难的是能搭建起这个框架,让我能在其中修改。整个修改过程只是心血来潮地简单尝试,还有很多可以尝试的空间。JavaScript 可以加的动画无穷无尽,但有时我不得不反省,脱离了文档内容,而一味追求外观的酷炫是否南辕北辙?添加这样的动画,或许没有一开始时,简单地在文档中加水印和加反复制粘贴代码来的有意义。

参考资料

发表/查看评论