普通视图

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

将一加 Ace 3 从 PixelOS 14 升级到 PixelOS 15

2025年2月13日 00:00

现在 PixelOS 15 发布了,我就打算从 PixelOS 14 升级上去,但是事情没那么简单。因为 PixelOS 15 要用到 ColorOS 15 的固件,所以不能直接升级。要安装 ColorOS 14,升级到 ColorOS 15(15.0.0.200 或者以上的版本),在 ColorOS 的更新器再次安装 ColorOS 15 以确保两个系统槽位(slot)都是 ColorOS 15。安装 ColorOS 14 的时候会清空用户数据,所以要先备份应用和文件,最后再恢复。

操作有风险,推荐在有条件去售后中心的时候操作,失败了就去售后直接安装 ColorOS 15 吧。

需要用到的文件 #

备份 #

我用 DataBackup 来备份应用与它们的数据、文件。

  • 应用
  • DCIM
  • Pictures
  • Documents

DataBackup 需要 root 权限,我用最新构建的 APatch使用文档,注意最新构建版是个 .zip 文件,需要解压之后安装里面的 .apk),你也可以用其他 root 方案。

备份完删掉一些应用再恢复,看看能不能行。没问题就把数据复制到电脑。

我对微信没有信心,所以将微信聊天记录额外备份到电脑。

有些东西 DataBackup 无法备份,只能自己截图或者用文字记下来。无法备份的东西有:

  • 系统设置(WIFI 密码、星标联系人、闹钟、谷歌账户等)
  • APatch 设置和模块(应该也能备份吧,但总感觉不太对劲)

清除密码 #

退出谷歌账号,删除锁屏密码。

用 9008/EDL 安装 ColorOS 14 #

参考 How To Guide - [12R/Ace 3] EDL DownloadTool to restore your device to OxygenOS/ColorOS | XDA Forums,用 9008/EDL 模式安装 ColorOS 14。

Download OnePlus 12R/Ace 3 EDL Unbrick Firmware/ROM - DroidWin 下载 EDL 刷机包(PJE110domestic_11_14.0.0.317CN01_2024012904340000.zip),登录谷歌账户才行,不然提示下载人数太多。

  1. 解压 zip 包。
  2. 将 xda 帖子的 prog_firehose_ddr.elf 放到 系统包/IMAGES/,覆盖掉里面的 prog_firehose_ddr.elf
  3. 打开 9008驱动.exe,安装。
  4. 打开报错安装我,提示已安装这个产品的另一个版本。
  5. 打开刷机工具1.3。
  6. 区域选择非欧洲,用户名、密码、验证码都输入 1,登录。
  7. 将系统包的文件夹拖拽进去。
  8. 保持默认选项,点击ok,点击开始。
  9. 手机按住两个音量键和电源键,刷机工具显示连接就松手。
  10. 显示 firehose 协议执行失败,好像是因为我前面还没关闭 fastboot firmware flasher,关闭它之后再来用刷机工具1.3就好了。
  11. 6 分钟后完成刷机,再等一段时间,ColorOS 14 开机成功。

第 1 次安装 ColorOS 15 #

ColorOS 14 开机成功,进入设置引导界面,增强服务这里关闭「自动下载」和「夜间自动更新」,后面的能跳过就点跳过。

目前版本信息是 PJE110_14.0.0.317(CN01)

  1. 将手机插到电脑,复制 ColorOS PJE110_15.0.0.500 刷机包到手机的 Download 文件夹。
  2. 开启开发者模式:打开设置-关于本机-版本信息-快速多次点击版本号。
  3. 开启飞行模式(关闭数据和 WIFI)。
  4. 打开设置-应用-应用管理-右上角的三个点-显示系统应用-搜索软件更新-储存占用-清除数据。
  5. 打开设置-关于本机-ColorOS-右上角的三个点-本地安装-选择前面从电脑放进去的 ColorOS 15 刷机包-立即解压-立刻安装。

安装完后重启到 ColorOS 15。

