Bulma 0.8.0 颜色系统演进:Light/Dark 双色机制、4 值颜色映射与 Dark Mode 的 Scheme 变量
本文围绕 Bulma 0.8.0 版本发布日志中关于颜色系统的六项核心更新展开:每个主色新增 *-light / *-dark 双色版本、面板全色支持、表单控件加大、$colors 映射升级为 4 值结构,以及为"Dark Mode"铺路的 $scheme-* 变量体系。读完后,你将掌握这些颜色函数与映射机制在 Bulma 源码中的真实实现(如 mergeColorMaps()、bulmaFindLightColor()),并能将 0.8.0 的设计思想延续到当前仓库的 CSS 变量主题系统(sass/themes/dark.scss)。
0.8.0 版本更新总览
Bulma 0.8.0 是一次以"更多颜色与更大灵活性"为主题的大版本更新,包含多项 bug 修复与以下新功能(原文记录于 2019-10-15-light-dark-colors.md):
- Light/Dark 双色:每个主色(
primary、info、success、warning、danger)各增加*-light与*-dark版本; - 视觉观感改进:调整部分基础色与
box/card的阴影; - Panel 组件全色支持:面板可以呈现任意主题色;
- 更大的表单控件:控件与按钮高度提升到
2.5em; - 4 值颜色映射:
$colors映射的每个值可接受最多 4 个分量的嵌套映射,由mergeColorMaps()统一处理; - 面向 Dark Mode 的 Scheme 变量:6 个新的
$scheme-*派生变量取代代码库中散落的$white/$black引用。
与本文档同步的完整变更明细见 CHANGELOG.md 中的 0.8.0 小节,其中"Initial variables"与"Derived variables"两节逐条列出了该版本新增的派生变量清单(如 $primary-invert: findColorInvert($primary)、$primary-light: findLightColor($primary) 等),可作为升级排查时的对照清单。
Light/Dark 双色:两个颜色函数如何工作
0.8.0 起,每个主色都派生出一对 light/dark 版本,二者由两个新增的 Sass 颜色函数计算:
findLightColor():找到一个颜色的浅色版本;findDarkColor():找到一个颜色的深色版本。
这两个颜色的用途分工明确:light 色被 button 元素使用(浅色背景 + 深色文字的"幽灵按钮"效果);light 与 dark 色共同被 message 组件使用(浅色标题/正文区 + 深色强调区),生成明显更好看的彩色消息块。
源码实现:从亮度算法到色相保持
当前仓库中这两个函数已演进为带 bulma 前缀的版本,定义在 sass/utilities/functions.scss,其核心逻辑值得拆开看:
// sass/utilities/functions.scss
@function bulmaFindLightColor($color, $l: 96%) {
@if meta.type-of($color) == "color" {
$l: 96%;
@if color.channel($color, "lightness", $space: hsl) > 96% {
$l: color.channel($color, "lightness", $space: hsl);
}
@return color.change($color, $lightness: $l);
}
@return $background;
}
@function bulmaFindDarkColor($color, $base-l: 29%) {
@if meta.type-of($color) == "color" {
$luminance: bulmaColorLuminance($color);
$luminance-delta: 0.53 - $luminance;
$target-l: math.round($base-l + $luminance-delta * 53);
@return color.change($color, $lightness: max($base-l, $target-l));
}
@return $text-strong;
}
从源码结构可以看出三点设计意图:
- 浅色版只改亮度、保留色相。
bulmaFindLightColor()把 HSL 亮度强制拉到96%(极浅),但保留原始色相与饱和度,因此is-info按钮的浅色背景仍带有该色系"淡淡的蓝味",而非纯灰; - 深色版按相对亮度动态补偿。
bulmaFindDarkColor()先通过bulmaColorLuminance()计算颜色的感知亮度(加权0.2126R + 0.7152G + 0.0722B的 sRGB 相对亮度公式),再以基准亮度29%为底、按亮度差做补偿——原本越亮的颜色,压暗后的目标亮度越高,保证深色版文字/边框在不同色系上对比度均衡; - 对非颜色输入有兜底。两个函数在参数不是合法颜色时分别回退到
$background与$text-strong,配合bulmaRgba()等防御性写法,使得自定义颜色映射传入意外值时不会直接编译失败。
与之配套的 bulmaFindColorInvert() 则用同一套亮度计算决定"反色文字":亮度大于 0.55 时返回 rgba(#000, 0.7),否则返回白色——这是主色上文字颜色的自动选择逻辑。
视觉观感改进:基础调色板与阴影
0.8.0 同时微调了三个基础色的 HSL 取值("Before / After"对比,数据来自原文与 CHANGELOG.md 0.8.0 节的"Initial variables"):
| 颜色变量 | 调整前 | 调整后 |
|---|---|---|
$green |
hsl(141, 71%, 48%) |
hsl(141, 53%, 53%) |
$cyan |
hsl(204, 86%, 53%) |
hsl(204, 71%, 53%) |
$red |
hsl(348, 100%, 61%) |
hsl(348, 86%, 61%) |
可以看出调整思路是普遍降低饱和度、略微提升亮度:原取值饱和度过高(71%~100%),在按钮、消息块等大面积使用场景下显得刺眼,降饱和后整体更沉稳。这些值统一定义在初始变量层 sass/utilities/initial-variables.scss(当前仓库中该文件已随 v1 再次调参,例如 $green: hsl(153, 53%, 53%)),遵循"initial 层只放原始取值、derived 层再做语义映射"的分层原则。
除颜色外,box 与 card 的阴影也做了改进,使卡片浮起感更柔和自然。阴影取值由派生变量 $shadow-color(见 sass/utilities/derived-variables.scss)驱动,后续主题化时也随之成为 CSS 变量。
Panel 组件的全色支持
panel 组件在 0.8.0 起支持所有主题色,即 is-primary、is-info、is-success 等修饰类均可用于面板。这类修饰类的生成依赖于 $colors 映射的完整性——只要某个颜色名存在于映射中,颜色修饰类循环(@each $name, $value in $colors)就会为全部组件批量产出对应的 is-<name> 规则。这正是"4 值颜色映射"(下一节)在组件层面的直接红利:颜色系统对组件是数据驱动的,新增颜色只需改映射,无需逐个组件修改。
更大的表单控件与回退配置
0.8.0 将表单控件与按钮的高度提升到 2.5em(原为 2.25em),改善了触控与视觉比例。若需要恢复旧尺寸,按原文给出的方式覆盖以下变量即可:
$control-height: 2.25em
$control-padding-vertical: calc(0.375em - #{$control-border-width})
$control-padding-horizontal: calc(0.625em - #{$control-border-width})
$button-padding-vertical: calc(0.375em - #{$button-border-width})
$button-padding-horizontal: 0.75em
注意这些变量内部相互引用 $control-border-width / $button-border-width,因此覆盖时应原样保留 calc(... - #{$...-border-width}) 结构,保证边框加在内侧、总高度不变。这段回退配置同时收录在 CHANGELOG.md 的 0.8.0 "Larger form controls"小节中。
4 值颜色映射:$colors 的 1~4 值嵌套结构与 mergeColorMaps()
0.8.0 起,$colors Sass 映射的每个值都可以是最多 4 个分量的嵌套映射,顺序固定为:
(基础色, 反色, 浅色, 深色) // 例如 "info" -> ($info, $info-invert, $info-light, $info-dark)
当用户提供 $custom-colors 自定义映射时,每个值可以给出 1、2、3 或 4 个分量;缺省的分量由 Bulma 自动计算。原文给出的完整示例如下:
$custom-colors: (
"lime": (
lime,
),
"tomato": (
tomato,
white,
),
"orange": (
$orange,
$orange-invert,
$orange-light,
),
"lavender": (
$lavender,
$lavender-invert,
$lavender-light,
$lavender-dark,
),
);
源码实现:mergeColorMaps() 的分量补齐逻辑
上述"缺多少算多少"的行为由 sass/utilities/functions.scss 中的 mergeColorMaps($bulma-colors, $custom-colors) 实现,其核心分支为:
@if meta.type-of($components) == "color" {
// 只给 1 个值:invert/light/dark 全部自动计算
$color-base: $components;
$color-invert: bulmaFindColorInvert($color-base);
$color-light: bulmaFindLightColor($color-base);
$color-dark: bulmaFindDarkColor($color-base);
} @else if meta.type-of($components) == "list" {
$color-base: list.nth($components, 1);
@if list.length($components) > 3 {
// 给了 4 个值:原样采用
} @else if list.length($components) > 2 {
// 给了 3 个值:dark 用 bulmaFindDarkColor() 补齐
} @else {
// 给了 2 个值:light、dark 均自动计算
}
}
对照源码可以得到一张"用户输入 -> 自动补齐"的完整规则表:
| 提供的分量 | base | invert | light | dark |
|---|---|---|---|---|
| 1 个(单个颜色) | 采用 | bulmaFindColorInvert() 计算 |
bulmaFindLightColor() 计算 |
bulmaFindDarkColor() 计算 |
| 2 个(base, invert) | 采用 | 采用 | 计算 | 计算 |
| 3 个(base, invert, light) | 采用 | 采用 | 采用 | 计算 |
| 4 个(全部) | 采用 | 采用 | 采用 | 采用 |
此外,函数内部还包含一层输入校验:颜色名必须是字符串、分量必须是合法颜色或长度不小于 1 的列表、且 base 必须是真实颜色值(meta.type-of($color-base) == "color")才会执行 map.merge() 合并——这意味着同名键(如 "info")可以被自定义映射整体覆盖而非报错,这也是"自定义颜色可以覆盖内置颜色"的实现基础。
该映射在派生变量层的挂载点见 sass/utilities/derived-variables.scss:内置 $colors(white / black / light / dark / text / primary / link / info / success / warning / danger)与用户 $custom-colors 在此通过 fn.mergeColorMaps() 合并,$shades 同理合并 $custom-shades。
Scheme 变量:面向 Dark Mode 的重构
0.8.0 引入 6 个新的 $scheme 派生变量:
$scheme-main $scheme-main-bis $scheme-main-ter
$scheme-invert $scheme-invert-bis $scheme-invert-ter
它们的作用是取代代码库中所有 $white 与 $black 的直接引用。其默认值在 sass/utilities/derived-variables.scss 中定义:
// sass/utilities/derived-variables.scss
$scheme-main: iv.$white !default;
$scheme-main-bis: iv.$white-bis !default;
$scheme-main-ter: iv.$white-ter !default;
$scheme-invert: iv.$black !default;
$scheme-invert-bis: iv.$black-bis !default;
$scheme-invert-ter: iv.$black-ter !default;
于是创建"Dark Mode"只需交换取值,无需改动任何组件源码:
$scheme-main: $black
$scheme-invert: $white
// 其余 scheme 变量同理
原文还点出了与之配套的关键决策:0.8.0 起大部分代码库引用的是派生变量($text、$background、$border 等),而非初始变量($grey、$grey-lighter、$grey-darker 等)。在 sass/utilities/derived-variables.scss 中可以看到这一分层的落地形态:
$text: iv.$grey-dark !default;
$text-weak: iv.$grey !default;
$text-strong: iv.$grey-darker !default;
$background: iv.$white-ter !default;
$border: iv.$grey-lighter !default;
$border-weak: iv.$grey-lightest !default;
派生变量是"语义层",初始变量是"原子层";组件只依赖语义层,因此更新派生变量会直接影响所有元素和组件——这正是 Dark Mode 能够"一处交换、全局生效"的原因。
后续演进:从 Sass 变量交换到 CSS 变量主题
这套 scheme 思想在当前仓库中进一步演进为基于 CSS 变量的运行时主题系统:官方深色主题 sass/themes/dark.scss 通过 register-vars() mixin 注册 --bulma-scheme-main-l: 11% 等亮度变量,并用 generate-on-scheme-colors() 为每个主色重新生成"on-scheme"色阶。触发方式同时支持 @media (prefers-color-scheme: dark)(system-theme() mixin)与 [data-theme=dark] / .theme-dark 选择器(bulma-theme() mixin),完整说明见 docs/documentation/features/dark-mode.md。对于只消费预编译 CSS(css/bulma.css)的使用者,0.8.0 的 scheme 变量交换属于构建期定制;而对于当前版本,则可以直接通过 HTML 属性或 CSS 类在页面内切换主题,两种方式在原理上一脉相承——都是把"背景/文字/边框"收敛到一组可整体翻转的变量上。
版本兼容性说明
原文结尾明确承诺:0.8.0 版本与任何既有 Bulma 配置完全兼容,升级遇到问题建议向仓库提交 issue 反馈。结合 CHANGELOG.md 0.8.0 小节可以看到,该版本的新增项(light/dark 色、scheme 变量、4 值映射)均以"扩展 + 默认值兜底"方式落地——$custom-colors 默认为 null、mergeColorMaps() 对未提供的分量自动补齐,因此既有配置在升级后行为不变。需要注意的适用前提是:本文所述的"控件加大可回退""颜色映射 4 值结构"等结论均针对 0.8.0 版本;当前仓库源码(v1 线)中的函数已统一加 bulma 前缀(bulmaFindLightColor() 等),若你的项目基于当前版本定制颜色函数,应以 sass/utilities/functions.scss 中的实际签名和 docs/documentation/features/dark-mode.md 的 CSS 变量主题方案为准。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00