Dawn 色彩方案:Liquid 把商家配置编译成 CSS 变量
本文基于 Dawn v15.3.0,源码固定到 ecb06c3(核验于 2026-10-01)。结论限于此提交;本篇为静态源码分析,未完成店铺运行验证。Dawn 上游已发布 v16.0.0,本库 Dawn 正文统一以 v15.3.0 为基线,升级前需重新核对。
商家在主题编辑器里可以定义若干套配色,每个分区再选用其中一套。Dawn 的做法是:布局在服务端把所有方案编译成 CSS 类,分区只需加一个类名。CSS 文件本身不含任何商家颜色。
一、设置:一组字段定义一套方案
settings_schema.json 用 color_scheme_group 类型定义方案的结构,每套方案有 7 个字段:background、background_gradient、text、button、button_label、secondary_button_label、shadow。
同一设置下的 role 把这些字段映射到语义角色,例如 links 对应 secondary_button_label、secondary_button 对应 background、背景同时登记纯色与渐变。平台怎样使用这份映射,本篇未核对官方文档;从主题内部看,这份映射与下文生成变量时的字段复用关系一致。
二、布局:循环生成 .color-<id>
theme.liquid#L57-L100 在 {% style %} 中遍历 settings.color_schemes:
{% for scheme in settings.color_schemes -%}
{% assign scheme_classes = scheme_classes | append: ', .color-' | append: scheme.id %}
{% if forloop.index == 1 -%}:root,{%- endif %}
.color-{{ scheme.id }} {
--color-background: {{ scheme.settings.background.red }},{{ scheme.settings.background.green }},{{ scheme.settings.background.blue }};
...
}
{% endfor %}
{{ scheme_classes | prepend: 'body' }} {
color: rgba(var(--color-foreground), 0.75);
background-color: rgb(var(--color-background));
}
有几个细节:
- 第一套方案同时写在
:root上,页面上没有套用任何方案的区域也有默认值。 - 循环同时累积一个选择器列表,最后生成
body, .color-a, .color-b, ... { color; background-color }。因此任何套用方案的容器都会重设文字色和背景色,嵌套的分区不会继承外层颜色。 - 变量值是
r,g,b三元组而不是完整颜色。使用时写成rgb(var(--color-foreground))或rgba(var(--color-foreground), 0.75),同一个变量可以配合任意透明度。assets/下的 CSS 中,rgba(var(--color-出现在 300 多行里,例如边框、分隔线、次要文字都是前景色加不同透明度,商家只需配置一个文字色。 - 字段被复用为多个变量。
--color-secondary-button取背景色,--color-link取次要按钮文字色,徽章的三个变量全部来自文字色与背景色(#L87-L92)。设置项保持精简,代价是这些元素无法单独配色。
三、服务端预先算对比色
assign background_color_brightness = background_color | color_brightness
if background_color_brightness <= 26
assign background_color_contrast = background_color | color_lighten: 50
elsif background_color_brightness <= 65
assign background_color_contrast = background_color | color_lighten: 5
else
assign background_color_contrast = background_color | color_darken: 25
endif
--color-background-contrast 用于需要与背景区分开的元素。计算由 Liquid 颜色过滤器在渲染时完成,浏览器不需要运行任何颜色计算,也不依赖 color-mix() 等较新的 CSS 特性。阈值 26、65 与调整幅度是 Dawn 自定的经验值,源码中没有说明依据。
四、分区:类名加 gradient
分区在根元素上写 color-{{ section.settings.color_scheme }} gradient,例如 rich-text.liquid#L18;sections/ 中共有 27 个文件使用这种写法。
渐变通过一个变量切换:方案设置了渐变时 --gradient-background 取渐变值,否则取纯色(theme.liquid#L64-L68)。.gradient 类先写一行纯色 rgb(var(--color-background)) 作为回退,再写 background: var(--gradient-background)(base.css#L2934-L2937)。CSS 不需要分支判断。
方案是纯 CSS 类,所以也能被 JS 搬运。快速加购弹窗从商品页响应里取出 product-info 后,把它身上的 color-* 和 gradient 类复制到弹窗容器(quick-add.js#L51-L55),弹窗就沿用了商品分区的配色。
取舍与限制
| 方面 | 现状 |
|---|---|
| 渲染成本 | 每个页面都输出全部方案的 CSS,方案越多,内联样式越长;方案数量很少时可以忽略 |
| 颜色格式 | 只存 RGB 三元组,没有 alpha 字段,也无法直接用于 color-mix() 等需要完整颜色值的函数 |
| 交互状态 | 方案中没有悬停、按下等状态色,组件 CSS 用透明度或阴影模拟 |
| 对比度 | 只计算了背景对比色;文字与背景、按钮文字与按钮之间的对比度不做校验,商家可以配出难以阅读的组合 |
| 暗色模式 | 全仓库未找到 prefers-color-scheme,系统暗色模式不会切换方案 |
迁移到自己主题时
这套写法可以原样用于 Tailwind 或 DaisyUI 主题:Liquid 循环生成 .color-<id> 和变量,CSS 框架的颜色令牌引用这些变量即可。需要透明度时,三元组比完整颜色更方便;如果项目以 color-mix() 为主,则改存完整颜色值。建议同时补上 Dawn 缺少的两项:在 Liquid 中用 color_contrast 等过滤器计算对比度并给出提示,以及为悬停、焦点状态生成派生变量。
建议的验证范围
本篇均未执行:在编辑器中新增第三套方案,查看生成的 CSS;把方案的背景分别设为极暗、中等和明亮的颜色,核对 --color-background-contrast 的取值;把某个分区设为渐变背景,确认 .gradient 生效。