友情提示:升级到 ColorOS 15 之后,原来的免授权 EDL 刷机工具会失效,用 EDL 工具的新方法:Fix Sahara Communication/Protocol Failed Error in Oppo Flash Tool - DroidWin

第 2 次安装 ColorOS 15 #

现在进入了设置引导界面,关闭「自动下载」和「夜间自动更新」。现在版本信息是 PJE110_15.0.0.500(CN01)

像前面一样,在设置里面使用本地安装,现在是第 2 次安装 ColorOS 15,安装完选立即重启。

安装 PixelOS 15 #

现在准备工作都做好了,按照 PixelOS 官方教程 做就行了。

手机关机,按住音量下键和电源键开机进入 fastboot,连接电脑。

1
2
3
4
5
fastboot flash boot boot-aston_20250121_1747.img
fastboot flash init_boot init_boot-aston_20250121_1747.img
fastboot flash vendor_boot vendor_boot-aston_20250121_1747.img
fastboot flash dtbo dtbo-aston_20250121_1747.img
fastboot flash recovery recovery-aston_20250121_1747.img

按手机音量下键,直到显示 Recovery mode,按下电源键确认。现在进入 PixelOS 的 recovery 了。

在手机选择 Apply update - Apply from ADB。

1
adb sideload PixelOS_aston-15.0-20250121-1747.zip

手机询问「Do you want to reboot to recovery now?」(是否重启到 recovery),选择 No。

选择 Factory reset - Format data/factory reset - Format data。

点左上角的左箭头返回,选择 Reboot system now。

简单设置一下:

  • Refresh rate 120 Hz (Adaptive)
  • Tap to wake
  • Tap to sleep
  • Display size and text - Display size(调大 1 档)
  • Network traffic indicator

恢复 #

安装 APatch,修补 boot.img,刷入后手机开机后卡死,触屏和按键没反应。头疼!按住音量上键和电源键强制重启,屏幕熄灭时马上松手并按住音量下键,进入 fastboot。刷入 PixelOS 的 boot.img。

1
2
fastboot flash boot boot-aston_20250121_1747.img
fastboot reboot

KernelSU 也试了下,刷入修补的 init_boot.img 后并不能获取 root 权限。最后只能用 Magisk 了。

把前面备份的数据从电脑复制到手机(里面有 DataBackup),安装 DataBackup 后恢复应用和数据。

有些应用要重新登录或者设置:

  • Bitwarden
  • Outlook
  • 支付宝
  • 欧路词典

不需要重新登录或者设置的应用:

  • 微信
  • KDE Connect

闪退的应用:

  • Hail
  • GKD

其他设置自己手动处理。

  • 连接 WIFI,登录谷歌账号。
  • 同步 outlook 联系人
  • 设置星标联系人
  • 设置闹钟
  • 克隆应用的功能没了,只好装个 Insular
  • 安装 root 模块

Magisk 的问题 #

Magisk 真的太容易被检测到了,云闪付、支付宝、微信里面都没有人脸支付、指纹支付的选项,云闪付直接提示处于 root 环境。之前用 APatch,支付宝是可以人脸支付的。真是头疼,只能先不用 root 了,支付宝又能用人脸支付了。

2025-02-16 更新:PixelOS 的维护者 inferno0230 提供了支持 KernelSU 的内核。我试了 OP12R-v5.15.175-20250119-0950.zip,在 PixelOS Recovery 里选 Apply update - Apply from ADB,在电脑执行 adb sideload OP12R-v5.15.175-20250119-0950.zip 就安装成功了。我把 KernelSU 管理器安装到 private space,用支付宝人脸识别正常,云闪付也没有提示 root 环境。

清理 #

删除电脑的备份文件、刷机包、刷机工具、微信记录。

文章到这里就结束了,下面是安装 ColosOS 14 的失败尝试,不知道是我操作不当还是工具有问题。

