普通视图

发现新文章,点击刷新页面。
昨天以前叶寻 | Cyrus Yip

小米已退烧

2024年12月8日 00:00

小米一开始的口号是为发烧(友)而生,现在新的解锁 bootloader 政策让我感觉小米退烧了。

原本小米解锁 bootloader 的条件是等待 168 小时(7 天)。小米推出 HyperOS 后中国大陆的型号解锁 bootloader 非常难,需要小米社区 5 级,还要考试1

这个解锁政策真是让我大跌眼镜,根本是刁难用户。真搞不明白小米公司怎么想的。如果嫌用户解锁后弄坏手机要去售后,那可以在用户解锁 bootloader 后取消售后。

新解锁政策还有一个恶心的点:只针对中国大陆的设备。凭什么陆版手机解锁就那么难,国际版手机解锁就和以前一样2

新解锁政策大大影响了第三方 ROM 的开发。本小米老用户感到伤心、失望、生气。买陆版小米手机刷机的日子可能一去不复返了。


  1. Xiaomi-HyperOS-BootLoader-Bypass 可以绕过 HyperOS 的解锁限制。 ↩︎

  2. 国际版手机解锁也没那么容易了,我在 PixelOS Chat 看到有人申请解锁时提示额度已满(https://t.me/pixeloschat/466581)。 ↩︎

一加 Ace 3 刷机前的准备与安装 PixelOS 的过程

2024年12月5日 00:00

注意事项 #

  • 本文的操作可能会过时,建议参考文末的文章。
  • 拿到手机要马上关闭自动更新,避免更新到不合适刷机的版本。
  • 解锁 bootloader 重启后马上关掉自动更新。
  • 解锁 bootloader 时会清除数据。如果确定要刷机,建议买到手机马上解锁,后面拿到 root 权限可以用 Neo Backup 备份应用数据。
  • PixelOS 安装完第一次后就清除数据并重启,然后在设置里再次安装系统。我在 recovery 安装两次 PixelOS 应该是错误操作。

名词解释 #

  • 刷机:安装操作系统
  • ROM:操作系统。ROM 本义是可读存储器(read-only memory),在刷机的语境下是(安装于 ROM 的)操作系统的意思。
  • bootloader:引导程序,需要先解锁它才能安装其他操作系统
  • root:最高权限
  • ocdt.img:每台一加手机特有的分区
  • persist.img:每台手机特有的分区

恢复系统教程 #

先看这篇恢复系统的教程:How To Guide - [12R/Ace 3] EDL DownloadTool to restore your device to OxygenOS/ColorOS | XDA Forums,最好用不上啦。

测试硬件功能 #

测试硬件功能,参考 charter/device-support-requirements.md at main · LineageOS/charter

确定硬件没问题后,如果刷机后有问题就是 ROM 有问题。

  • 扬声器、听筒
  • WIFI
  • 通话
  • USB
  • 蓝牙
  • 前置摄像头(拍照、录像)
  • 后置摄像头(0.6/1/2/5 倍数,拍照、录像)
  • 指纹
  • NFC
  • 红外线(遥控)
  • 定位
  • 陀螺仪(指南针)
  • 距离传感器(通话时会熄屏)
  • 光传感器(自动亮度)
  • 网络(只测了 5G)

记录版本信息 #

记录版本信息,可能以后有用。打开设置->关于手机->版本信息。

1
2
3
4
5
6
7
8
版本号
PJE110_14.0.0.813(CN01U140P02)
基带版本
Q_V1_P14,Q_V1_P14
内核版本
5.15.123-android-13-8-00766-gf04dea8b48fa
SOTA 版本号
U140P02(BRB1CN01)

解锁 bootloader #

在电脑安装 adb 和 fastboot,参看 Using ADB and fastboot | LineageOS Wiki

解锁时会清除数据。如果确定要刷机,建议买到手机马上解锁,后面拿到 root 权限可以用 Neo Backup 备份应用数据。

打开设置->关于手机->版本信息,快速多次点击版本号,开启开发者模式。

返回设置,打开系统与更新->开发者选项,开启 OEM 解锁和 USB 调试。

手机通过数据线插到电脑 USB 口。

1
2
3
4
adb devices # 手机按允许调试
adb reboot bootloader # 进入 fastboot 模式
fastboot devices # 应该会看到编号
fastboot flashing unlock # 解锁 bootloader,用音量键选择 UNLOCK THE BOOTLOADER,按电源键确认

现在手机重置了,开机后跳过可以跳过的设置,进入桌面。打开设置,搜索「更新」,打开「自动更新设置」,关闭自动下载和夜间自动更新。

获取 root 权限 #

Oxygen Updater 不能下载一加 Ace 3 的 ROM,所以从大侠阿木的网站下载当前版本的 ROM,也就是 PJE110_14.0.0.813

解压文件,获得 payload.bin

安装 payload-dumper-go

1
2
# Arch Linux 的安装方法
paru -S payload-dumper-go-bin

解压 payload.bin

1
payload-dumper-go payload.bin

进入解压目录,将 init_boot.img 传到手机。

1
adb push init_boot.img /sdcard/Download/

在电脑下载 Magisk,通过 adb 安装到手机。

1
adb install app-release.apk # 在手机同意安装

在手机打开 Magisk,点击「Magisk 安装->选择并修复一个文件」,选择 init_boot.img,点击「开始」。在文件管理将修补好的文件重命名为 magisk_patched.img

magisk_patched.img 复制到电脑。

1
adb pull /sdcard/Download/magisk_patched.img

获取 root 权限。

1
2
3
adb reboot bootloader
fastboot flash init_boot magisk_patched.img
fastboot reboot

备份 ocdt.img 和 persist.img #

每台一加手机的 ocdt.img 和 persist.img 都是独一无二的,所以先要备份下来。

1
2
3
4
5
6
adb shell
su # 在手机授权
dd if=/dev/block/bootdevice/by-name/ocdt of=/sdcard/Download/ocdt.img
dd if=/dev/block/bootdevice/by-name/persist of=/sdcard/Download/persist.img
exit
exit

现在 ocdt.img 和 persist.img 都在手机的 Download 文件夹,将它们复制到电脑。

1
2
adb pull /sdcard/Download/ocdt.img
adb pull /sdcard/Download/persist.img

把前面记录的版本信息保存成文本文件,和这两个 .img 文件一起备份到网盘。

安装 Pixel OS #

参考以下资料安装 Pixel OS。

1
2
3
4
5
adb reboot bootloader
fastboot flash boot boot-aston-20241019.img
fastboot flash vendor_boot vendor_boot-aston-20241019.img
fastboot flash dtbo dtbo-aston-20241019.img
fastboot flash recovery recovery-aston-20241019.img

按音量键切换到 Recovery mode,按电源键确认。选择 Apply update -> Apply from ADB。

1
adb sideload PixelOS_aston-14.0-20241018-1605.zip

现在屏幕显示 Active slot: bDo you want to reboot to recovery now?,选择 Yes

现在屏幕显示 Active slot: a,选择 Apply update -> Apply from ADB,再次安装 PixelOS(文档写要安装两次,这里是错误操作,应该是进入系统后再次安装)。

1
adb sideload PixelOS_aston-14.0-20241018-1605.zip

这次报错了,先不管。

选择 Factory reset -> Format data/factory reset -> Format data,返回,选择 Reboot system now。可以开机。

开机连接 WIFI 后等了有两三分钟,显示 Slow connection,选择左边的 Set up offline。字体有点小,在设置里调大 Display size。

下拉通知栏,点击「Charging this device via USB」,选择 File Transfer。在电脑把 PixelOS_aston-14.0-20241018-1605.zip 复制到 Download 文件夹。

在手机打开 Settings -> System -> System updates -> Local update,选择刚刚的 .zip文件,点击 INSTALL,完成后点 Reboot,重启成功。

再重启一次,没事,应该算成功安装 PixelOS 了。

系统自带谷歌服务,用 YASNAC 测试 SafetyNet,Basic integrity 显示 pass。

再次测试硬件功能 #

参考前面的章节。既然在原装系统没事,我这次就懒得测了。

参考资料 #

OPPO / 一加 / ColorOS 的 Google Play 安装教程

2024年11月15日 00:00
  1. 开启谷歌服务:打开设置,搜索「google」,点击「Google 设置」,开启「Google 移动服务」。
  2. 安装 Google Play:打开应用商店,搜索「google play」,升级「谷歌应用市场」。升级完后桌面会出现「Play 商店」。

测试机型:一加 OnePlus Ace 3(ColorOS 14)、OPPO Reno12(ColorOS 14)。

概念解析:物理像素、逻辑像素、CSS 像素、物理分辨率、逻辑分辨率

2024年11月2日 00:00

物理(physical)表示实际的值,逻辑(logical)表示经过缩放的值。

物理像素 #

像素(pixel)是显示器的基本单位,一个像素显示一种颜色。为了和逻辑像素(logical pixel)区分,像素(pixel)又称物理像素(physical pixel)。

逻辑像素 / CSS 像素 #

逻辑像素(logical pixel),亦称 CSS 像素(CSS pixel),是经过操作系统缩放的像素。CSS 的 px 就是 CSS 像素。

物理分辨率 #

物理分辨率(physical resolution)表示像素的数量。1920×1080 分辨率就表示显示器水平方向有 1920 个像素,垂直方向有 1080 个像素,共 2073600 个像素。尺寸相同时,显示器分辨率越高,显示效果就越精细。

逻辑分辨率 #

逻辑分辨率(logical resolution)是经过系统缩放的分辨率。

我用 1920×1080 分辨率,27 英寸的显示器,文字看得清楚。假如我换成同尺寸 3840×2160 的显示器,此时显示器像素数是前者的 4 倍,可以容纳更多的文字,文字会变得小得看不清。在浏览器按几下 Ctrl - 调节缩放也可以看到过小的文字。

在系统设置 200% 缩放后,分辨率就变成了 (3840/2)×(2160/2),也就是 1920×1080,此时文字大小又正常了。3840×2160 是物理分辨率,1920×1080是逻辑分辨率。

相关的 Web API #

以 2560×1440 物理分辨率,200% 缩放的显示器为例,展示相关 Web API 的用法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 逻辑宽度
console.log(window.screen.width) // 1280
// 逻辑高度
console.log(window.screen.height) // 720
// 缩放(物理高度/逻辑高度)
console.log(window.devicePixelRatio) // 2
// 物理宽度,需要借助 devicePixelRatio 来计算
console.log(window.screen.width*window.devicePixelRatio) // 2560
// 物理高度,需要借助 devicePixelRatio 来计算
console.log(window.screen.height*window.devicePixelRatio) // 1440

在线查看分辨率 #

我写了个查看物理分辨率、逻辑分辨率和视口大小的网站,欢迎使用。

网站链接:https://resolution-viewer.cyrusyip.org/

源代码:https://github.com/CyrusYip/resolution-viewer

jQuery 使用笔记

2024年10月23日 00:00

jQuery 是一个经典的 JavaScript 库,其功能为修改 HTML 元素、处理事件、制作动画、发送请求(Ajax)。jQuery 首次发布于 2006 年 8 月 26 日,现在(2024 年)已经 18 岁了。截止于 2024 年 10 月 23 日,有 75.8% 的网站使用了 jQuery1

虽然 jQuery 的使用率高,但新项目没必要用 jQuery,原生 JavaScript 已经可以实现 jQuery 的操作,参看 You Might Not Need jQueryYou-Dont-Need-jQuery。维护老项目时可能会用到 jQuery。

jQuery 的引入方法很多,我在本文采用 CDN 引入。

1
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>

下面介绍一些常见用法。

获取元素 #

jQuery 使用 CSS 选择器获取元素。

1
2
3
$(document) // 获取整个文档
$("#id1") // 获取 id 为 id1 的元素
$(".class1") // 获取 class 为 class1 的元素

插入元素 #

1
2
<!-- HTML 示例 -->
<div id="container"><p>Container</p></div>
1
2
3
4
$("#container").append("<p>inside-end</p>"); // 在 #container 末尾插入子元素
$("#container").prepend("<p>inside-start</p>"); // 在 #container 开头插入子元素
$("#container").after("<p>outside-end</p>") // 在 #container 后面插入兄弟元素
$("#container").before("<p>outside-start</p>") // 在 #container 前面插入兄弟元素

现在页面上的元素是这样的:

1
2
3
4
5
6
7
<p>outside-start</p>
<div id="container">
 <p>inside-start</p>
 <p>Container</p>
 <p>inside-end</p>
</div>
<p>outside-end</p>

取值与赋值 #

jQuery 使用同一个函数来取值和赋值,根据参数数量进行不同操作。

1
2
<!-- HTML 示例 -->
<h1 id="title">Title</h1>
1
2
3
4
$("#title").html() // 获取 #title 的值
$("#title").html("Another title") // 修改 #title 的值
$("#title").width() // 获取 #title 的宽度
$("#title").width(1) // 修改 #title 的宽度

链式调用 #

jQuery 的每个操作都会返回 jQuery 对象,所以可以进行连续操作。

1
2
3
4
5
<!-- HTML 示例 -->
<div id="container">
 <p>Paragraph 1</p>
 <p>Paragraph 2</p>
</div>
1
2
3
4
5
6
7
$("div") // 选择 div 元素
 .find("p") // 查找 div 里的 p 元素
 .eq(1) // 选择第 2 个元素
 .html("end") // 把第 2 个元素内容修改为 end
 .end() // 退回到上一个选中的元素,也就是 .find("p")
 .eq(0) // 选择第 1 个元素
 .html("start") // 将第 1 个元素内容修改为 start

现在页面是 start 在上面,end 在下面。

资料 #

优化 Hugo 博客速度:Pjax、dynamic script、preload、minification

2024年10月18日 00:00

本文介绍了优化博客速度的几个方式:Pjax(免刷新加载页面)、dynamic script(动态插入脚本)、rel=preload(预加载)、minification(极简化)。

本博客是 Hugo 生成的静态网站,感觉访问速度还不算慢。有天我看别人的博客(大概是单页应用),发现点击链接居然没刷新网页就加载了新页面,速度非常快。那时我想:要是我的 Hugo 博客也能这么流畅就好了。我感觉把博客改成单页应用要耗费不少时间,遂作罢。

后来我发现可以用 Pjax 技术让静态网站实现免刷新加载页面。Pjax 的意思是 pushState(修改 URL)+ Ajax(asynchronous JavaScript and XML,发送请求)。通俗来说,Pjax 就是请求网页、替换内容、修改 URL,这个过程比加载整个页面更快。

搜索 Pjax 库,找到两个好几年没更新的库(MoOx/pjaxdefunkt/jquery-pjax),我不想用不维护的库。我快放弃的时候发现了一个支持 Pjax 的 Hugo 主题:hugo-theme-luna,从自述文件可以看出 Pjax 是用 swup 实现的,然后我就用它了。

Pjax 免刷新加载页面 #

如果网站没有 JavaScript 代码,那直接加载 swup 就好了。

建议使用这些插件:

  • Head Plugin:刷新 <head> 元素的内容和 <html> 元素的 lang 属性。
  • Preload Plugin:光标在链接时预加载 URL,用户点击时就会内容会马上替换,还可以配置自动加载出现在可见区域的链接。
  • Progress Bar Plugin:加载时间较长时显示进度条。

注意要先加载插件再加载 swup。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<script src="https://unpkg.com/@swup/head-plugin@2"></script>
<script src="https://unpkg.com/@swup/preload-plugin@3"></script>
<script src="https://unpkg.com/@swup/progress-plugin@3"></script>
<script src="https://unpkg.com/swup@4"></script>

<script>
 const swup = new Swup({
 containers: ["body"], // 替换 <body> 的内容
 plugins: [
 new SwupHeadPlugin(),
 new SwupPreloadPlugin({ preloadVisibleLinks: true }), // 预加载页面可见的链接
 new SwupProgressPlugin(),
 ],
 });
</script>

我的博客用到了 3 个 JavaScript 程序:Google Analytics(流量统计)、Giscus(评论服务)、Disqus(评论服务),用了 swup 之后要考虑是否需要额外处理。

读者点击链接加载新页面后的处理:

Dynamic Script 动态插入脚本 #

我写了个动态插入脚本的函数,可以设置等待时间、加载前执行的函数、async、加载后执行的函数、attribute。

 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
function loadScript({
 url,
 delay,
 onloadCallback,
 async,
 preloadCallback,
 attributes = {},
}) {
 function load() {
 if (preloadCallback) preloadCallback();

 const script = document.createElement("script");
 script.src = url;
 script.async = async;

 // Set attributes
 Object.entries(attributes).forEach(([key, value]) => {
 script.setAttribute(key, value);
 });

 if (onloadCallback) script.onload = onloadCallback;

 document.body.appendChild(script);
 }

 if (typeof delay !== "undefined") {
 setTimeout(load, delay);
 } else {
 load();
 }
}

我给 Google Analytics、Giscus、Disqus 加了 100ms 等待时间,给 swup 添加了 200ms 等待时间。

rel=preload 预加载 #

<link> 标签使用 rel="preload" 可以让浏览器提前下载资源(字体、图片、脚本、样式等),后面动态插入脚本时浏览器就可以马上执行下载好的脚本。

示例:

1
2
3
4
5
<head>
 <link rel="preload" as="script" href="https://unpkg.com/@swup/head-plugin@2">
 <link rel="preload" as="script" href="https://unpkg.com/@swup/preload-plugin@3">
 <link rel="preload" as="script" href="https://unpkg.com/swup@4">
</head>

preload 应该叫 predownload,因为它并不会提前加载脚本,只是提前下载。

Minification 极简化 #

这次优化博客没用到极简化,不过既然讲到优化博客了,就顺便说一下吧。极简化就是移除代码中不必要的内容(比如:空格、空行、缩进),从而减小文件大小,这样浏览器就能更快下载完网页。

Hugo 可以极简化 HTML、CSS 和 JavaScript 文件。本博客的代码用 Hugo 极简化了。举个例子,样式文件大小原本是 13.8 kB,经过极简化后是 7.4 kB(原文件的 53.6%),传输过程中再经过 Brotli 压缩后是 2.1 kB(原文件的 15.2%)。

相关代码 #

以下是这次优化用到的代码:

感想 #

我已经用 swup 两三天了,非常满意,真没想到静态网站可以变成单页应用。Preload 插件能让浏览器会自动提前加载页面,读者点击链接时页面会瞬间加载,这个感觉太爽啦!

参考资料 #

将 LineageOS 20 升级到 LineageOS 21

2024年10月11日 00:00

我手机用的系统是开源的 LineageOS 20(Android 13),跨版本升级到 LineageOS 21(Android 14)需要用电脑手动操作。因为怕操作失误导致无法开机,所以想着先备份资料,但我懒得备份资料,于是好几个月没升级。今天就来升级了。

备份 #

虽然升级 LineageOS 不会清空储存空间,但为了保险起见,我按照自己的需求备份以下内容:

  1. 微信聊天记录
  2. 图片
  3. 电子邮箱账户截图
  4. 闹钟截图
  5. 应用列表

升级 #

按照 Upgrade LineageOS on lisa | LineageOS Wiki 的步骤,安装 LineageOS 21、重启到 Recovery、安装 Google Apps、重启。

这里碰到一个奇怪的问题:手机通过数据线直接插到笔记本电脑的 USB 口,用 fastboot 可以检测到设备,但安装新系统时会报错。数据线插到 USB 集线器才行。

感受 #

升级过程比我预想的简单。系统正常运作,所以清除了前面的备份。目前遇到两个问题:状态栏的快捷设置在亮色模式下仍然是暗色;Fcitx5 输入法底下的导航栏不见了,将「导航栏背景」设置为「跟随键盘背景色」即可解决。

如果你也要升级 LineageOS,不要照搬我的步骤,要遵循 LineageOS Wiki 的步骤,不同设备的升级步骤可能有差异。

开放的心态

2024年10月1日 00:00

在网上看到有人争论同类事物的好坏(比如:Vim 和 VS Code、Arch linux 和 Ubuntu),我认为没有必要去争。纠结哪个最好容易导致无尽的争吵。没有完美的工具,我们应该去思考工具的特点和适用场景。两个工具适用场景相似时可以先随便选一个,不要追求完美,适合就继续用,不适合就换另一个。

拿厨刀举例,主厨刀(chef’s knife)和切片刀哪个好?这个问题没有绝对的答案。

主厨刀
切片刀

主厨刀更轻更小,刀尖有弧度。刀尖有弧度就可以用摇刀切(rock chop),适合切蒜末。

切片刀更重,用它切菜时更流畅,使用者更省力。切片刀更宽大,可以把食材拍扁,还可以把切好的食材铲起来倒入锅中。

所以哪把刀好还是得看使用者的偏好,如果你喜欢轻巧就用主厨刀,喜欢功能多就用切片刀。

当然其实这两把刀都能切蔬菜和肉,不是很了解的情况下任选一把即可。选其他工具也一样,没实际用过就感受不到细微的差别,与其纠结,不如先随便挑一个用着。


图片出处:

冷泡茶教程

2024年9月25日 00:00

冷泡茶是用低温水泡的茶,苦味比热泡茶淡,有清凉感。

材料与工具 #

  • 茶叶(红茶、绿茶等)
  • 水壶
  • 冰箱

流程 #

  1. 茶叶与冷水的重量比例为 1:50,将茶叶与冷水加到到水壶,关闭盖子。
  2. 将水壶放入冰箱冷藏室,泡 4~8 小时。可以睡前泡,这样第二天早上就能喝了。

补充资料 #

QWERTY 键盘标点符号的中英文名称

2024年9月20日 00:00

AmE 表示 American English(美式英语),BrE 表示 British English(英式英语),表格的空白表示没有对应名称。

标点 英文 中文
~ tilde 波浪号
! exclamation point (AmE) / exclamation mark (BrE) 感叹号
@ at sign / at symbol
# hash / pound sign / number sign 井号
$ dollar sign 美元符号
% percent sign 百分号
^ caret 脱字符
& ampersand / and sign
* asterisk 星号
( ) parenthesis (AmE) / bracket (BrE) / round bracket (BrE) 圆括号
- hyphen / minus sign 连字符/减号
+ plus sign 加号
` backtick/backquote/grave 反引号/重音符
_ underscore 下划线
= equal sign (AmE) / equals sign (BrE) 等号
{ } brace / curly bracket 花括号
| verticle bar / pipe 竖线/管道
[ ] square bracket 方括号
\ backslash 反斜线/反斜杠
: colon 冒号
" double quote / double quotation mark 双引号
; semicolon 分号
' apostrophe / single quote / single quotation mark 撇号/单引号
< less-than sign 小于号
> greater-than sign 大于号
? question mark 问号
, comma 逗号
. period (AmE) / full stop (BrE) 句号
/ slash / forward slash (正)斜线/(正)斜杠

参考资料:Wikipedia, the free encyclopedia

少囤草稿,尽快发布

2024年9月19日 00:00

笔记软件里面的草稿越来越多,但是发布的文章却越来越少了。造成这个情况的原因有:用于写博客的时间少了、对文章的要求过高。囤着草稿是不好的,写好的文章应该发出来,为了帮助自己和他人。

刚写博客那会,我对文章内容没有太高要求,就是想着写好后几天内改几次(吐槽:这要求还不高吗),力求通俗易懂。后来写作要求提升了两次。

第一次提升要求是因为我查 Linux 资料时经常看 ArchWiki。ArchWiki 的内容又新又全面,而且注册个账号就可以贡献内容,我自己也贡献了一些内容。在这个过程中我觉得知识应该像 ArchWiki 那样,集中在一个地方,每个话题都有单独的页面,还方便大家贡献。慢慢我就不想写那些别人写过的内容,除非我能写得更好。能查到的内容就没必要写了。这就是程序员说的「Don’t repeat yourself」,不要重复产生信息。

第二次提升要求是因为我写出了排在谷歌搜索首位的文章,这给网站带来了持续的访问量,会有更多人看到我的博客,我很开心。在此之前,我一直觉得推广博客很难,到处去分享感觉就像滥发消息(spamming)。从此之后,我会优先写别人没写过的话题,写重复的话题尽量保证质量好到能排到谷歌搜索的第一页。

最近我刻意改变,将文章的重心放到帮助自己,降低要求。要求就两个:自己能读懂(可以对别人来说没那么通俗)、完整(不要写一半就发,写完初稿就可以发了)。发稿速度马上就上涨了,感觉非常好。

最近发布了四篇文章:

Grid 教程和 Flexbox 教程都是参考 CSS-Tricks 的教程写的。之前我也看过 CSS-Tricks 那两篇教程(CSS Flexbox Layout Guide | CSS-TricksCSS Grid Layout Guide | CSS-Tricks),但是自己写教程的过程中对两个 CSS 布局的理解更深入了,对我来说很有用。写好之后我哪里不懂也可以看自己写的教程,自己写的教程查起来更方便。写重复的话题可以加深理解,这也不意味着做复读机,我在写的过程中会有新的感悟,写出一些新内容。

写博客就像做开源项目,要让自己感觉舒服,对自己有用,这样才容易坚持。

菜谱:酱油蒜香炒蛋

2024年9月18日 00:00

材料 #

  • 蒜末
  • 酱油

步骤 #

  1. 将蛋打入碗中,不要搅拌
  2. 加酱油,搅拌均匀蛋黄和蛋清
  3. 加蒜末
  4. 下锅前搅拌
  5. 煎蛋或者炒蛋

备注 #

  • 可用鸡蛋或者鸭蛋,混蛋也行
  • 可用刀将蒜头切成蒜末,越碎越好
  • 搅拌蛋后再加酱油就看不清加了多少酱油
  • 蒜末会沉淀,所以下锅前搅拌

用柠檬酸和热水去除电热水瓶和保温瓶的水垢

2024年8月31日 00:00

柠檬酸是天然的清洁剂,可去除电热水瓶和保温瓶内的顽固水垢。它的用法是先浸泡,后清洗。如果水垢未完全清除,可以重复上述步骤。

材料 #

  • 柠檬酸除垢剂

电热水瓶浸泡方法 #

  1. 倒入冷水和柠檬酸(用量请参考说明书)。
  2. 煮沸后继续浸泡 30 分钟。

保温瓶浸泡方法 #

  1. 先倒入热水,再倒柠檬酸。
  2. 浸泡 3~4 小时。

清洗方法 #

电热水瓶和保温瓶的清洗方法是一样的。

  1. 倒掉混合液。
  2. 用清水冲洗。
  3. 如果冲洗后还有水垢,可以用厨房抹布擦。如果手无法伸入瓶内,可以用筷子抵住抹布擦拭。

CSS Grid 布局教程

2024年9月16日 00:00

CSS Grid 是二维布局方法,也就是用竖线和横线将内容划分成格子,像棋盘一样。本文只介绍常见用法,要了解全部用法请看 MDN Web Docs。推荐大家看完后做文末提到的习题。

概念 #

网格容器(Grid Container)、网格项(Grid Item) #

display: grid | inline-grid 使元素变成网格容器,其子元素叫网格项(其他后代不算),按照网格布局排列。下面代码的 .container 是网格容器,.item 是网格项,.sub-item 不是网格项。

1
2
3
.container {
 display: grid;
}
1
2
3
4
5
6
7
<div class="container">
 <div class="item"></div>
 <div class="item">
 <p class="sub-item"></p>
 </div>
 <div class="item"></div>
</div>

网格线(Grid Line) #

划分网格的竖线(vertical line)或横线(horizontal line)。横线方向和书写方向一致(英文是从左到右),竖线方向是从上到下。同一条网格线可以有多个名称。

虚线是网格线

网格单元(Grid Cell) #

网格布局的最小单位,就像电子表格的单元格和棋盘的格子,相邻的 2 个竖线之间和相邻的 2 条横线之间的区域。1 个网格项可以使用多个网格单元。

网格轨道(Grid Track) #

2 条相邻网格线之间的区域,也就是 1 个横排(row)或者 1 个竖排(column)。

网格区域(Grid Area) #

由 4 条网格线划分的区域,也就是长方形,比如 1 个网格单位、4 个网格单位、6 个网格单位。

示例(A 表示网格区域):

1
2
A???
????
1
2
AA??
AA??
1
2
AAA?
AAA?

显性网格(Explicit Grid)、隐性网格(Implicit Grid) #

显性网格有固定数量的网格轨道,比如 3×3。如果此时加入额外的网格项,网格将自动添加 auto 尺寸的隐性网格轨道,原来的显性网格轨道加上自动添加的隐性网格轨道就是隐性轨道。只定义竖网格线也可以产生隐性网格。

fr(fraction)1 份可用空间 #

fr 表示网格容器的 1 份可用空间。

1
2
3
4
5
6
7
.container {
 display: grid;
 /* 3 个竖排,比例为 1:2:1 */
 grid-template-columns: 1fr 2fr 1fr;
 /* 3 个横排,第 1 横排为 30px,剩余横排比例为 1:1 */
 grid-template-rows: 30px 1fr 1fr;
}

minmax() 最大最小值 #

参考资料:minmax() - CSS: Cascading Style Sheets | MDN

minmax() 函数设置最小值和最大值。下面代码表示第 1 横排的尺寸最小 50px,最大 auto(根据内容自动扩大)。

1
2
3
.container {
 grid-template-rows: minmax(50px, auto) 1fr 1fr;
}

repeat() 函数 #

参考资料:repeat() - CSS: Cascading Style Sheets | MDN

repeat() 函数用于表示重复的网格轨道片段。

在线示例

1
2
3
.container {
 grid-template-rows: repeat(3, 1fr); /* 1fr 1fr 1fr */
}

在线示例

1
2
3
.container {
 grid-template-rows: 2fr repeat(2, 1fr); /* 2fr 1fr 1fr */
}

除了使用固定的数字,还可用 auto-fitauto-fill

网格容器的属性 #

网格容器可以使用以下属性。

  • display
  • grid-template-columns
  • grid-template-rows
  • grid-template-areas
  • grid-template
  • grid-column-gap
  • grid-row-gap
  • grid-gap
  • justify-items
  • align-items
  • place-items
  • justify-content
  • align-content
  • place-content
  • grid-auto-columns
  • grid-auto-rows
  • grid-auto-flow
  • grid

display 设置容器 #

对元素使用,使其变成网格容器。

1
2
3
.container {
 display: grid | inline-grid;
}

值:

  • grid:生成 block 级网格容器
  • inline-grid:生成 inline 级网格容器

grid-template-columns、grid-template-rows,网格线名称与网格轨道 #

参考资料:

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="container">
 <div class="item item1">1</div>
 <div class="item item2">2</div>
 <div class="item item3">3</div>
 <div class="item item4">4</div>
 <div class="item item5">5</div>
 <div class="item item6">6</div>
 <div class="item item7">7</div>
 <div class="item item8">8</div>
 <div class="item item9">9</div>
</div>
1
2
3
4
5
6
7
8
9
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 /* 3 个竖排,比例为 1:2:1 */
 grid-template-columns: 1fr 2fr 1fr;
 /* 3 个横排,第 1 横排为 30px,剩余横排比例为 1:1 */
 grid-template-rows: 30px 1fr 1fr;
}

现在网格项排列成这样:

1
2
3
1 2 3
4 5 6
7 8 9

可以用 [] 定义网格线名称,用空格分隔多个名称。

1
2
3
.container {
 grid-template-columns: [column1-start] 1fr [column2-start] 2fr [column3-start] 1fr [column-end another-name];
}

grid-column-startgrid-row-start 可以改变网格项的位置,在线示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.container {
 display: grid;
 grid-template-columns: [column1-start] 1fr [column2-start] 2fr [column3-start] 1fr [column-end];
 grid-template-rows: 30px 1fr 1fr;
}

.item9 {
 grid-column-start: column2-start;
 grid-row-start: 2; /* 第 2 条横线 */
}

现在 .item9 占据了 .item5 的位置。

1
2
3
1 2 3
4 9 5
6 7 8

grid-template-areas 网格区域名称 #

参考资料:grid-template-areas - CSS: Cascading Style Sheets | MDN

grid-template-areas 以网格区域名称表示网格的结构。grid-template-areas 的优点是放置网格项时不需要用网格线(数网格线或者命名真的太痛苦了)。相同名称可以用多次,表示占用多个网格单元。英文句号 . 表示不使用此网格单元。grid-area 定义元素对应的网格区域名称。

grid-template-areas 会自动使用 -start-end 命名网格线。header 网格区域的起始网格线(横线与竖线)都是 header-start,终止网格线(横线与竖线)都是 header-end。同一条网格线可以有多个名称。

示例(在线版):

1
2
3
4
5
6
<div class="container">
 <header>header</header>
 <aside>sidebar</aside>
 <main>main</main>
 <footer>footer</footer>
</div>
 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
header,aside,main,footer {
 border: 1px solid green;
}

.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template-areas:
 "header header header header"
 "main main . aside"
 "footer footer footer footer";
 grid-template-columns: 1fr 1fr 1fr 50px;
 grid-template-rows: auto;
}

header {
 grid-area: header;
}
aside {
 grid-area: aside;
}
main {
 grid-area: main;
}
footer {
 grid-area: footer;
}

grid-template(grid-template-rows、grid-template-columns、grid-template-areas 的缩写) #

参考资料:

grid-templategrid-template-rowsgrid-template-columnsgrid-template-areas 的缩写。grid-template 不会重置隐性网格属性。

只设置 grid-template-rowsgrid-template-columns 的用法是用 / 隔开两者。

示例(在线版):

1
2
3
4
5
6
7
8
9
.container {
 display: grid;
 /* 横排比例 1:1:1,竖排比例 1:3:1 */
 grid-template: 1fr 1fr 1fr / 1fr 3fr 1fr;
 /* 等价于
 grid-template-rows: 1fr 1fr 1fr;
 grid-template-columns: 1fr 3fr 1fr;
 */
}

同时设置 grid-template-rowsgrid-template-columnsgrid-template-areas 的用法:

  1. 先写 grid-template-areas
  2. 在每横排右边写上尺寸
  3. 在最后的横排加上 / 和竖排的尺寸(/ 从下一行开始写也行)

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template:
 "header header header" auto
 "main main aside " 1fr
 "footer footer footer" auto / auto auto 50px;
 /* 等价于
 grid-template-areas:
 "header header header"
 "main main aside"
 "footer footer footer";
 grid-template-columns: auto auto 50px;
 grid-template-rows: auto 1fr auto;
 */
}

row-gap、column-gap、gap 间隔 #

参考资料:

row-gap 表示横排之间的间隔,column-gap 表示竖排之间的间隔。gap 是前面两者的缩写,使用 1 个值表示横排间隔和竖排间隔一样,使用 2 个值分别表示横排间隔是竖排间隔。

示例:

1
2
3
4
5
6
.container {
 row-gap: 30px;
 column-gap: 10px;
 gap: 10px; /* 竖排间隔和横排间隔都是 10px */
 gap: 30px 10px; /* 横排间隔 30px,竖排间隔 10px */
}

以前这 3 个属性前面要加上 grid-,比如 grid-row-gap。带 grid- 前缀的属性已被弃用,浏览器为了保持兼容,仍然支持这些属性。

justify-items,网格项的 inline 轴(横轴)对齐 #

参考资料:

justify-items 设置网格项 inline 轴(横轴)对齐方式,默认值为 stretch(占满网格单元宽度)。

1
2
3
.container {
 justify-items: start | end | center | stretch;
}

align-items,网格项的 block 轴(竖轴)对齐 #

参考资料:

align-items 设置网格项 block 轴(竖轴)的对齐方式,默认值为 stretch(占满网格单元高度)。baseline 表示按基线对齐。内容有多行时,first baseline 表示按照首行的基线对齐,last baseline 表示按照尾行的基线对齐。

1
2
3
.container {
 align-items: start | end | center | stretch | baseline | first baseline | last baseline;
}

place-items(align-items、justify-items 的缩写) #

参考资料:

place-itemsalign-itemsjustify-items 的缩写。使用 1 个值同时设置 2 个属性,使用 2 个值分别设置两个属性。

1
2
3
4
.container {
 place-items: center; /* 正中间 */
 place-items: start end; /* 右上角 */
}

justify-content,网格项整体 inline 轴(横轴)对齐 #

参考资料:

如果网格项的总尺寸小于网格容器的尺寸,网格容器会有多于的空白,此时网格项被放置于左上角(使用从左向右的语言)。justify-content 设置 inline 轴(横轴)的对齐方式。Flex 布局也有 justify-content,用法类似,参看 CSS Flexbox 布局教程#justify-content-主轴对齐(发布前看看这个链接对不对)。

在线示例

1
2
3
.container {
 justify-content: start | end | center | stretch | space-around | space-between | space-evenly; 
}

align-content,网格项整体的 block 轴(竖轴)对齐 #

参考资料:

如果网格项的总尺寸小于网格容器的尺寸,网格容器会有多于的空白,此时网格项被放置于左上角(使用从左向右的语言)。align-content 设置 block 轴(竖轴)的对齐方式。Flex 布局也有 align-content,用法类似。

在线示例

1
2
3
.container {
 align-content: start | end | center | stretch | space-around | space-between | space-evenly; 
}

place-content(align-content、justify-content 的缩写) #

参考资料:place-content - CSS: Cascading Style Sheets | MDN

place-contentalign-contentjustify-content 的缩写。用 1 个值同时设置两者,用 2 个值分别设置两者。

示例(在线示例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.container {
 place-content: start end;
 /*
 align-content: start;
 justify-content: end;
 */
 
 place-content: center;
 /*
 align-content: center;
 justify-content: center;
 */
}

grid-auto-columns、grid-auto-rows 隐性网格轨道大小 #

参考资料:

如果网格项数量多于已定义的网格项数量,那么多出了网格项就位于隐性网格轨道。grid-auto-columns 设置竖向隐性网格轨道大小,grid-auto-rows 设置横向隐性网格轨道大小,两者默认值都是 auto

在线示例

1
2
3
.container {
 grid-auto-rows: 100px;
}

grid-auto-flow 自动放置算法 #

参考资料:

1
2
3
.container {
 grid-auto-flow: row | column | row dense | column dense;
}

grid-auto-flow 设置网格项的放置算法。

row(默认值):横向放置网格项,必要时添加新的横排。

1
2
3
1 2 3
4 5 6
7 8 9

column:竖向放置网格项,必要时添加新的竖排,在线示例

1
2
3
1 4 7
2 5 8
3 6 9

dense 关键词表示后面的元素可以移动到前面的空位,示例(在线示例):

1
2
3
4
5
6
7
8
<div class="container">
 <div class="item item1">1</div>
 <div class="item item2">2</div>
 <div class="item item3">3</div>
 <div class="item item4">4</div>
 <div class="item item5">5</div>
 <div class="item item6">6</div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
.item {
 border: 1px solid green;
}

.container {
 border: 1px solid red;
 height: 200px;
 width: 200px;
 display: grid;
 grid-template-columns: 1fr 1fr 1fr;
 grid-auto-flow: row dense; /* .item3 提前放置于 .item1 右边 */
}

.item1, .item2 {
 grid-column: span 2; /* 2fr */
}

grid( grid-template-rows、grid-template-columns、grid-template-areas、grid-auto-rows、grid-auto-columns、grid-auto-flow 的缩写) #

参考资料:

gridgrid-template-rowsgrid-template-columnsgrid-template-areasgrid-auto-rowsgrid-auto-columnsgrid-auto-flow 的缩写。

创建显性网格时用法与 grid-template 一样。

创建隐性网格时有 2 种用法(左边设置横排,右边设置竖排,中间以 / 分隔):

  1. 显性横排,隐性竖排,grid-auto-flowcolumn<grid-template-rows> / [ auto-flow && dense? ] <grid-auto-columns>?
  2. 隐性横排,显性竖排,grid-auto-flowrow[ auto-flow && dense? ] <grid-auto-rows>? / <grid-template-columns>

显性横排隐性竖排示例(在线版):

1
2
3
4
.container {
 /* 横排比例 1:2:1,按照竖排方向放置网格项,竖排尺寸 50px */
 grid: 1fr 2fr 1fr / auto-flow 50px;
}

隐性横排显性竖排示例(在线版):

1
2
3
4
.container {
 /* 隐性横排,按照横排方向放置网格项,竖排比例 1:2:1 */
 grid: auto-flow / 1fr 2fr 1fr;
}

网格项的属性 #

网格项可以使用以下属性:

  • grid-column-start
  • grid-column-end
  • grid-row-start
  • grid-row-end
  • grid-column
  • grid-row
  • grid-area
  • justify-self
  • align-self
  • place-self
  • subgrid
  • order

grid-column-start、grid-column-end、grid-row-start、grid-row-end 网格项位置 #

参考资料:

这几个属性通过网格线的起点与终点规定 1 个网格项的位置,默认值为 auto(自动放置)。span 数字 表示占用多少个网格轨道,不加数字时为 1。

示例(在线版):

1
2
3
4
5
6
7
8
<div class="container">
 <div class="item item1">1</div>
 <div class="item item2">2</div>
 <div class="item item3">3</div>
 <div class="item item4">4</div>
 <div class="item item5">5</div>
 <div class="item item-x">x</div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template: 1fr 1fr 1fr / 1fr [col2-start] 1fr 1fr;
}

.item {
 border: 1px solid green;
}

.item-x {
 /* 从 col2-start 竖网格线到倒数第 1 条竖网格线 */
 grid-column-start: col2-start;
 grid-column-end: -1;
 /* 从第 2 条横网格线开始,占用 2 个横向网格轨道 */
 grid-row-start: 2;
 grid-row-end: span 2;
}

grid-column(grid-column-start、grid-column-end 的缩写)、grid-row(grid-row-start、grid-row-end 的缩写) #

参考资料:

grid-columngrid-column-start / grid-column-end 的缩写,使用 1 个值时只设置 grid-column-start

grid-rowgrid-row-start / grid-row-end 的缩写,使用 1 个值时值设置 grid-row-start

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.item-x {
 grid-column: col2-start / -1;
 grid-row: 2 / span 2;
 /*
 grid-column-start: col2-start;
 grid-column-end: -1;
 grid-row-start: 2;
 grid-row-end: span 2; 
 */
}

grid-area(grid-row-start、grid-column-start、grid-row-end、grid-column-end 的缩写) #

grid-area 有两个用法。一是 grid-row-start / grid-column-start / grid-row-end / grid-column-end 的缩写(吐槽:这个顺序不好读,-start 后面应该跟 -end);二是搭配 grid-template-area,使用名称指定位置。

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.item-x {
 grid-area: 2 / col2-start / span 2 / -1;
 
 /*
 grid-column: col2-start / -1;
 grid-row: 2 / span 2;
 
 grid-column-start: col2-start;
 grid-column-end: -1;
 grid-row-start: 2;
 grid-row-end: span 2; 
 */
}

justify-self,inline 轴(横轴)对齐 #

justify-self 设置 1 个网格项的 inline 轴(横轴)对齐方式,默认值是 stretch

1
2
3
.item {
 justify-self: start | end | center | stretch;
}

在线示例

1
2
3
.item4 {
 justify-self: center;
}

align-self,block 轴(竖轴)对齐 #

align-self 设置 1 个网格项的 block 轴(竖轴)对齐方式,默认是 stretch

1
2
3
.item {
 align-self: start | end | center | stretch;
}

在线示例

1
2
3
.item4 {
 align-self: end;
}

place-self(align-self、justify-self 的缩写) #

参考资料:

place-selfalign-self justify-self 的缩写,只使用 1 个值时同时设置两者。

示例:

1
2
3
4
.item4 {
 place-self: center; /* 正中间 */
 place-self: end start; /* 左下角 */
}

subgrid 继承网格容器属性 #

参考资料:Subgrid - CSS: Cascading Style Sheets | MDN

虽然 subgrid 不是属性,但用于网格项,所以列举于此。

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="container">
 <div class="item item1">Item1</div>
 <div class="item item2">Item2</div>
 <div class="item item3">Item3</div>
 <div class="item item4">Item4</div>
 <div class="item item5">Item5</div>
 <div class="item item6">Item6, 4×4
 <div class="item6-1">Item6.1, 1×1</div>
 <div class="item6-2">Item6.2, 1×1</div>
 </div>
</div>
 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
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template: 1fr 1fr 1fr / 1fr 1fr 1fr;
 gap: 10px;
}

.item {
 border: 1px solid green;
}

.item6 {
 border: 1px solid yellow;
 grid-area: 2 / 2 / span 2 / span 2;
 display: grid;
 /*
 用 subgrid 才能继承容器的 gap 属性,
 可以删掉下面两行代码看看差异 */
 grid-template-columns: subgrid;
 grid-template-rows: subgrid;
}

.item6-1 {
 border: 1px solid blue;
 /* 使用 .item6 的网格线编号,不要用 .container 的网格线编号 */
 grid-column-start: 2;
 grid-row-start: 2;
}

.item6-2 {
 border: 1px solid blue;
 grid-column-start: 2;
 grid-row-start: 1;
}

order 顺序 #

参考资料:order - CSS: Cascading Style Sheets | MDN

网格项默认按照源代码顺序出现。order 设置网格项的出现顺序,默认值为 0,可使用正数和负数。

示例(在线版):

1
2
3
4
5
6
.item1 {
 order: 1;
}
.item9 {
 order: -1;
}

待写内容 #

参考资料 #

图片出处 #

本文使用的图片出自 CSS Grid Layout Module Level 1#grid-concepts

练习 #

CSS BEM 命名规范入门教程

2024年9月8日 00:00

BEM(Block, Element, Modifier)是 HTML/CSS 类的命名方法,它可以让 HTML 和 CSS 代码更有条理。

概念与用法 #

一开始看不懂没关系,后面有示例。

  • block(块):可以独立使用的 HTML 元素(比如:<nav>),可以不包含 element。
  • element(元素):依附于 block 的 HTML 元素,无法独立存在(比如:<li>),前面要加上双下划线 __
  • modifier(修饰符):表示 block 或者 element 的状态和外观,前面要加上双连字符 --
  • 用单连字符 - 连接单词,比如:search-form
  • element 只属于 block,而不是另一个 element。错误写法:block__element1__element2,正确写法:block__element2
  • 使用 modifier 时,同时保留不含 modifier 的类名,比如:<a class="menu__link menu__link--active" href="/zh-cn/">主页</a>

HTML 示例 #

<nav> 元素是 block,它包含的 element 有 <ul><li><a>--active 是修饰符。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<nav class="menu">
 <ul class="menu__list">
 <li class="menu__item">
 <a class="menu__link menu__link--active" href="/zh-cn/">主页</a>
 </li>
 <li class="menu__item">
 <a class="menu__link" href="/zh-cn/posts/">文章</a>
 </li>
 <li class="menu__item">
 <a class="menu__link" href="/zh-cn/about/">关于</a>
 </li>
 </ul>
</nav>

CSS 示例 #

在线示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/* 横向列表 */
.menu__list {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap;
 justify-content: space-between;
 list-style-type: none;
 padding-inline-start: 0;
}

/* 加粗和当前页面匹配的链接 */
.menu__link--active {
 font-weight: bolder;
}

SCSS 示例 #

我更推荐用 SCSS,用父选择器 & 可以把 block 和 element 的样式都放在一起,这种结构可以清晰地展现它们的层级关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
.menu {
 // 横向列表
 &__list {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap;
 justify-content: space-between;
 list-style-type: none;
 padding-inline-start: 0;
 }

 // 加粗和当前页面匹配的链接
 &__link {
 &--active {
 font-weight: bolder;
 }
 }
}

替代方案:原子式 CSS #

如果你觉得 BEM 太冗长或者难以维护,那你可以试试原子式1 CSS 框架(atomic CSS framework),比如:Tailwind CSSUnoCSS。这些框架会提供 utility(预定义的类)。开发者只需要写预定义的类名,不需要写 CSS 代码。以下是一些 Tailwind CSS 的 utility:

1
2
3
4
5
6
.flex {
 display: flex;
}
.flex-row {
 flex-direction: row;
}

用 Tailwind CSS 重写之前的 CSS 代码(在线示例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<script src="https://cdn.tailwindcss.com"></script>
<script>
 tailwind.config = {
 corePlugins: {
 preflight: false,
 }
 }
</script>
<nav>
 <ul class="flex flex-row flex-wrap justify-between list-none ps-0">
 <li>
 <a class="font-bold" href="/zh-cn/">主页</a>
 </li>
 <li>
 <a href="/zh-cn/posts/">文章</a>
 </li>
 <li>
 <a href="/zh-cn/about/">关于</a>
 </li>
 </ul>
</nav>

补充资料 #


  1. atomic CSS 一般译为「原子化 CSS」,我认为这个翻译是错的。「化」表示状态变化,对应英文的「-ize」和「-ify」词缀,比如:净化(purify)、标准化(standardize)。atomic 可以拆分成 atom(原子)和 -ic(形容词后缀),所以我把 atomic 翻译成原子式,「式」表示形式、样式。atomic 也可以翻译成原子型,「型」表示类型、样式。 ↩︎

CSS Flexbox 布局教程

2024年9月6日 00:00

Flexbox(弹性盒子)是一维的布局方法,也就是在一条横线(row)或者竖线(column)上排列元素。推荐大家看完本文后做文末提到的习题

flex 容器的属性 #

flex 容器(flex container)的属性有 displayflex-directionflex-wrapflex-flowjustify-contentalign-itemsalign-contentrow-gapcolumn-gapgap

flex 容器与 flex 项 #

以下的 section 元素包含了 3 个 article 元素。

1
2
3
4
5
<section>
 <article>Article 1</article>
 <article>Article 2</article>
 <article>Article 3</article>
</section>

要排列 article 元素,将它们的父元素设置为 flex 容器。

1
2
3
section {
 display: flex | inline-flex; /* inline-flex 表示容器是 inline 元素 */
}

在上述例子中,section 元素是 flex 容器(flex container),article 元素是 flex 项(flex item)。

flex-direction 方向、主轴、交叉轴 #

flex-direction 定义排列方向和主轴。

1
2
3
.container {
 flex-direction: row | row-reverse | column | column-reverse;
}
  • row(默认值):横向,和文字方向(dir)一致,如果方向是从左往右,那么就是从左往右排列子元素
  • row-reverse:和前者相反,从右往左
  • column:纵向,从上到下
  • column-reverse:和前者相反,从下到上

主轴(main axis)就是和 flex 项排列方向一致的轴,交叉轴(cross axis)则是与主轴垂直的轴。一定要分清楚方向和两条轴,在对齐 flex 项时会用到。

1
2
3
4
.container {
 display: flex;
 flex-direction: row;
}

上述代码表示从左到右排列 flex 项,主轴是从左到右,交叉轴是从上到下。

轴的图示

flex-wrap 换行 #

flex-wrap 控制是否换行,默认不换行。

1
2
3
.container {
 flex-wrap: nowrap | wrap | wrap-reverse;
}
  • nowrap(默认值):全部 flex 项排列成一条线
  • wrap:溢出时换行
  • wrap-reverse:溢出时换行,但后面的元素会排到前面

示例代码(在线示例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<section>
 <article>Article 1</article>
 <article>Article 2</article>
 <article>Article 3</article>
 <article>Article 4</article>
 <article>Article 5</article>
 <article>Article 6</article>
 <article>Article 7</article>
 <article>Article 8</article>
 <article>Article 9</article>
</section>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
section {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap-reverse;
}

article {
 width: 100%;
 outline: 1px solid green;
}

flex-flow(flex-direction 和 flex-wrap 的缩写) #

flex-flowflex-directionflex-wrap 的缩写,默认值为 row nowrap

下面两份代码效果一样:

1
2
3
4
section {
 flex-direction: row;
 flex-wrap: wrap;
}
1
2
3
section {
 flex-flow: row wrap;
}

justify-content 主轴对齐 #

justify-content 设置主轴的对齐方式。

1
2
3
.container {
 justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly | start | end | left | right ... + safe | unsafe;
}

下面只介绍常用的值。

  • flex-start(默认):将所有 flex 项放到主轴的起点。如果主轴是 row(从左到右),那么所有 flex 项贴近左边。例子(. 表示空间):Item1Item2Item3......
  • flex-end:将所有 flex 项放到主轴的终点。例子:......Item1Item2Item3
  • center:将所有 flex 项放在主轴的中间。例子:....Item1Item2Item3....。
  • space-between:均匀分配 flex 项,每个 flex 项之间的距离一样,头部 flex 项贴近主轴开头,尾部 flex 项贴近主轴末尾,例子:Item1..Item2..Item3
  • space-around:均匀分配 flex 项,每个 flex 项的前后间隔一样,例子:.Item1..Item2..Item3.
  • space-evenly:均匀分配 flex 项,每个 flex 项之间的距离一样,头部 flex 项和主轴开头有一样的距离,尾部 flex 项和主轴末尾有一样的距离。例子:..Item1..Item2..Item3..

align-items 交叉轴对齐 #

交叉轴垂直于主轴。align-items 设置交叉轴的对齐方式。

1
2
3
.container {
 align-items: stretch | flex-start | flex-end | center | baseline | first baseline | last baseline | start | end | self-start | self-end + ... safe | unsafe;
}

align-items 用法和 justify-content 类似,这里介绍一些不一样的值。

  • stretch(默认值):使 flex 项填满容器。
  • baseline:根据基线对齐,用于字体大小不同的 flex 项,图示

align-content 交叉轴对齐(仅用于多行 flex 项) #

align-content 设置多行 flex 项的交叉轴对齐方式,flex 项只有一行时(flex-wrap: nowrap;)无效,图示

1
2
3
.container {
 align-content: flex-start | flex-end | center | space-between | space-around | space-evenly | stretch | start | end | baseline | first baseline | last baseline + ... safe | unsafe;
}

align-content 用法和 align-items 类似。

row-gap、column-gap、gap 间隔 #

如果在 flex 项上画横线和竖线,row-gap 表示横线的间隔大小,column-gap 表示竖线的间隔大小,gap 是前面两者的缩写,在线示例

1
2
3
4
5
6
7
8
.container {
 display: flex;
 flex-wrap: wrap;
 row-gap: 20px;
 column-gap: 5px;
 gap: 20px 5px; /* row-gap, column-gap */
 gap: 20px; /* row-gap 和 column-gap 都是 20px */
}

gap 只是设置最小间隔,使用 space-between 可以加大间隔。

flex 项的属性 #

flex 项(flex item)的属性有 orderflex-growflex-shrinkflex-basisflexalign-self

flexbox 没有 justify-self,参看 Box alignment in flexbox - CSS: Cascading Style Sheets | MDN#there_is_no_justify-self_in_flexbox

order 顺序 #

默认情况下,flex 项按照源代码出现的顺序排列。使用 order 属性可以改变顺序,默认值为 0,数字越大位置越后,可以使用负值(比如:-1)。如果多个 flex 项的 order 值一样,它们还是按原始顺序排列。

1
2
3
4
5
6
7
8
9
#item1 {
 order: 2;
}
#item2 {
 order: 1;
}
#item3 {
 order: -1;
}

图示:

1
item3 | item2 | item1

flex-basis 大小 #

flex-basis 设置 flex 项的大小,默认为 auto

1
2
3
.item {
 flex-basis: 50px;
}

flex-grow 增长 #

flex 容器有多余空间时,flex-grow 设置 flex 项的增长系数,默认为 0(不增长),负值无效。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.item1 {
 flex-grow: 1;
}

.item2 {
 flex-grow: 2;
}

.item3 {
 flex-grow: 1;
}

上面代码表示将多余空间(假设为 100px)均分为 4 份,.item1 的宽度增加 1 份(25px),.item2 的宽度增加 2 份(50px),.item3 的宽度增加 1 份(25px),在线示例

flex-shrink 收缩 #

空间不足时,flex-shrink 设置 flex 项的收缩指数,默认为 1(可收缩),0 表示不可收缩,负值无效。flex-shrink 的效果和 flex-grow 类似。

假设有 1 个 200px 宽度的 flex 容器,它包含 3 个 flex 项(每个宽度 100px,共 300px),此时有 100px 溢出的宽度。

1
2
3
4
5
6
7
.container {
 display: flex;
 width: 200px;
}
.item {
 flex-basis: 100px;
}

如果 flex 项总宽度减去 100px,那么就不会溢出,在线示例

1
2
3
4
5
6
7
8
9
.item1 {
 flex-shrink: 1;
}
.item2 {
 flex-shrink: 2;
}
.item3 {
 flex-shrink: 1;
}

上面代码表示把溢出的 100px 均分为 4 份。.item1 宽度减去 1 份(25px),变成 75px;.item2 宽度减去 2 份(50px),变成 50px;.item3 宽度减去 1 份(25px),变成 75px。

flex(flex-grow、flex-shrink 和 flex-basis 的缩写) #

flexflex-growflex-shrinkflex-basis 的缩写,默认为 0 1 auto。推荐使用 flex 代替那 3 个属性,它会自动使用合理的默认值。比如使用 flex: 1 设置 flex-grow: 1 时,flex-basis 会变成 0%。

flex 可以使用 1~3 个值,具体用法请看 flex - CSS: Cascading Style Sheets | MDN

使用 flex 可以轻松设置 flex 项的大小比例,在线示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
.container {
 display: flex;
 flex-direction: row;
 width: 100px;
}

.item1 { /* 25px */
 flex: 1;
}
.item2 { /* 50px */
 flex: 2;
}
.item3 { /* 25px */
 flex: 1;
}

align-self 交叉轴对齐 #

align-self 设置单个 flex 项的交叉轴对齐方式,用法和 align-items 一样,两者可以同时用。

习题 #

参考资料 #

2024-08-03 本站改动:使用 Yue 主题和 giscus 评论,post 目录改为 posts

2024年8月3日 00:00

本文列举了本站于2024年7月28日至2024年08月03日的改动。

  • 清理无用文件
  • 使用我写的 Yue 主题(感觉自己从几乎不折腾博客一下走到折腾博客的尽头了,用自己的主题太爽了,哈哈)
  • 讲 utterances 评论服务改为 giscus(终于可以回复评论了,不需要引用文字或者 @
  • post 文件夹改为 posts

改动很多,最好在 Git 仓库建一个分支搭配子域名(如 test.cyrusyip.org)测试,没问题再合并代码至主分支。修改网站要注意功能和链接(URL)变动,改了链接要修改文章内部链接、重定向、迁移评论。

清理无用文件 #

不再使用以下软件/服务:

  • blogdown
  • Netlify
  • Vercel
  • ……

删除相关文件,修订自述文件和 package.json

将主题换为 Yue #

先构建一次,保存起来

1
2
3
4
cd cyrusyip-blog
mkdir ~/Desktop/blog-builds
rm -r public; npx hugo # hugo 前面多了 npx 是因为我用 npm 安装 hugo
mv public ~/Desktop/blog-builds/jane

删除 Jane 主题和相关文件,重命名配置和中文内容文件夹。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 删除相关文件
rm -r assets layouts resources
git add --all
git commit --message 'chore: remove custom files related to jane theme'
# 删除主题
git submodule deinit themes/jane # 修改 .git/config
git rm themes/jane # 修改 .gitmodules
rm -rf .git/modules/themes/jane # 删除本地文件
git commit --message 'feat!: remove jane theme'
# 重命名
mv config.toml _config.toml
mv content/zh-cn content/zh-CN
git add --all
git commit --message 'refactor: rename to content/zh-CN and _config.toml'

安装主题。

1
2
git submodule add --depth=1 https://github.com/CyrusYip/hugo-theme-yue themes/hugo-theme-yue
git commit --message 'feat: add hugo-theme-yue'

写新配置,对照原来的配置(_config.toml)。删掉了 keywords、description、Google Analytics、Disqus、utterances,后面再加回来。

1
2
3
cp themes/hugo-theme-yue/exampleSite/hugo.yaml .
# 写好新配置后删除旧配置
rm _config.toml

构建,对比目录名和文件名变化,如果没变化表示 URL(链接)没变化。用 tree 命令列举两次构建的文件,复制到剪切板,使用 Diffchecker 对比。

1
2
3
4
5
rm -r public; npx hugo
# 旧主题
tree ~/Desktop/blog-builds/jane | wl-copy
# 新主题
tree ~/Desktop/blog-builds/yue | wl-copy

新主题编译的网站少了一些没用到的页码,这里无需处理。新网站还多了一篇 2021-01-29-python-calculate-utility-bills.Rmarkdown,这是 Rmarkdown 文件,不应该出现于此,在配置文件忽略它。

1
2
# hugo.yaml
ignoreFiles: ["\\.Rmd$", "\\.Rmarkdown$", "_cache$", "\\.knit\\.md$", "\\.utf8\\.md$"]

构建网站后再对比,没这个 .Rmarkdown 文件了。

对比:

  • 原主题编译的网站有 607 个目录,480 个文件,构建时间 750ms。
  • 新主题编译的网站有 398 个目录,354 个文件,构建时间 450ms。新网站关闭了 enableGitInfo,还没添加统计和评论功能(只是加脚本应该不太会影响构建时间)。

用肉眼对比新旧网站,看看有没有问题。看样式、文章数量、日期等。

添加本站原有的功能 #

  • Google Analytics
  • utterances
  • Disqus
    • 只在 cyrusyip.org 上面显示
    • 这次加上了多语言功能
  • 图片说明文字
  • 页脚
    • 社交媒体、Github、Stack overflow、Twitter
    • CC BY-NC-SA 4.0 协议
  • meta description
  • keywords(meta keywords 已过时,参看 Meta Keywords:是什么、为什么不 | Sukka’s Blog

将 utterances 评论改为 giscus #

另建一个站点测试会比较稳妥,但这次为了节约时间就直接改了。

giscusGitHub Discussions 驱动,功能比 utterances 强,支持多语言和回复评论。

转换步骤:

  1. 删除网站的 utterances
  2. 给相关 issue 打标签「Comments」,issue 不多我就直接在网页操作了,一次可以改一页,多的话用 gh issue edit 命令
  3. 开启 Discussion,新建类别「Comments」
  4. 测试 giscus 是否能新建讨论,标题是否正确
  5. 转换 issue(Comments 标签)为 discussion( Comments 类别),参看 Managing discussions - GitHub Docs#converting-issues-based-on-labels。页面显示「Open issues with label ‘Comments’ are being converted to discussions」,但是过了一两个小时都没反应,最后手动转换了。
  6. 创建 giscus.json 配置文件,限制域名,参看 giscus/ADVANCED-USAGE.md#origins
  7. 在 GitHub 卸载 utterances
  8. 把仓库名 blog-comment 改为 blog-comments,去 giscus 官网重新获取脚本(非必须步骤,这里存了超过一则评论,所以用复数 comments)

post 文件夹改为 posts #

之前用的 Even 主题Jane 主题 要求内容放在 content/post 文件夹,但是标签(/tags/)和类别(/categories/)用的是复数,我觉得这非常不协调。这次换主题终于可以改成复数 posts 了。

改了之后网站链接(URL)会变化,需要重定向旧链接、修改文章内链接、迁移评论,参考我之前写的文章(真是帮了大忙):

重命名文件夹 #

构建未修改的网站并保存。

1
2
3
cd cyrusyip-blog
rm -r public; npx hugo
mv public ~/Desktop/blog-builds/before

重命名文件夹。

1
2
mv content/en/post content/en/posts
mv content/zh-CN/post content/zh-CN/posts

预览并修改配置。

1
rm -r public; npx hugo server --navigateToChanged --bind 0.0.0.0
1
2
3
4
5
6
7
8
--- a/hugo.yaml
+++ b/hugo.yaml

- post: /post/:year/:month/:day/:slug/
+ posts: /posts/:year/:month/:day/:slug/

- pageRef: /post
+ pageRef: /posts
1
2
3
4
5
6
7
8
--- a/frontmatter.json
+++ b/frontmatter.json

- "path": "[[workspace]]/content/en/post",
+ "path": "[[workspace]]/content/en/posts",

- "path": "[[workspace]]/content/zh-CN/post",
+ "path": "[[workspace]]/content/zh-CN/posts",
1
2
git add --all
git commit --message "refactor: content/*/post -> content/*/posts"

关掉 Hugo 服务器,构建修改后的网站并保存。

1
2
rm -r public; npx hugo
mv public ~/Desktop/blog-builds/after

复制两个网站目录的结构到剪切板,用 Diffchecker 对比。

1
2
3
4
# 旧网站,Original text
tree ~/Desktop/blog-builds/before | wl-copy
# 新网站,Changed text
tree ~/Desktop/blog-builds/after | wl-copy

就只是 post 目录变成了 posts

1
2
3
4
5
- /en/post
+ /en/posts

- /zh-cn/post
+ /zh-cn/posts

修改文章内部链接 #

要将 [本站的某文章](/zh-cn/post/2021/01/01/hi/) 这样的链接改成 [本站的某文章](/zh-cn/posts/2021/01/01/hi/)。 获取包含链接的文件,并删掉不需要改的链接。

1
2
3
mkdir todo
rg '\[.*\]\(/.*\)' content --only-matching >> todo/internal-links
rg 'cyrusyip\.org' content >> todo/internal-links

链接记录节选:

1
2
3
content/zh-CN/posts/2021-06-19-ac2100-openwrt.md:[红米 AC2100 刷 breed 后刷回官方固件](/zh-cn/post/2022/10/16/ac2100-stock-firmware/)
content/zh-CN/posts/2021-08-30-raspberry-pi-4b-openwrt.md:[在 OpenWrt 控制树莓派 Argon Mini Fan](/zh-cn/post/2021/09/15/openwrt-argon-mini-fan/)
content/zh-CN/posts/2021-08-30-raspberry-pi-4b-openwrt.md:[树莓派 4B 超频教程](/zh-cn/post/2021/09/20/raspberry-pi4-overclock/)

自己手动改链接,改一条就删去一条记录。使用 hugo server --navigateToChanged 命令预览网站可以很方便地查看变化。

我一开始让 ChatGPT 写 Node.js 脚本帮我改,多番交流后它生成了一些不存在的链接,看来这个工作还是太难为它。

重定向 #

现在本站使用 Cloudflare Pages 构建,参考 Redirects · Cloudflare Pages docs 设置重定向。我有点搞不懂我之前写的一些重定向规则,所以要写好注释,未来的我会感谢现在写注释的我。

1
2
3
4
5
6
7
# static/_redirect
######################################################################
# Rename "content/{en,zh-CN}/post" to "content/{en,zh-CN}/posts"
# URL changes: /en/post/ -> /en/posts/ , /zh-cn/post/ -> /zh-cn/posts/
######################################################################
/en/post/* /en/posts/:splat 308
/zh-cn/post/* /zh-cn/posts/:splat 308

测试链接:

关闭评论 #

关闭 Disqus 和 giscus 评论,以免迁移时有读者留言。

迁移 Disqus 评论 #

参考教程:How to download, edit, and upload a URL Map CSV | Disqus

打开迁移工具,点击「Start URL mapper」->「you can download a CSV here」,去邮箱拿到下载链接,下载并解压。这个 CSV 里面的链接非常乱,有无评论的、带查询参数的(/?utterances=xxx)、失效链接。建一个 Git 仓库,把 CSV 文件放进去并提交,后面方便看改动。

修改 CSV,留下以 https://cyrusyip.org/zh-cn/posthttps://cyrusyip.org/en/post 开头的。

文件节选:

1
2
https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/
https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/

在 Vim 执行 :%s/.*/&,&,现在每行都有两个一样的链接,提交改动。

1
2
https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/,https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/
https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/,https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/

执行 :%s/\v(.*)(post)/\1posts,现在把每行第二个链接的 post 改成 posts,提交改动。

1
2
https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/,https://cyrusyip.org/zh-cn/posts/2021/03/08/girls-day-womens-day-and-goddesses-day/
https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/,https://cyrusyip.org/zh-cn/posts/2021/01/10/remove-odor/

把修改后的 CSV 文件上传到 Disqus 的 URL mapper。

迁移 giscus 评论 #

将标题的 post 改为 posts

示例:

1
2
- zh-cn/post/2024/04/05/i-hate-obscure-words/
+ zh-cn/posts/2024/04/05/i-hate-obscure-words/

GitHub CLI 不支持修改 Discussion,又得手动改了。

查看评论 #

这几篇文章有 Disqus 评论,打开看看是否有评论。

Discussions 里找几篇文章,打开看看是否有评论。

开启评论 #

可以看到评论,重新打开 Disqus 和 giscus。

菜谱:煎荷包蛋

2024年7月5日 00:00

材料与工具 #

  • 冷藏的鸡蛋(1 个约 51g)
  • 电磁炉
  • 精铁平底炒锅(已开锅)
  • 花生油(不要使用菜籽油这种低闪点的油,热锅后倒下去可能会着火)
  • 锅铲
  • 筷子
  • 计时器
  • 两个碗(一个装鸡蛋,另一个装少量水)

第一次煎蛋的流程 #

  1. 打蛋到碗里
  2. 打开计时器记录热锅时间(后面会用到),电磁炉开到最高档位(2200w)。用手沾一点点水甩入锅,如果直接蒸发就是温度不够,如果水珠弹跳说明温度够了,记下热锅时间,重置计时器,等水珠蒸发。
  3. 暂停电磁炉,倒油,摇晃锅使锅底都被油覆盖
  4. 倒蛋,马上调到 500W1,按下计时器
  5. 等大概 1.5 分钟。等待期间可用锅铲轻推蛋的底部,若不能推动则等待15秒再推,重复此过程直至蛋可以推开。这段时间可以加白胡椒粉2调味。
  6. 铲蛋,用筷子夹住边缘并慢慢地翻面
  7. 大概煎40秒,试着用锅铲轻推,如果不行就等待后再推,能推开就可以铲起来吃了。

继续煎蛋的流程 #

  1. 如果粘锅了,洗锅、烧干水,从头开始
  2. 如果没粘锅,铲走残渣后按照第一次的流程煎蛋
  3. 注意现在锅里有油,不要洒水测试温度,水和油混合会炸开,请参考第一次记录的热锅时间进行热锅

窍门 #

  • 蛋下锅那瞬间温度要够高,之后根据你的喜好调节温度和煎的时间
    • 要脆的就用更大档位
    • 要嫩的就用更小档位
    • 要溏心蛋就减少煎的时间
    • 要太阳蛋(单面荷包蛋)就一面煎好后加水稍微覆盖锅底,合盖利用蒸汽煮熟热蛋的上面
  • 粘锅时等待一段时间鸡蛋会放开锅底
  • 多加油更容易成功,熟练后再根据自己的喜好调节油量
  • 鸡蛋也可以用常温的
  • 可以直接打蛋到锅中,但是蛋壳弄进去就没法拿出来了,如果你会铁砂掌也可以拿
  • 测试温度的原理是莱顿弗罗斯特现象,水接触温度远超其沸点的铁锅时,水底部会产生有隔热作用的蒸汽,令水沸腾的速度大大减慢
  • 不粘锅煎蛋更容易成功,但是不要干烧,要先加油再加热
  • 可以每次煎 2 个蛋,但是 3 个就太多了,很难翻面

  1. 我这个电磁炉 500W 时是间隔加热,持续加热的电磁炉应该也是可以的 ↩︎

  2. 胡椒研磨成粉后香味会散发,不推荐用成品胡椒粉,推荐用胡椒加研磨器 ↩︎

Hugo:自定义标签标题

2024年7月9日 00:00

背景 #

一开始我用 Hugo 做中文网站时打算在文章里用中文标签,但是链接里面的中文会被转换为人类读不懂的字符。比如:/zh-cn/tags/博客/ 会变成 /zh-cn/tags/%E5%8D%9A%E5%AE%A2/。我不喜欢这样,当时搜索一番也没找到办法,就用英文标签了。现在终于知道怎么改了,最终改为链接里面还是用英文,但是页面就显示中文。

下面是针对 hugo-theme-jane(ef8a126)主题的更改方法,方法是通用的,但是不同主题修改的部分可能不同。开始前先厘清两个概念:标签名(tag name)、标签标题(tag title)。在文章前页(front matter)写的是标签名,用于链接;在 _index.md 文件定义的是 title 是标签标题,它会显示于页面。

1
2
3
4
5
6
---
# /content/zh-cn/post/2021-03-21-dongman.md
title: 动漫这个词不能再用了
tags:
 - anime # 标签名
---
1
2
3
4
---
# /content/zh-cn/tags/anime/_index.md
title: 日本动画 # 标签标题
---

如果你启用了 GitInfo 而且不想更改 lastmod,请参考 Hugo:给文章添加 lastmod(上次修改时间)

修改标签单页标题(/zh-cn/tags/*/ #

以 anime(日本动画)为例。先自定义一个标签的标题。

1
2
mkdir --parents content/zh-cn/tags/anime/
touch content/zh-cn/tags/anime/_index.md

添加内容:

1
2
3
4
---
# /content/zh-cn/tags/anime/_index.md
title: 日本动画
---

文章前页(front matter)的 tags 还是保持用 anime。

1
2
3
4
5
6
---
# /content/zh-cn/post/2021-03-21-dongman.md
title: 动漫这个词不能再用了
tags:
 - anime
---

现在 /zh-cn/tags/anime/ 会显示「日本动画」。

修改文章底下的标签标题(/zh-cn/post/* #

在浏览器 inspect 找到 CSS 类,然后去 VS Code 搜索。

本地预览网站。随便打开一篇文章(http://localhost:1313/zh-cn/post/2022/12/03/ajin-oad/),滚动到底部的标签,右击 Inspect,可以看到对于的 HTML 代码。

1
2
3
<div class="post-tags">
 <a href="http://localhost:1313/zh-cn/tags/anime/">anime</a>
</div>

搜索主题目录。

1
rg '<div class="post-tags">' themes/jane

可以发现此代码就在 themes/jane/layouts/post/single.html,将其复制到 layouts/post/(我之前复制过,这次跳过此步骤)。

编辑 layouts/post/single.html

找到下面这段代码:

1
2
3
4
5
6
7
8
<div class="post-tags">
 {{ range . }}
 {{- $name := . -}}
 {{- with $.Site.GetPage "taxonomy" (printf "tags/%s" $name) | default ($.Site.GetPage "taxonomy" (printf "tags/%s" ($name | urlize))) -}}
 <a href="{{ .Permalink }}">{{ $name }}</a>
 {{ end -}}
 {{ end }}
</div>

<a href="{{ .Permalink }}">{{ $name }}</a> 里面的 $name 改为 .LinkTitle。改完是这样的:

1
<a href="{{ .Permalink }}">{{ .LinkTitle }}</a>

现在 http://localhost:1313/zh-cn/post/2021/03/21/dongman/ 底下会显示标签「日本动画」。

修改标签列表页面(/zh-cn/tags/ #

用前面的方法 Inspect 元素并搜索代码。

1
rg '<div class="tag-cloud-tags">' themes/jane

可以发现标签列表页面的模板为 themes/jane/layouts/_default/terms.html,复制一份到根目录再改。

1
2
3
4
mkdir --parents layouts/_default/
cp themes/jane/layouts/_default/terms.html layouts/_default/
git add layouts/_default/terms.html
git commit --message 'chore: copy layouts/_default/terms.html'

打开 layouts/_default/terms.html,搜索 tag-cloud-tags,找到这段代码。

1
2
3
4
5
6
<div class="tag-cloud-tags">
...
 <a href="{{ .Permalink }}"
 style="font-size:{{$currentFontSize}}{{$fontUnit}}">{{ $tagName }}</a>
...
</div>

将里面的 $tagName 改为 .LinkTitle

1
2
<a href="{{ .Permalink }}"
 style="font-size:{{$currentFontSize}}{{$fontUnit}}">{{ .LinkTitle }}</a>

提交改动。

1
2
git add layouts/_default/terms.html layouts/post/single.html
git commit --message 'feat: use tag title'

修改所有标签名,加上标签标题 #

标签名全部改成全小写,以连字符分割。改动比较多,新建一个分支,提交完再合并到主分支。

1
git switch --create customize-tag-titles

使用 VS Code 插件 Front Matter CMS 修改标签名。

加标签标题就比较繁琐,要自己创建文件。我让 ChatGPT 给我写了个脚本,好使!

我说的话:

I am customizing tag titles in a Hugo website. I want you to create a CLI program.

1
create-tag-title directory tag-name tag-title

If I run

1
create-tag-title content/en arch-linux 'Arch Linux'

the program should create content/en/tags/arch-linux/_index.md, the content of _index.md should be:

1
2
3
---
title: Arch Linux
---

ChatGPT 给的 Python 脚本(Shebang 是我加的1):

 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
#!/usr/bin/env python 
import os
import sys

def create_tag_title(directory, tag_name, tag_title):
 # Construct the path to the tag directory
 tag_dir = os.path.join(directory, 'tags', tag_name)
 
 # Make the directories if they don't exist
 os.makedirs(tag_dir, exist_ok=True)
 
 # Define the path for the _index.md file
 index_file_path = os.path.join(tag_dir, '_index.md')
 
 # Define the content of the _index.md file
 index_content = f"""---
title: {tag_title}
---
"""
 # Write the content to the _index.md file
 with open(index_file_path, 'w') as index_file:
 index_file.write(index_content)

 print(f"Created {index_file_path} with title: {tag_title}")

if __name__ == '__main__':
 # Ensure the correct number of arguments are provided
 if len(sys.argv) != 4:
 print("Usage: create-tag-title directory tag-name tag-title")
 sys.exit(1)

 # Read the arguments from the command line
 directory = sys.argv[1]
 tag_name = sys.argv[2]
 tag_title = sys.argv[3]

 # Call the function to create the tag title file
 create_tag_title(directory, tag_name, tag_title)

自己写提交信息还是太繁琐,让 ChatGPT 把这部分也加上。

我说的话:

Besides creating the _index.md, I want to stage content/ directory and commit. Example:

1
git add content/; git commit -m 'tag-title: arch-linux -> Arch Linux'

ChatGPT 返回的代码(Shellbang 是我加的):

 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
#!/usr/bin/env python
import os
import sys
import subprocess

def create_tag_title(directory, tag_name, tag_title):
 # Construct the path to the tag directory
 tag_dir = os.path.join(directory, 'tags', tag_name)
 
 # Make the directories if they don't exist
 os.makedirs(tag_dir, exist_ok=True)
 
 # Define the path for the _index.md file
 index_file_path = os.path.join(tag_dir, '_index.md')
 
 # Define the content of the _index.md file
 index_content = f"""---
title: {tag_title}
---
"""
 # Write the content to the _index.md file
 with open(index_file_path, 'w') as index_file:
 index_file.write(index_content)

 print(f"Created {index_file_path} with title: {tag_title}")

 # Stage the content directory
 try:
 subprocess.run(['git', 'add', directory], check=True)
 print(f"Staged {directory}")

 # Commit the changes
 commit_message = f'tag-title: {tag_name} -> {tag_title}'
 subprocess.run(['git', 'commit', '-m', commit_message], check=True)
 print(f"Committed with message: '{commit_message}'")
 except subprocess.CalledProcessError as e:
 print(f"Error during git operation: {e}")

if __name__ == '__main__':
 # Ensure the correct number of arguments are provided
 if len(sys.argv) != 4:
 print("Usage: create-tag-title directory tag-name tag-title")
 sys.exit(1)

 # Read the arguments from the command line
 directory = sys.argv[1]
 tag_name = sys.argv[2]
 tag_title = sys.argv[3]

 # Call the function to create the tag title file
 create_tag_title(directory, tag_name, tag_title)

我使用的 Python 版本是 3.12.4。

检查链接变化 #

可以在修改前后构建网站,用于对比。如果链接有变化,需要重定向旧链接。改了一堆东西,我已经没心思检查了,忽略此步骤。

参考资料 #


  1. 写完文章我才想到可以让 ChatGPT 加 Shebang ↩︎

❌
❌