- A+
有时大家可能会想为自己的博客增添一些色彩,但这种热情却常常因繁杂的配置步骤饱受消磨。CNBlogX是一套项目模板及脚手架的合集,用于快速搭建博客园的页面定制脚本开发环境。使用方式也非常简单,专注于自己的代码即可。让我们开始吧!
起步
- 请确保安装了Node.js,需要14.0或更新的版本。
通过以下命令基于CNBlogX模板创建一个叫做mytheme的项目:
npm init cnblogx mytheme
这样项目就建立完成了,让我们看看src/
下的代码:
非常简洁,不必关心CNBlogX在背后做了什么,我们只要在这三个文件中添加代码即可。如果有定制化的需要,可以查看README.md中的可配置项,接下来让我们把代码部署到博客园。
首次部署
- 若依赖安装很慢,可以考虑先配置npm淘宝镜像源。
首先给我们的项目安装依赖:
npm install
再编译我们的项目:
npm run build
然后将dist/
下的生成物复制到博客园-管理-设置的对应选项中:
- 将
custom.css
的内容复制到页面定制CSS代码中。 - 将
custom.html
的内容复制到页脚HTML代码中。
最后保存博客后台设置即可。
首次部署是必要的步骤,CNBlogX在构建时插入了开发者模式相关代码,接下来让我们体验一下。
开发者模式
- 请确保已经完成首次部署。
首先执行以下命令启动调试服务器,它将监视代码变化并将其应用到博客页面中:
npm run dev
然后浏览器中打开自己的博客,双击页脚的Copyright © 你的名字,进入开发者模式。
此后,若src/
下对应的文件发生了变化,效果将立即应用到博客页面中:
main.ejs
:支持HTML/EJS(兼容,后缀不可更改)。main.js
:支持Javascript/Typescript(更改后缀为.ts即可支持Typescript)。main.scss
:支持CSS/SCSS(兼容,后缀不可更改)。
- 再次双击页脚的Copyright © 你的名字,可退出开发者模式。
实践:编写常用组件
让我们写几个常用的博客组件,体验一下热模块替换带来的效率提升吧!后文中出现的组件可以在这个项目中找到。
评论区头像
第一版:低分辨率头像
博客园默认不显示评论区用户的头像,但在评论区中提供了每位用户的头像链接,我们可以通过浏览器的开发者工具看到:
所以我们只要通过Javascript新建一个img
标签,显示对应链接的头像即可:
嗯... 好像有一点糊,这个头像分辨率太低了。
第二版:回退式高清头像
经过观察,我们发现个人主页的头像链接和评论区的头像链接只存在一个目录的差异:
那我们可以先显示这个清晰的头像,如果获取失败了,再回退到低分辨率的头像。效果非常不错:
第三版:二级回退式头像
有时候会发现,有的用户根本没有上传头像,我们可以为他添加一个默认头像。那么我们的代码至多可能有两次回退,高清头像->普通头像->默认头像,像这样:
avatar = document.createElement('img'); avatar.src = get_hi_definition_avatar_src(addr); // 设置为高清头像。 avatar.addEventListener('error', () => { if (avatar.src != addr) { avatar.src = addr; // 回退到低分辨率头像。 } else { // 回退到默认头像。 avatar.parentNode.replaceChild(new_default_avatar(nick), avatar); } });
接下来让我们给用户画一个默认头像吧!
我们把衣服的位置镂空,再给头像元素设置不同的背景色,就可以为不同的用户显示不同颜色的头像了!完整代码可以看这里。
随笔目录
虽然Markdown允许通过[toc]
创建一个目录,但每次都要回到顶部查看目录并不方便,让我们也写一个目录吧。
栈:将数组转换为一棵树
目录通常是多级的,大标题包含小标题。我们可以用.querySelectorAll()
将文章中所有的标题收集到一个数组中,然后通过一个栈将线性的数组转化为一棵树,像这样:
article.querySelectorAll(SELECTOR_HEADERS).forEach(function (header) { const node = CreateTocNode(header); for (; ;) { if (level(node_stack_top().refel) < level(header)) { node_stack_top().add_toc_child(node); node_stack.push(node); break; } else { node_stack.pop(); } } });
下一个功能是让目录高亮当前小节的标题。
不妨将“当前小节的标题”定义为“离屏幕顶端最近的一个标题”。那么思路就清晰起来了:注册一个滚动事件,于事件发生时遍历所有的标题,找到getBoundingClientRect().top
的绝对值最小的一个,赋予其一个表示高亮的类名即可。
不过,这朴素的思路存在着一定的效率问题,下面我们将对它做出一些优化。
节流:防止滚动事件频繁触发
页面滚动时,滚动事件连续触发的频率非常高,可以用一个节流函数降低更新高亮目录的频率。同时为了避免节流函数导致丢失滚动快结束时的滚动事件,添加一个会被不断重置的setTimeout
即可。代码是这样的:
let timeout = null; regi_scroll(throttle(() => { update_current_node(); if (!timeout) { timeout = setTimeout(() => { update_current_node(); timeout = null; }, 400); } }, 200));
二分搜索:获取当前小节的标题
标题们的getBoundingClientRect().top
虽然有正有负,但只要是递增的,就可以应用二分搜索。通过二分搜索找到top
值在零附近的至多两个标题,再从中取top
的绝对值最小的一个即可。代码是这样的:
let left = 0; let right = node_list.length - 1; while (left + 1 < right) { const mid = Math.floor((left + right) / 2); if (distance(mid) <= 0) { left = mid; } else { right = mid; } }
实现目录大概用了一百行左右的代码,可以在这里查看。
常见问题
我的项目能够与其它代码共存吗?
CNBlogX默认会在进入开发者模式时清除用户的页面定制CSS代码,以免用户混淆部署版本与开发版本的样式,可以通过PRESERVE_CSS
编译选项阻止这个默认行为。
有离线的文档吗?
有的,看项目根目录下的README.md
。通过脚手架建立的新项目与Github上的模板只有包名不同。
开发者模式会影响他人阅读吗?
没有,开发者模式仅对启用它的单个浏览器有效。
- 要令博客定制代码对所有读者生效,需要一次新的部署。
为什么有时js的更改在刷新后才生效?
因为相关的模块存在未消除的副作用,参考热模块替换的文档。
可以单独生成.js
文件吗?
可以,通过STANDALONE_JS
编译选项生成单独的.js
文件。通过PUBLIC_PATH
编译选项可令.html
文件从指定的路径加载.js
文件。
可以自定义端口吗?
可以,通过PORT
编译选项配置端口,博客园中的代码与本地测试服务器的代码配置的端口应当相同。
没有页脚的博客如何进入开发者模式?
打开浏览器的控制台,执行以下代码即可:
cnblogx_development(true);
结语
希望大家喜欢,意见或建议也是很欢迎的,Issue或Pull Request就更欢迎了。