用 fastboot 安装 ColorOS 14(失败) #

  1. 手机关机,按住电源键和音量下键开机,手机震动后松手,进入 fastboot 模式。将手机插到电脑 USB 口。
  2. 打开 Fastboot Firmware Flasher。
  3. 选择 [7] FIRMWARE UNPACKER,解开 ColorOS 14。
  4. 返回主菜单,选择 [3] FLASH ROM
  5. 电脑显示 Rebooting into fastboot,但是手机进了 fastbootd。可以选择清除数据、重启、关机,我选了选择关机,结果就反复启动了。啊!也许我应该选择清除数据。

用文本文件制作 Anki 牌组

2025年1月30日 00:00

Anki 是开源的记忆软件。我们可以用文本文件制作牌组(一组需要记忆的内容),文本文件更易于修改。

将下面文本保存为 deck.txt,然后用文本编辑器编辑。

1
2
3
4
5
#separator:Pipe
#html:true
hi|你好
why|为何
two lines|第 1 行<br>第 2 行

此文本对应的导出格式是「Cards in Plain Text(.txt)」。

第 1 行的 separator(分隔符)用于区分正面和反面,可以用 Comma(,)、Semicolon(;)、Tab( )、Space( )、Pipe(|)、Colon(:),用名称(例如:Pipe)和符号(例如:|)都可以。我推荐用 |,它很少在卡片里用到,而且可以用键盘直接输入。

第 2 行表示可以使用 HTML 代码。比如:<br> 表示换行。

第 3 行开始就是卡片,一行就是一张卡,分隔符左边是卡片正面,分隔符右边是卡片反面。

在 Anki 创建牌组,将 deck.txt 导入到这个牌组。如果你想制作翻转卡片(反面->正面),导入时 Note Type 选择「Basic and (reversed) card」,这样牌组会同时存在基础卡片(正面->反面)和翻转卡片。


相关资料:Text Files - Anki Manual

在闲鱼遭遇到手后砍价,最后拿回货款

2025年1月1日 00:00

为保护隐私,本故事略有改编。

卖前 #

我在闲鱼卖一台五六年前发售的手机,定价 200 元人民币,原价 2999 元。这台手机可以正常使用,屏幕有一处明显划痕,边框掉漆。

放了几天,有个人猛砍价1到 120 元,我说 180 元他就不回了。

后来又有个人问了,这个人一开始就让我感觉不爽。她的名字类似于「闭嘴吧你」,一副不好沟通的模样,芝麻信用还未授权。

买家说话吞吞吐吐,沟通起来很浪费时间。

买家:你好

我:你好,想了解一下这台手机吗?

买家:是的

我内心:喂,你有话快说,非要我说一句你就说一句吗!

买家开始问有没有换过屏幕和电池,是不是自用。我说原装自用。买家叫我拍边框,拍了她就挑剔有磕碰,又说几年前手机电池不行,换电池都要几十块,以这些为由要求降价。这时我就开始感觉恶心,二手手机本来就有耗损,还按照新机那种标准挑剔,新机至于卖 200 吗?

买家问钢化膜、手机壳、充电器。我都在商品页面写清楚只有手机跟手机壳了。买家还嫌手机壳变色。几年前的手机壳你想要多好,有送都不错了。买家问有没有划痕,我都在商品页面说了在哪个位置有一处明显划痕,她又要我拍照。

充电器我说用 USB Type-C 就行了,其他品牌的充电器也可以的。买家以只有 iPhone 为由(iPhone 15 之前的 iPhone 不使用 Type-C 接口),让我送数据线。为了促成交易,我也送了一条本来在用的,重新买又得十多块,气死。

买家问有没有账号、会不会重启,最后她砍价到 160 元,并且收到马上确认收货(买家确认收货,卖家才会收到货款)。我内心的最低价格是 180 元,她砍那么多我是很不爽的,看她说马上确认收货就算了吧。

下单后她说地址错了。我不知道这是什么套路。让她取消订单,重新上架让她买。

到货 #

发货后买家这个贱人又开始挑剔了,想看购买凭证。我连包装盒都没了,哪来的购买凭证。一般人也不会问这个,这台手机没什么造假价值,又不是奢侈品。

