声明:我这里魔改的 Butterfly 版本是 5.6.1,其他版本可能存在偏差,请注意甄别!
第一节:自定义样式 在魔改主题的时候,避免不了的就是修改样式(修改主题源码或者新增自己的样式),修改源码就不说了,那么该怎么增加自己的样式文件呢?在 Butterfly 主题中,增加自定义样式有两种方式,下面分别介绍一下!
1.01:样式注入 样式注入是 Butterfly 提供和推荐的一种方式,主题的配置文件中,暴露出一个配置项inject:
1 2 3 4 5 6 7 inject: head: bottom:
首先,在主题根目录(博客根目录也可以)下的/source/css/文件夹中创建自己的样式文件,比如custom.styl;
然后,在主题配置文件的inject中进行如下配置:
这样一来,当执行hexo generate命令后,会在博客根目录下生成/css/custom.css文件,并被引入到项目中!
1.02:源码引入 使用样式注入时,最终会产生额外的样式文件(除原本的index.css以外),而下面这个做法则不会产生多余的样式文件,会将文件中自定义的样式代码合并到index.css文件中!
首先,最终生成的index.css文件是由主题根目录下的/source/css/index.styl文件生成的,在最后增加:
然后在主题根目录下的/source/css/中创建一个名为_custom的文件夹,并在其中新建文件custom.styl,这样一来,不管以后在该文件夹中添加任意样式文件,都会被引入到/source/css/index.styl文件中,并且编译生成的样式代码会放在网站 index.css 的最后。
1.03:我的选择 对于这两种方式,根本的区别在于前者会生成多个样式文件,后者只会生成一个。如果不考虑主题升级,我强烈建议使用第二种方式,因为这样的话,浏览器只有一次 CSS HTTP 请求。后面所有的魔改,如果需要自定义样式,我都会用源码引入的方式将其引入到我的博客项目中!
第二节:主题的颜色 作为一个深度强迫症,对于主题颜色这种能明显体现主题特点的部分,毫无疑问是要修改的!Butterfly 主题有深色模式和浅色模式两种,先来分析一下主题原本的颜色实现逻辑!
2.01:默认逻辑 首先,在 /source/css/var.styl 文件中定义颜色变量,比如:
1 2 $body-bg = $font-black =
浅色模式的颜色取自文件 /source/css/_global/index.styl 中的:root,比如:
1 2 3 4 5 :root --global-bg : $body -bg --font-color : $font -black --hr-border : lighten ($theme -hr-color , 50% ) --hr-before-color : lighten ($theme -hr-color , 30% )
深色模式的颜色取自文件 /source/css/_mode/darkmode.styl 中的data-theme='dark':
1 2 3 4 5 6 if hexo-config ('darkmode.enable' ) || hexo-config ('display_mode' ) == 'dark' [data-theme='dark' ] --global-bg : darken (#121212 , 2 ) --font-color : alpha (#FFFFFF , .7 ) --hr-border : alpha (#FFFFFF , .4 ) --hr-before-color : alpha (#FFFFFF , .7 )
最后,在主题 UI 相应的.styl文件中,在设置颜色时通过var()来适配不同模式的颜色值,如var(--global-bg)。
2.02:我的思路 我的思路如下:
将自定义主题颜色(深/浅)全部定义到 themes/butterfly/source/css/var.styl 中,然后在上述两个文件中通过变量名来获取不同的颜色值;
对于适配深浅模式的颜色,先在上述两个文件中定义同名的全局变量(形如--monkey-card-bg),然后在对应的样式代码中使用var来获取(如var(--monkey-card-bg));
这样一来,后面要修改某个颜色的时候,就只需要修改 var.styl 这一个文件就可以了。
至于细节的实现,我这里就不详细记载了(修改的地方比较多,不好记录)。
第三节:设置一图流 3.01:何为一图流 Butterfly 首页由导航栏、顶部图、网站背景、页脚这四部分组成,经过前面的设置:
网站默认的头图default_top_img: /img/top_img/whale.webp;
网站背景background: /img/background/whale.webp;
页脚透明footer_img: transparent;
头图和页脚的遮蔽色mask->header: true和mask->footer: true:
现在的样式如下:
所谓一图流,就是将整个网站都设置为一张图,具体就是:
去掉网站默认的头图;
页脚透明带有遮罩(上图中的效果,已经不需要修改了);
3.02:设置一图流 第一步:在主题设置文件中,设置网站背景图片(之前已经设置过,保持不变)
1 2 3 4 background: /img/whale.webp
第二步:在主题配置文件中,取消默认顶部图的配置(之前设置过的,现在取消)
第三步:在主题配置文件中,设置透明+遮罩(之前已经设置过,保持不变)
1 2 footer_img: transparent
1 2 3 mask: header: true footer: true
第四步:修改主题样式,取消顶部图背景颜色
主题提供的配置:
disable_top_img:顶部图全局开关,默认 false。若设置为 true 的话,页面顶部图完全消失,顶部图原本应该占的位置也完全消失,看起来不美观;
default_top_img:默认的顶部图,默认留空。若配置的话,当没有其他配置干扰的情况下,所有页面的顶部图都是这个;
index_img、archive_img、tag_img、tag_per_img、category_img、category_per_img,这几个配置项默认留空,若配置的话,对应页面的顶部图就展示为配置的图片;
对于菜单页,如果在 Front-matter 中设置了top_img,顶部图显示为配置的图片,若没有,显示主题默认蓝色;
对于文章页,如果在 Front-matter 中设置top_img或cover,顶部图显示为配置的图片(前者优先级高),否则显示主题默认蓝色;
可见,只要取消顶部图位置加载系统默认蓝色,并且不设置top_img和cover,那就可以实现顶部图透明了。打开主题根目录下的 /source/css/_layout/head.styl 文件,找到下面这段,并删掉第4行的背景颜色样式:
1 2 3 4 5 6 7 8 #page-header position : relative width : 100% background-color : $light -blue // 删掉这一行 background-position : center center background-size : cover background-repeat : no-repeat transition : all .5s
第五步:关于文章页的处理
经过以上的魔改后,所有的页面都成为一图流了(前提是这些页面不设置top_img和cover),对于首页等菜单页而言,不设置top_img是一图流的基本要求;对于文章页而言,cover是必须设置的,因为它负责所有文章卡片的填充。
但现在有个问题:一旦文章页设置了cover,或主题配置文件中设置了默认的cover,那么文章页的顶部图就会展示配置的图片,也就是说,文章页没实现一图流。
对于文章页是否展示顶部图,各有各的看法,有人认为文章页展示 cover 图比较好,有人认为文章页不展示 cover 才是完整的一图流。我的逻辑是:如果我想给这篇文章显示顶部图,我就配置 top_img,如果我不想,就不配置 top_img,也就是说,仅通过 top_img 来决定文章页的顶部图。
打开主题根目录下的 /source/layout/includes/header/index.pug 文件,进行如下修改:
3.03:优化一图流 存在问题:顶部图和页脚的遮蔽色是固定不变的,但是针对不同的网站背景,需要调整不同的遮蔽色
解决办法:遮蔽色分为浅色模式和深色模式两种,按照本文 2.2 中的思路来进行调整。
第一步:在全局变量文件中自定义遮蔽色
在 /themes/butterfly/source/css/var.styl 文件中添加两个遮蔽色:
1 2 3 $monkey -black = #000000 $monkey -light-mark-bg = alpha ($monkey -black, .5 )$monkey -dark-mark-bg = alpha ($monkey -black, .2 )
第二步:在深浅模式的变量文件中定义同名变量
打开 themes/butterfly/source/css/_global/index.styl 文件,新增定义:
1 --monkey-mark-bg : $monkey -light-mark-bg
打开 themes/butterfly/source/css/_mode/darkmode.styl 文件,新增定义:
1 --monkey-mark-bg : $monkey -dark-mark-bg
第三步:在对应的组件中,修改遮蔽色
修改头部遮蔽色,打开文件 themes/butterfly/source/css/_layout/head.styl 文件,找到下面这段并修改
1 2 3 4 5 6 7 8 if hexo-config ('mask.header' ) &:not (.not-top -img):before position : absolute width : 100% height : 100% background-color : var (--monkey-mark-bg) content : ''
修改页脚遮蔽色,打开文件 themes/butterfly/source/css/_layout/footer.styl 文件,找到下面这段并修改
1 2 3 4 5 6 7 8 if hexo-config ('footer_img' ) != false && hexo-config ('mask.footer' ) &:before position : absolute width : 100% height : 100% background-color : var (--monkey-mark-bg) content : ''
3.04:卡片透明度 一图流的“终极形态”就是将博客中所有的卡片都设置为半透明 !Butterfly 主题的页面都是由一个个卡片组装起来的,将这些卡片设置为半透明以后,会让网站的背景图片更完美的呈现出来!
第一步:在全局变量文件中自定义卡片背景
在 /themes/butterfly/source/css/var.styl 文件中添加两个卡片背景色:
1 2 3 $monkey -white = #FFFFFF $monkey -light-card-bg = alpha ($monkey -white, .75 )$monkey -dark-card-bg = alpha (#121212 , .6 )
第二步:在深浅模式的变量文件中定义同名变量
打开 themes/butterfly/source/css/_global/index.styl 文件,新增定义:
1 --monkey-card-bg : $monkey -light-card-bg
打开 themes/butterfly/source/css/_mode/darkmode.styl 文件,新增定义:
1 --monkey-card-bg : $monkey -dark-card-bg
第三步:修改卡片背景色
比如修改主页文章列表的卡片时,先找到设定它样式的文件 themes/butterfly/source/css/_page/homepage.styl,发现里面没有对应的代码,只找到了:
这才发现,原来 Butterfly 主题所有卡片样式都在 themes/butterfly/source/css/_global/function.styl 中定义的,修改为:
1 2 3 4 5 6 .cardHover background : var (--monkey-card-bg) // 自定义卡片背景 box-shadow : var (--card-box-shadow) transition : all .3s addBorderRadius (8 )
第四节:设置字体栈 4.01:先验知识 网页的字体是通过font-family来设定的,Butterfly 主题在/source/css/var.styl文件中定义了一些变量:
1 2 3 4 5 6 7 $chineseFont = $language == 'zh-CN' ? 'Microsoft YaHei' : 'Microsoft JhengHei' $default -font-family = -apple-system, BlinkMacSystemFont, 'Segoe UI' , 'Helvetica Neue' , Lato, Roboto, 'PingFang SC' , $chineseFont , sans-serif$default -code-font = consolas, Menlo, monospace, 'PingFang SC' , $chineseFont , sans-serif$font -family = hexo-config ('font.font_family' ) ? unquote (hexo-config ('font.font_family' )) : $default -font-family $code -font-family = hexo-config ('font.code_font_family' ) ? unquote (hexo-config ('font.code_font_family' )) : $default -code-font$site -name-font = hexo-config ('blog_title_font.font_family' ) && unquote (hexo-config ('blog_title_font.font_family' ))
然后在/source/css/_global/index.styl中进行了设定:
1 2 3 4 5 6 7 8 9 10 11 body position : relative overflow-y : scroll min-height : 100% background : var (--global-bg) color : var (--font-color) font-size : var (--global-font-size) font-family : $font -family line-height : $text -line-height -webkit-tap-highlight-color : rgba (0 , 0 , 0 , 0 ) scroll-behavior : smooth
font-family的工作原理我就不废话了,总而言之,Butterfly 主题总能匹配到访客本地浏览器安装的字体,不需要从网上另行加载字体并用来显示!
可见,默认情况下,Butterfly 主题会针对不同的系统使用不同的字体,比如在 Mac 上使用的是-apple-system,即苹果系统默认的字体,而在 Windows 上,则至少会有微软雅黑来兜底!
4.02:我的执念 最一开始,我无法忍受我的博客在不同的访客那里展示的字体不同,强迫症推动我去研究网站字体的相关知识,如字体选择(研究了各种常见的字体)、字体引入(浏览器如何引入字体)、字体设置(样式文件设置字体族)等。但一番折腾下来,我慢慢觉得自定义字体似乎是一件没那么必要的事情,因为:
自定义字体,浏览器需加载额外的字体文件,中文字体文件一般很大,会严重拖慢网页加载速度(主要原因);
我相中的字体都不能免费商用,比如苹方(PingFang SC)等优秀字体;
所以我就放弃了。但随着魔改的深入,我越来越想把主题的字体换掉,并且意识到应该在魔改的一开始就把字体换掉,因为字体对整体的主题效果影响很大!这是我一直以来的执念,但苦于中文字体文件过大(严重拖慢网页加载速度)这个硬伤,我一直忍着!
直到有一天,我突然灵感一现,既然字体文件中是一个个字形(Glyphs),那整个字体文件中一定包含了大量我网站中没有用到的字符,这部分字符对于我而言就是多余的。这样的话,为什么不能只保留我网站中用到的字符,而把这些多余的字符删掉呢?这样的话我的字体文件不就小很多了吗?说干就干。
4.03:下载字体 我选择的字体是 Glow Sans(未来荧黑),访问它的 GitHub官方地址 ,在 Releases 页进行下载:
选择下载 Normal(标准宽度)字体文件,解压后会得到多个文件:
1 2 3 4 5 6 7 8 9 GlowSansSC-Normal-Bold.otf GlowSansSC-Normal-Book.otf GlowSansSC-Normal-ExtraBold.otf GlowSansSC-Normal-ExtraLight.otf GlowSansSC-Normal-Heavy.otf GlowSansSC-Normal-Light.otf GlowSansSC-Normal-Medium.otf GlowSansSC-Normal-Regular.otf GlowSansSC-Normal-Thin.otf
每个文件对应不同字重(粗细):
文件名后缀
字重英文
数字权重
视觉效果
Butterfly 主题使用情况
Thin
Thin
100
极细
极少用,不建议网页加载
ExtraLight
ExtraLight
200
超细
基本不用,移动端渲染脆弱
Light
Light
300
细体
次要小字可选
Regular
Regular
400
标准常规
传统正文基准
Book
Book
450
介于常规和中等粗之间
正文首选 ,屏幕阅读舒适感优于 Regular
Medium
Medium
500
中等粗
不必加载
Bold
Bold
700
标准粗体
粗体首选 ,标题、导航栏、正文加粗等用这套字重
ExtraBold
ExtraBold
800
超粗,厚重醒目
必选:首页标题、文章封面大字
Heavy
Heavy
900
最重黑度,视觉冲击力极强
超大标题、标语
整个 Butterfly 主题只要求了两种字重:normal 和 bold,也就是 400 和 700,但是因为未来荧黑的 450 阅读感更好,选择用 450 的替代 400。所以这些文件中,我只需要 GlowSansSC-Normal-Book.otf 和 GlowSansSC-Normal-Bold.otf。
4.04:创建目录 在博客根目录下的 source 文件夹中新建名为 fonts 的文件夹,并将需要的字体文件放入其中:
4.05:安装工具 为了实现我的设想,至少需要两个工具:
打开终端,直接执行如下命令来全局安装 cheerio:
工具 fonttools 可以通过 pip 来安装:
此外,运行 fonttools 还需要依赖 brotli,所以还得安装它:
4.06:创建脚本 首先,在本地博客根目录下的 source/fonts 文件夹中创建名为 /scripts/extract-font-glyphs.js 的脚本文件,这个脚本用来给 cheerio 提供参数的,作用是将全站用到的字形都提取到 /source/fonts/all-font-glyphs.txt 文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 const fs = require ('fs' );const path = require ('path' );const cheerio = require ('cheerio' );const publicDir = path.resolve (__dirname, '../../public' );const charOutputFile = path.join (__dirname, 'all-font-glyphs.txt' );let allRawText = '' ;function scanAllHtml (dirPath ) { const fileList = fs.readdirSync (dirPath); fileList.forEach (fileName => { const fullFilePath = path.join (dirPath, fileName); const stat = fs.statSync (fullFilePath); if (stat.isDirectory ()) { scanAllHtml (fullFilePath); return ; } if (!fileName.endsWith ('.html' )) return ; try { const htmlContent = fs.readFileSync (fullFilePath, 'utf8' ); const $ = cheerio.load (htmlContent, { decodeEntities : false , scriptingEnabled : false }); $('script, style, noscript, template, pre, figure.highlight' ).remove (); allRawText += $('body' ).text (); $('[alt]' ).each ((_, el ) => allRawText += $(el).attr ('alt' ) || '' ); $('[title]' ).each ((_, el ) => allRawText += $(el).attr ('title' ) || '' ); $('[aria-label]' ).each ((_, el ) => allRawText += $(el).attr ('aria-label' ) || '' ); } catch (err) { console .warn (`文件解析跳过:${fullFilePath} ,错误:${err.message} ` ); } }); } scanAllHtml (publicDir);const chineseReg = /[\u4e00-\u9fa5]/g ;const onlyChineseList = allRawText.match (chineseReg) || [];const uniqueCharSet = new Set (onlyChineseList);const uniqueCharString = [...uniqueCharSet].sort ().join ('' );fs.mkdirSync (path.dirname (charOutputFile), { recursive : true }); fs.writeFileSync (charOutputFile, uniqueCharString, 'utf8' ); console .log (` ===================== ✅ 全站字形提取已经完成,共收集:${uniqueCharString.length} 个汉字 =` );
4.07:裁剪流程 第一步:生成全站的静态网页
打开终端,切换到本地博客根目录下,执行如下命令:
1 hexo clean && hexo generate
第二步:切到脚本所在目录
第三步:执行文字提取脚本,生成 all-font-glyphs.txt 文件
1 NODE_PATH=$(npm root -g) node extract-font-glyphs.js
第四步:裁剪常规字体文件,输出 woff2 字体文件到当前目录
接着执行如下命令:
1 pyftsubset GlowSansSC-Normal-Book.otf --text-file=all-font-glyphs.txt --flavor=woff2 --output-file=GlowSansSC-Normal-Book.woff2
第五步:裁剪粗体字体文件,输出 woff2 字体文件到当前目录
接着执行如下命令:
1 pyftsubset GlowSansSC-Normal-Bold.otf --text-file=all-font-glyphs.txt --flavor=woff2 --output-file=GlowSansSC-Normal-Bold.woff2
完成全部操作后,此时 fonts 文件夹中就生成了裁剪后的 woff2 文件,且文件很小(300 KB 左右)。
第六步:引入字体
打开本文 1.02 中创建的 custom.styl 文件,写入如下内容(将字体引入到网页中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @font-face font-family : "GlowSansSC" src : url ("/fonts/GlowSansSC-Normal-Book.woff2" ) format ("woff2" ) font-weight : 400 font-style : normal font-display : swap @font-face font-family : "GlowSansSC" src : url ("/fonts/GlowSansSC-Normal-Bold.woff2" ) format ("woff2" ) font-weight : 700 font-style : normal font-display : swap
第七步:设置字体栈
打开 themes/source/css/var.styl 文件,在变量$default-font-family值的最前面,添加GlowSansSC即可。
经过这样以后,将博客 deploy 后,网站中所有的字体(包括英文字体)就变成未来荧黑了!
4.08:封装命令 在这之前,对网站进行修改或增加新文章后,我需要执行下面三个命令来发布:
1 hexo clean && hexo genarate && hexo deploy
但是现在,增加了裁剪字体的逻辑,那么在 genarate 和 deploy 之间就得多执行如下命令:
1 2 3 4 5 cd source/fonts NODE_PATH=$(npm root -g) node extract-font-text.js pyftsubset GlowSansSC-Normal-Regular.otf --text-file=chars.txt --flavor=woff2 --output-file=GlowSansSC-Normal-Regular.woff2 pyftsubset GlowSansSC-Normal-Bold.otf --text-file=chars.txt --flavor=woff2 --output-file=GlowSansSC-Normal-Bold.woff2 cd ../../
这就很麻烦了,发布一次文章都得累死。为了简化字体裁剪,可以将这一系列命令封装进一个命令,我选择使用 Hexo 自定义命令的方式来实现。
第一步:创建脚本
在博客根目录下创建目录 scripts,并在其中创建文件 font-file-subset.js,在该文件中写入如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 const { execSync } = require ('child_process' );const path = require ('path' );const fs = require ('fs' );hexo.extend .console .register ( 'font' , '自动提取全站文字,裁剪源字体文件并生成新的字体文件' , function ( ) { console .log ('\n ===================== ✅ 启动字体文件裁剪任务 =====================' ); try { const blogDir = process.cwd (); const fontSourceDir = path.resolve (blogDir, './source/fonts' ); const bookFontPath = path.resolve (fontSourceDir, 'GlowSansSC-Normal-Book.woff2' ); const boldFontPath = path.resolve (fontSourceDir, 'GlowSansSC-Normal-Bold.woff2' ); const publicDir = path.resolve (blogDir, './public' ); if (!fs.existsSync (publicDir)) { throw new Error (' ===================== ❌ public文件夹不存在,请先执行hexo generate =====================' ); } const publicFiles = fs.readdirSync (publicDir); const hasHtml = publicFiles.some (file => file.endsWith ('.html' )); if (!hasHtml) { throw new Error (' ===================== ❌ public内没有html文件,页面生成不完整 =====================' ); } if (!fs.existsSync (fontSourceDir)) { throw new Error (' ===================== ❌ 字体目录 source/fonts 不存在 =====================' ); } process.chdir (fontSourceDir); if (fs.existsSync (bookFontPath)) { try { fs.unlinkSync (bookFontPath); console .log (' ===================== ✅ 已删常规书本字体文件 =====================' ); } catch (err) { console .warn (' ===================== ⚠️ 常规书本字体删除失败:' , err.message ); } } if (fs.existsSync (boldFontPath)) { try { fs.unlinkSync (boldFontPath); console .log (' ===================== ✅ 已删常规粗体字体文件 =====================' ); } catch (err) { console .warn (' ===================== ⚠️ 常规粗体字体删除失败:' , err.message ); } } const tempCharFile = path.resolve (fontSourceDir, 'all-font-glyphs.txt' ); if (fs.existsSync (tempCharFile)) { try { fs.unlinkSync (tempCharFile); console .log (' ===================== ✅ 已删除旧临时字符文件 =====================' ); } catch (err) { console .warn (' ===================== ⚠️ 临时字符文件删除失败:' , err.message ); } } const extractScriptPath = path.resolve (fontSourceDir, 'extract-font-glyphs.js' ); try { execSync (`NODE_PATH=$(npm root -g) node ${extractScriptPath} ` , { stdio : 'inherit' }); console .log (' ===================== ✅ 已生成新临时字符文件 =====================' ); } catch (err) { throw new Error (` ===================== ❌ 字符提取脚本执行失败:${err.message} ` ); } try { execSync ( 'pyftsubset GlowSansSC-Normal-Book.otf --text-file=all-font-glyphs.txt --flavor=woff2 --output-file=GlowSansSC-Normal-Book.woff2' , { stdio : ['inherit' , 'inherit' , 'ignore' ] } ); if (fs.existsSync (bookFontPath)) { console .log (' ===================== ✅ 生成常规书本字体文件 =====================' ); } else { throw new Error ('未输出常规woff2文件' ); } } catch (err) { throw new Error (` ===================== ❌ 常规书本字体裁剪失败:${err.message} ` ); } try { execSync ( 'pyftsubset GlowSansSC-Normal-Bold.otf --text-file=all-font-glyphs.txt --flavor=woff2 --output-file=GlowSansSC-Normal-Bold.woff2' , { stdio : ['inherit' , 'inherit' , 'ignore' ] } ); if (fs.existsSync (boldFontPath)) { console .log (' ===================== ✅ 生成常规粗体字体文件 =====================' ); } else { throw new Error ('未输出粗体woff2文件' ); } } catch (err) { throw new Error (` ===================== ❌ 常规粗体字体裁剪失败:${err.message} ` ); } console .log (' ===================== 🎉 字体裁剪任务执行完毕 =====================' ); } catch (error) { console .error (' ===================== ❌ 字体裁剪任务终止执行:' , error.message ); } } ); hexo.extend .console .register ( 'f' , '自动提取全站文字,裁剪源字体文件并生成新的字体文件(等价 hexo font)' , function (args ) { return this .call ('font' , args); } );
有这个文件,我们就自定义了Hexo font和hexo f命令。打开终端,切换到博客根目录下,在 generate 后执行:
至此,全站字体修改完成,以后部署博客只需要执行:
1 hexo clean && hexo generate && hexo font && hexo deploy
或者:
1 hexo cl && hexo g && hexo f && hexo d
4.09:忽略文件 存在问题:执行 generate 和 deploy 命令时,发现 Hexo 会带上 fonts 文件中的所有文件,实际上这并不需要。
解决办法:在站点配置文件中使用exclude来配置不需要 Hexo 处理的文件
1 2 3 exclude: - fonts/*.txt - fonts/*.otf
同时,在博客根目录下的 .ignore 文件中,添加要忽略的字体文件:
1 2 source/fonts/*.otf source/fonts/*.txt
4.10:英文字体 我选择的英文字体是 Nimbus Sans L ,直接在官网上下载即可,文件本身就很小,所以不需要裁剪.基本步骤跟设置未来荧黑的步骤差不多:
第一步:转为 woff2 格式(使用在线工具 https://transfonter.org/ )
第二步:将转换的 woff2 文件放入博客根目录下的 /source/fonts/ 目录下;
第三步:在主题根目录下的 /source/css/_custom/custom.styl 中增加 @font-face 来引入字体;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @font-face font-family : "NimbusSanL" src : url ("/fonts/NimbusSanL-Reg.woff2" ) format ("woff2" ) font-weight : 400 font-style : normal font-display : swap @font-face font-family : "NimbusSanL" src : url ("/fonts/NimbusSanL-Bol.woff2" ) format ("woff2" ) font-weight : 700 font-style : normal font-display : swap
第四步:修改主题根目录下的 /source/css/var.styl 文件,将引入的 NimbusSanL 字体配置进字体栈。
4.11:代码字体 代码块字体采用默认的配置,这里就不做记录了。
第五节:字体的大小 5.01:主题的逻辑 更换了字体以后,总觉得网站整体的字体都变小了,在修改之前,先来看一下 Butterfly 主题字号设置的基本逻辑。
最终页面的 index.css 文件是由 themes/butterfly/source/css/index.styl 生成的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if hexo-config ('css_prefix' ) @import 'nib' @import '_third-party/normalize.min.css' @import 'var' @import '_global/*' @import '_highlight/highlight' @import '_page/*' @import '_layout/*' @import '_tags/*' @import '_mode/*' @import '_search/index' @import '_custom/*'
可见,只要我们按照这个文件的生成顺序去找对应的文件中的字号设置,就能搞明白浏览器的字体是如何设置的了。
第一个:normalize.min.css
该文件是基础浏览器标准化,它规定了标题正文等字体,仅仅是打底,后续存在相同设定时,这里的设置会被覆盖。
第二个:themes/butterfly/source/css/var.styl
改文件中定了主题要用的字号,这里设定了根字号(如果不在主题配置中修改,则默认14px):
第三个:index.styl 和 function.styl
这两个文件在 themes/butterfly/source/css/_global 文件夹中,前者设定了页面的基础字号:
后者的主要作用是提供定义了不同屏幕尺寸,在不同尺寸屏幕中的字号还需要到具体的业务模块中去设置:
5.02:主题根字号 主题默认的根字号是 14px:
5.03:响应式断点 针对不同尺寸的屏幕,页面会有不同的布局,这就是响应式布局,Butterfly 的响应式断点定义在 function.styl 中。主题默认的响应断点有:600、768、900、1024、2000,对于移动端只有一个小于 600 的适配,我为了更好的移动端体验,在原有的基础上,新增了移动端更细致的断点。
打开 function.styl 文件,增加如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 maxWidth375 () @media screen and (max-width : 375px ) {block} minWidth376 () @media screen and (min-width : 376px ) {block} maxWidth390 () @media screen and (max-width : 390px ) {block} minWidth391 () @media screen and (min-width : 391px ) {block} maxWidth393 () @media screen and (max-width : 393px ) {block} minWidth394 () @media screen and (min-width : 394px ) {block} maxWidth430 () @media screen and (max-width : 430px ) {block} minWidth431 () @media screen and (min-width : 431px ) {block} maxWidth440 () @media screen and (max-width : 440px ) {block} minWidth441 () @media screen and (min-width : 441px ) {block} maxWidth480 () @media screen and (max-width : 480px ) {block} minWidth481 () @media screen and (min-width : 481px ) {block} minWidth601 () @media screen and (min-width : 601px ) {block}
只有定义了这个,我们在具体的组件样式文件中,才能直接使用,使用方式:
1 2 3 +maxWidth上限() 选择器 属性: 值
第六节:阿里的图标 6.01:默认图标 Butterfly 主题支持 Font-Awesome V7 图标。首先,主题根目录下的plugins.yml文件中指定了:
1 2 3 4 5 fontawesome: name: '@fortawesome/fontawesome-free' file: css/all.min.css other_name: font-awesome version: 7.3 .1
只需在使用时,用<i class="fas fa-github"></i>来引用就可以了,这里的fas fa-github就是 Awesome 的图标名,名称可以到官网上去找。除了使用默认的 Awesome 图标,我们还可以自己引入阿里图标来使用。
6.02:挑选图标 访问 阿里图标库 iconfont.cn ,搜到想要的图标,将其添加到购物车,然后统一添加到项目中,比如我添加了 13个:
6.03:下载图标 下载图标的 Font class 文件:
下载下来的文件解压后,只保留下面的文件,其余的都删除:
1 2 3 4 iconfont.css iconfont.ttf iconfont.woff iconfont.woff2
6.04:图标文件 将iconfont.ttf、iconfont.woff和iconfont.woff2这三个文件,放到博客根目录下的/source/icons/中(如果没有就新建):
6.05:配置CSS 修改 iconfont.css 中的原始内容为下面这样(主要是图标文件的路径),然后将其转换为 stylus 代码,最后整个内容都复制到 custom.styl 文件的最后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 // 引入阿里图标 @font-face font-family "iconfont" src url('/icons/iconfont.woff2?t=1785494740304' ) format('woff2' ), url('/icons/iconfont.woff?t=1785494740304' ) format('woff' ), url('/icons/iconfont.ttf?t=1785494740304' ) format('truetype' ) font-display swap .iconfont font-family "iconfont" !important font-size 16px font-style normal -webkit-font-smoothing antialiased -moz-osx-font-smoothing grayscale .icon-square-douyin :before content "\e61b" .icon-circle-weibo :before content "\e6f5" .icon-circle-github :before content "\e60d" .icon-circle-rss :before content "\e777" .icon-circle-gitee :before content "\e600" .icon-circle-zhihu :before content "\ecb2" .icon-circle-weixin :before content "\e61f" .icon-circle-youxiang :before content "\e62e" .icon-circle-qq :before content "\e64c" .icon-square-qq :before content "\e625" .icon-square-weibo :before content "\e6c3" .icon-square-weixin :before content "\e6ba" .icon-square-youxiang :before content "\e623"
6.06:使用方式 经过以上的操作,阿里图标就已经引入到博客网站了,只需要在页面中使用下面的方式来引用就可以了:
1 <i class ="iconfont icon-square-github" > </i >
至于图标的颜色,可在对应的 CSS 样式去设定。
第七节:侧边栏魔改 首页的侧边栏有很多的卡片,Butterfly 页提供了自定义侧边栏的功能,官方文档 中的描述是“可自行决定哪个项目需要显示,可决定位置,也可以设置不显示侧边栏”,并且基本的设置都可以在主题配置文件中的aside进行配置!
7.01:站长信息 站长信息卡片建议保留,里面的内容都可以配置来实现,其中社交图标的配置在主题配置文件中的social:
1 2 3 4 5 social: fab fa-github: https://github.com/fattymonkey || Github || '#24292e' fas fa-envelope: mailto:1185349843@qq.com || Email || '#4a7dbe'
这是默认的图标,参考本文第六节引入阿里图标的操作,我在这里配置为:
1 2 3 4 5 6 7 8 social: iconfont icon-square-youxiang: mailto:1185349843@qq.com || 给我发邮件 || '#FF8901' iconfont icon-square-weibo: https://weibo.com/u/5102287461 || 访问我的微博 || '#E6162D' iconfont icon-square-douyin: https://v.douyin.com/bmpNaH4XdmI || 访问我的抖音 || '#000000'
7.02:字体居中 修改了网站的字体后,发现侧边栏站长信息卡片上的 GitHub 图标和文字“我的GitHub”在水平方向上没有对齐,文字偏高了,这是因为字体和图标的基线不一致导致的,我这里直接将文字往下拉一些。
打开/themes/butterfly/source/css/_layout/aside.styl文件,增加下面的代码:
这个方案是临时方案,因为整个博客网站中还存在其他地方有类似的问题,暂时先改这一个,后面更新根治的办法。
7.03:社交弹窗 现在站长信息卡片上有三个社交链接,但是我还是想加上微信和 QQ 的链接,能让用户点击的时候,弹窗显示二维码。但是主题原生不支持这种方式,所以我需要自己来增加弹窗。
第一步:放置二维码图片
将二维码图片放到博客根目录下的 /source/img/social/ 中:
第二步:增加弹窗的组件
首先,在 themes/butterfly/layout/includes/ 中新建一个名为 qrmodal.pug 的文件,用来写弹窗的页面元素:
1 2 3 4 5 div.qr-modal#qrModal div.qr-modal-inner span.qr-close x p#qr-title img#qr-img(alt="社交二维码")
然后,因为我需要把弹窗的 DOM 元素放到 body 里面,页面的结构是由 themes/butterfly/layout/includes/layout.pug 决定的,我打算把弹窗组件放到 rightside 和 additional-js 中间,所以找到下面的代码:
1 2 include ./rightside.pug include ./additional-js.pug
在这两行中间添加一行:
第三步:修改图标的逻辑
在 themes/butterfly/layout/includes/header/social.pug,找到下面的代码:
1 2 a.social-icon(href=href target="_blank" title=iconTitle) i(class=icon style=iconStyle)
这说明所有的标签被点击时,都会新开标签页来展示,但是我想要它在当前页面展示,所以将其修改为:
1 2 3 4 5 6 if link.startsWith('javascript:') a.social-icon(href=href title=iconTitle) i(class=icon style=iconStyle) else a.social-icon(href=href target="_blank" title=iconTitle) i(class=icon style=iconStyle)
第四步:编辑弹窗的样式
在 /themes/butterfly/source/css/_custom/custom.styl 文件的末尾增加如下代码,用来设定弹窗的样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 .qr-modal display : none position : fixed inset : 0 background : rgba (0 ,0 ,0 ,0.72 ) z-index : 9999 align-items : center justify-content : center backdrop-filter : blur (4px ) .qr-modal .show display : flex .qr-modal-inner position : relative background : #fff padding : 32px 28px border-radius : 16px text-align : center box-shadow : 0 8px 30px rgba (0 ,0 ,0 ,0.22 ) max-width : 320px .qr-close position : absolute right : 16px top : 12px font-size : 26px cursor : pointer color : #888 width : 32px height : 32px display flex align-items center justify-content center border-radius : 50% transition : all 0.2s ease &:hover background rgba (0 ,0 ,0 ,0.08 ) color : #222 #qr-title font-size : 16px margin : 0 0 16px color : #333 #qr-img width : 220px border-radius : 8px [data-theme="dark" ] .qr-modal-inner background : #242424 #qr-title color : #e5e5e5 .qr-close color : #aaa &:hover background rgba (255 ,255 ,255 ,0.1 ) color : #fff
第五步:增加弹窗的逻辑脚本
在 /themes/butterfly/source/js/ 目录中新建 qr-popup.js 脚本文件,填入如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 window .getCode = function (type ) { const popover = document .getElementById ('qrModal' ) const titleDom = document .getElementById ('qr-title' ) const imgDom = document .getElementById ('qr-img' ) const qrAsset = { weixin : { title : '扫码添加我的微信' , src : '/img/social/weixincode.webp' }, qq : { title : '扫码添加我的QQ' , src : '/img/social/qqcode.webp' } } const assetInfo = qrAsset[type] titleDom.innerText = assetInfo.title imgDom.src = assetInfo.src popover.classList .add ('show' ) } function closeQRModal ( ) { const popover = document .getElementById ('qrModal' ) popover.classList .remove ('show' ) } document .addEventListener ('click' , e => { if (e.target .classList .contains ('qr-close' )) { closeQRModal () } }) document .addEventListener ('click' , e => { const popover = document .getElementById ('qrModal' ) if (e.target === popover) { closeQRModal () } }) document .addEventListener ('pjax:complete' , () => { closeQRModal () })
然后打开 /themes/butterfly/layout/includes/additional-js.pug 文件,添加引入脚本的代码(注意缩进):
1 2 // 加载社交弹窗的脚本 script(src=url_for('/js/qr-popup.js'))
第六步:配置图标
在主题配置文件的 social 处增加微信和 QQ 的配置:
1 2 3 4 5 6 7 8 9 10 social: iconfont icon-square-youxiang: mailto:1185349843@qq.com || 给我发邮件 || '#FF8901' iconfont icon-square-weibo: https://weibo.com/u/5102287461 || 访问我的微博 || '#E6162D' iconfont icon-square-douyin: https://v.douyin.com/bmpNaH4XdmI || 访问我的抖音 || '#000000' iconfont icon-square-weixin: javascript:getCode('weixin') || 扫码添加我的微信 || '#0CC161' iconfont icon-square-qq: javascript:getCode('qq') || 扫码添加我的QQ || '#0FB8F6'
7.04:图标大小 默认的图标大小是 16 px,在 custom.styl 中有定义,如果将这里的 16px 改成 1em 的话,就使用博客全局的根字号 14 px 了。同时,在 blog/themes/butterfly/source/css/_layout/aside.styl 中,针对社交链接,单独设置了social-icon 的大小为 1.4 em,所以此时社交图标的大小是 19.6 px,我的做法是修改这两个地方。
第一,将 custom.styl 中的默认图标大小设置为 1em:
1 2 3 4 5 6 .iconfont font-family "iconfont" !important font-size 1em font-style normal -webkit-font-smoothing antialiased -moz-osx-font-smoothing grayscale
第二,将 social-icon 的大小设置为 1.5 em,最终图标大小 21 px:
1 2 3 4 .social-icon margin : 0 10px color : var (--font-color) font-size 1.5em
7.05:标签卡片 默认情况下,标签卡片上的标签是左对齐的,这不是很美观,我打算把它改成两边对齐。
打开 /themes/butterfly/source/css/_layout/aside.styl 文件,增加下面几行即可:
7.06:网站信息 网站信息卡片上的文字,可以在文件themes/butterfly/languages/zh-CN.yml中进行自定义修改,不再赘述。
第八节:页脚的魔改 在本系列的第二篇文章《Butterfly主题之开始使用》 中,我根据官方文档对页脚部分先进行了最基本的配置,是这样的:
8.01:跳转问题 在页脚导航栏配置站内的网页后(不管配置的是相对路径还是绝对路径),当点击这个链接时,浏览器会跳转到一个新的标签页来访问。显然这是不合理的,正确的行为应该是自动判断点击的链接是否是站内页面,如果是站内页面就不应该新开标签页来访问,而是在当前标签页直接跳转。
打开themes/butterfly/layout/includes/footer.pug 文件,找到:
1 a(href=url_for(subitem.url), target='_blank' title=subitem.title)= subitem.title
然后用下面这三行去替换这一行(注意代码缩进):
1 2 3 - const linkUrl = url_for(subitem.url) - const isExternal = linkUrl.startsWith('http') a(href=linkUrl, target= isExternal ? '_blank' : '_self', title=subitem.title)= subitem.title
这样,只要再配置相对路径,则会在当前标签页打开,配置绝对路径,则会用新标签页打开。
8.02:随机文章 之前,添加“随机文章”非常简单,只需要安装 hexo-random-post 插件,并在站点配置文件中简单配置下就可以了。但是现在这个插件已经在 npm 中下架,所以就不能使用这种方式了。
我的实现方式是:在 Hexo 渲染时把所有文章链接一次性输出到页面全局变量,点击随机文章直接从数组取值,不需安装插件,也无需网络等待。
第一步:在博客根目录下的 /source/js/ 中新建 random-post.js 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 window .__articleUrls = window .__articleUrls || [];function goRandom ( ){ if (window .__articleUrls .length > 0 ){ const idx = Math .floor (Math .random () * window .__articleUrls .length ); location.href = window .__articleUrls [idx]; return ; } fetch ('/archives' ) .then (res => res.text ()) .then (html => { const dom = new DOMParser ().parseFromString (html, 'text/html' ); const links = dom.querySelectorAll ('.article-sort-item-info a.article-sort-item-title' ); window .__articleUrls = Array .from (links).map (el => el.href ); if (window .__articleUrls .length === 0 ){ alert ("未读取到文章" ); return ; } const idx = Math .floor (Math .random () * window .__articleUrls .length ); location.href = window .__articleUrls [idx]; }) .catch (err => { alert ("获取文章列表失败,请刷新页面" ); console .error (err); }) }
第二步:在主题根目录下的 /layout/includes/head.pug 文件,添加如下代码
1 2 // 加载随机文章的脚本 script(src=url_for('/js/random-post.js'))
第三步:修改主题根目录下的 /layout/includes/footer.pug 中的逻辑
第四步:在主题配置文件中配置标签的路径
8.03:字体对齐 页脚的标题和子菜单是左对齐的,我希望将其设置为垂直对齐。修改主题根目录下的 /source/css/_layout/footer.styl:
8.04:屏幕适配 页脚设置了四个大类,用浏览器访问时四个分类平铺展开,但是当用手机访问时,发现页脚这个地方很别扭:
这是因为 Butterfly 原生的适配屏幕有点问题。修改themes/butterfly/source/css/_layout/footer.styl:
8.05:社交横栏 第一步:增加页面元素
在themes/butterfly/source/layout/includes/footer.pug中if nav的下面添加代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 .footer_social a.social_link(href="mailto:1185349843@qq.com" title="邮箱") i.iconfont.icon-circle-youxiang a.social_link(href="javascript:getCode('weixin')" title="微信" rel="noopener nofollow") i.iconfont.icon-circle-weixin a.social_link(href="javascript:getCode('qq')" title="QQ" rel="noopener nofollow") i.iconfont.icon-circle-qq a.social_link(href="https://weibo.com/u/5102287461" title="微博" target="_blank" rel="noopener nofollow") i.iconfont.icon-circle-weibo //- 回到顶部头像 img.footer_mini_logo.entered.loading(style="border-radius:50%" src="/img/avatar/boy.webp" onclick="btf.scrollToDest(0,500)" title="返回顶部") a.social_link(href="https://gitee.com/fattymonkey" title="Gitee" target="_blank" rel="noopener nofollow") i.iconfont.icon-circle-gitee a.social_link(href="https://www.zhihu.com/people/fattymonkey" title="知乎" target="_blank" rel="noopener nofollow") i.iconfont.icon-circle-zhihu a.social_link(href="https://github.com/fattymonkey" title="GitHub" target="_blank" rel="noopener nofollow") i.iconfont.icon-circle-github a.social_link(href="/atom.xml" title="RSS" target="_blank" rel="noopener nofollow") i.iconfont.icon-circle-rss
第二步:设置横栏样式
打开 themes/butterfly/source/css/_layout/footer.styl 文件,在#footer的下一层,添加如下样式代码(注意缩进):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 .footer_social display : flex justify-content : space-between margin : 0 auto padding : 20px 40px 0 40px max-width : 1200px width : 100% +maxWidth768 () padding : 20px 10px 0 10px a .social_link display : flex margin : 1rem auto border-radius : 3rem width : 2em // a标签宽度 height : 2em // a标签高度 justify-content : center align-items : center transition : transform .3s .iconfont font-size : 2em // 跟a标签一样大 &:hover transform : scale (1.5 ) transition : all .3s ease 0s -webkit-transform : scale (1.5 ) //-webkit-解决浏览器兼容问题 -webkit-transition : all .5s ease 0s +maxWidth768 () &:hover transform : none transition : none -webkit-transform : none img .footer_mini_logo width : 4rem height : 4rem margin : 0 auto cursor : pointer transition : cubic-bezier (0 , 0 , 0 , 1.29 ) .5s &:hover transform : scale (1.4 ) transition : all .3s ease 0s -webkit-transform : scale (1.5 ) //-webkit-解决浏览器兼容问题 -webkit-transition : all .5s ease 0s +maxWidth768 () &:hover transform : none transition : none -webkit-transform : none
创建社交横栏中的 RSS 订阅页面,需要先安装插件。
第一步:终端切换到博客根目录下执行
1 npm install hexo-generator-feed --save
第二步:打开站点配置文件,增加下面的配置
1 2 3 4 5 6 7 8 9 feed: enable: true type: atom path: atom.xml limit: 20 content: false content_limit: 140 autodiscovery: true
第三步:终端切换到博客根目录下执行
1 hexo clean && hexo generate
然后在本地博客根目录中的 /public 文件夹中,能看到 atom.xml 文件,即代表 RSS 功能正常。需要注意的是:此时你使用hexo server启动本地预览时,点击 RSS 订阅的按钮,此时展示的atom.xml中的中文可能是乱码的,不要慌,此时你再去点击部署到远程的 RSS 订阅,发现不存在乱码问题,所以不用慌,不影响正常使用!至于为什么本地预览会出现乱码,就不得而知了~~