到货后买家并没有爽快得确认收货,从这时开始她越来越恶心了。她说手机缝隙不一致,怀疑拆过机,一边缝隙大,似乎夹了纸片。我也不确定,但之前用手机的时候是没问题的,我就说不用就放着了。于是买家又换另一个理由,说摄像头进灰了影响拍照,又装可怜说贴膜、手机壳、充电头也要钱,希望我优惠点(到手后砍价)。这些配件要钱关我屁事,而且她那么吝啬肯定不会买配件。啊,气死我了!我说摄像头进灰是二手机正常损耗。我手头上刚好有台摄像头进灰的手机,前置摄像头和后置摄像头都进灰了,但是拍照没有明显问题。

过了一天,买家说去手机店清灰要 20 元,问我给她 20 元还是退货2。我说已经最优惠了。她接下来一直以进灰为由缠着我,我就一直说已经最优惠了,后面不想回复了等到货后 3 天自动确认收货。结果这个家伙在最后一天申请了退货,理由是质量问题/功能异常,附上前摄像头的照片,确实有灰尘。最后没收货真是气死我了。

我无法接受退货,因为:

  • 不能容忍她讹人这样错误的行为。
  • 质量问题退货要卖家(我)给运费。
  • 我前面的交流让我觉得她品德低下,怕她弄坏手机再寄回来,真的有人得不到就毁掉。
  • 我不想留下质量问题退货的记录。

闲鱼小法庭 #

我拒绝了退货申请,理由是:问题不存在,补充的文字和截图表示买家有到手刀(到手后砍价)行为。我点击维权之后就进到闲鱼小法庭了。闲鱼小法庭就是解决买家卖家纠纷的方式,由 17 位陪审员(闲鱼用户)投票,17 票 9 胜制,票差小于 4 票时可以申诉。陪审员可以查看双方聊天记录。小法庭有两个环节:前 24 小时是双方举证,发起维权者先发言 1 次,然后开始计时,双方可以发言 5 次;后 24 小时是评审员投票。在我这个例子,如果我赢了就马上到账,如果买家赢了我就必须接受退货。

老实说,第一次上小法庭还是蛮紧张的,要是输了就很难过。发言太多了,我就简单总结一下。

买家的发言:

  • 摄像头进灰影响拍照(附上前摄像头照片,水印表面是用 Android 手机拍的)。
  • 卖家不说明摄像头有灰尘、敷衍了事、不回信息。
  • 急起来说气话,说卖家强买强卖和土匪(拜托,是你自己买又不是我逼你买的)、说用多台手机不行吗。
  • 卖家未标明售出不退。

卖家(我)的发言:

  • 摄像头进灰是手机使用的正常损耗,我之前自拍和打视频电话都没问题,我已经提供了最优惠的价格。
  • 买家买前过度挑剔,也没有表面很在意摄像头灰尘。
  • 买家到手后用模糊的语言砍价(说缝隙大又不提供图片,说摄像头进灰在聊天时也未提供图片),心虚才不会提供照片证明观点。
  • 买家承诺直接确认收货,买后到手刀,这是不诚信。
  • 买家说自己用 iPhone 没 Type-C 数据线,但被我发现他拍照用了 Android 手机,所以他有 Type-C 数据线。买家骗了我一条数据线,这是不诚信。

差不多晚上 12 点的时候开始投票,第二天买家已经取消了退货申请,大概是自知理亏。买家拖 20 小时后自动确认收货,我收到钱了,太好了!

回顾 #

现在回想起来,买家的手段不算很高明,只是我没经验而且想快点卖出手机就中招了。

下面总结一下甄别坏买家和避免纠纷的技巧。

甄别坏买家 #

有以下特征的买家可能是坏买家:

  • 吞吞吐吐(可能没有恶意,但和这样的人聊天很浪费时间)
  • 挑剔,尤其是不合理的挑剔(电池损耗)
  • 用模糊的表达而不是提供证据
  • 表现得很在意问题但只要求砍价(一般人会选择退货)
  • 没有芝麻信用或者买家芝麻信用优秀以下
  • 差评多
  • 新用户

避免纠纷 #

  • 拍 360 度视频/图片
  • 拍装箱视频
  • 用验货宝(闲鱼的验货服务)
  • 声明售出后非质量问题不退

  1. 像 200 元砍到 120 元这种大比例砍价叫做屠龙刀。 ↩︎

  2. 如果不想和买家纠缠,一定要让其确认收货后才发钱给他。 ↩︎

了解与使用 Android 的 root 权限

2024年12月23日 00:00

root 权限 #

root 是 Android 系统的最高权限。Android 系统就像房子。没有 root 的时候用户就是租客,只有使用权,不能随便改动房子。有 root 权限的时候,用户就是房东,有所有权,可以随便改动房子(修改系统)、丢掉原有的家具(卸载系统应用)。

总之,有了 root 权限之后我们就可以完全掌控 Android 系统了。

基本流程 #

获取 root 的前提条件是解锁引导程序(bootloader),解锁了引导程序才能安装 init_boot.imgboot.img

init_boot.imgboot.img 是启动系统需要用到的文件,推荐先备份好它们。修补其中一个并安装后就能获取 root 权限。最后使用 root 管理器来管理 root 权限。

方案 #

目前有三个开源的 root 方案:

Magisk 是最经典的 root 方案,教程和资源最多。缺点是会被应用检测到,比如:中国农业银行应用检测到 root 会退出。用额外的模块才能隐藏 root。

KernelSU 是内核级 root 方案,不需要刻意隐藏 root,被授权的应用才能感知到 root。App Profile 功能可以授予应用部分权限,比如:只允许应用使用 adb 权限。

APatch 也是内核级 root 方案,KernelSU 是内核级 root 方案,不需要刻意隐藏 root,被授权的应用才能感知到 root。需要设置密码。使用方法是修补 boot.img,使用 fastboot boot boot.img 可以临时获取 root,重启后 root 消失。

如果你懒得研究隐藏 root,那就使用 KernelSU 或者 APatch。

用法 #

root 有两种使用方法。一是管理器授权给应用,二是在管理器安装 root 模块(module)。比如:授权给 Neo Backup 就可以备份应用。安装 BCR 模块之后可以自动录制通话。

root 权限可以很危险,比如用来清除手机数据或者弄坏系统。请勿授权给来源不明的应用,也不要安装来源不明的模块。尽量选择开源、知名的应用和模块。

我的用法 #

我目前使用 APatch 最新编译版1

用到 root 的应用有:

模块有:

  • BCR

root 的功能远不止这些,推荐你看 GitHub - fynks/awesome-android-root: A comprehensive and up-to-date list of latest Android root apps that require or utilize root privileges, rooting guides, tips, tricks and tools

相关项目 #


  1. 本来我是想在一加 Ace 3 的 PixelOS 14 用 KernelSU 的,但是修复 init_boot.img 后没用,不知道为什么。 ↩︎

推荐可以刷机的一加 Ace 3

2024年12月15日 00:00

上个月买了可以刷机1的一加 Ace 32。一加 Ace 3 的性能不错,处理器是骁龙 8 二代(2022 年末的旗舰处理器),目前在骁龙处理器中仅次于骁龙 8 三代和骁龙 8 至尊版。喜欢刷机的读者可以考虑这台手机。

相关链接:


  1. 刷机指安装操作系统。 ↩︎

  2. 一加 Ace 3 的国际版为 OnePlus 12R。 ↩︎

小米已退烧

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

PixelOS 升级流程:将一加 Ace 3 从 PixelOS 14 升级到 PixelOS 15

注意事项 #

  • 本文的操作可能会过时,建议参考文末的文章。
  • 拿到手机要马上关闭自动更新,避免更新到不合适刷机的版本。
  • 解锁 bootloader 重启后马上关掉自动更新。
  • 解锁 bootloader 时会清除数据。如果确定要刷机,建议买到手机马上解锁,后面拿到 root 权限可以用 Neo Backup 备份应用数据。

名词解释 #

  • 刷机:安装操作系统
  • 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。

我的安装过程有错误操作,为了避免误导读者,就删掉了。如果你真的想看,请点击这里

系统自带谷歌服务,用 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

练习 #

❌
❌