拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Compose Multiplatform HTML Style DSL 实战指南:用类型安全的 Kotlin 编写 CSS 样式表

Compose Multiplatform HTML Style DSL 实战指南用类型安全的 Kotlin 编写 CSS 样式表【免费下载链接】compose-multiplatformCompose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform本文以 tutorials/HTML/Style_Dsl/README.md 教程为主体系统讲解 Compose for Web 中 Style DSL 的核心能力行内样式inline style、样式表Stylesheet、选择器组合、媒体查询Media Query与 CSS 变量并结合html/core模块源码说明每条 Kotlin 声明最终如何被编译为浏览器可执行的 CSS 规则。读完本文你可以在纯 Kotlin 代码中完成整套 Web 样式开发并理解类名生成、规则挂载等底层机制。Style DSL 是什么Style DSL 是 Compose for Web 提供的类型安全样式表 DSL你可以用 Kotlin 代码表达 CSS 规则并根据 Compose 应用的状态动态修改样式。它覆盖两类典型场景行内样式直接在组件上声明style { }块最终生成元素的style属性样式表定义StyleSheet对象通过类名class把规则挂载到组件上支持选择器、伪类、媒体查询与 CSS 变量。核心 API 位于 html/core 模块的org.jetbrains.compose.web.css包下关键文件包括 StyleSheet.kt、StyleScope.kt、StyleSheetBuilder.kt、CSSMediaRule.kt 与选择器实现 CSSSelectors.kt。行内样式Inline Style最直接的用法是在组件的style块中声明属性。属性值使用专用类型如20.px而非字符串从而获得类型检查对于 DSL 未内置的属性可以用property(name, value)自由扩展Div({ style { display(DisplayStyle.Flex) padding(20.px) // 自定义属性或 DSL 未内置支持、需按 spec 指定的属性名写入的属性 property(font-family, Arial, Helvetica, sans-serif) } }) { /* content goes here */ }渲染后的 HTML 等价于div styledisplay: flex; padding: 20px; font-family: Arial, Helvetica, sans-serif;/div从源码结构看style { }块的作用域类型是 StyleScope 接口它的 KDoc 明确了两个职责向元素行内样式添加任意 CSS 属性property方法属性名遵循 CSS spec值可以是String、CSSNumeric、CSSColorValue等专门类型为 CSS 变量赋值variable方法。具体实现类 StyleScopeBuilder 把每个声明收集为StylePropertyDeclaration包含属性名、值、!important标志分别存入properties与variables两个列表之后由 DOM 层统一写出。这也是为什么property的重载接受String/Number等多种值类型——它们都会被包装成StylePropertyValue。样式表Stylesheet类名与规则挂载行内样式只作用于单个元素更工程化的做法是定义一个StyleSheet对象其中每个val xxx by style { }委托声明都会生成一个带前缀的类名object AppStylesheet : StyleSheet() { val container by style { // container 是一个类class display(DisplayStyle.Flex) padding(20.px) // 自定义属性或 DSL 未内置支持、需按 spec 指定的属性名写入的属性 property(font-family, Arial, Helvetica, sans-serif) } } // 样式表需要被挂载mount renderComposable(root) { Style(AppStylesheet) Container { Text(Content) } } Composable fun Container(content: Composable () - Unit) { Div( attrs { classes(AppStylesheet.container) } ) { content() } }渲染后的 HTML 等价于style/style div classAppStylesheet-containerContent/div这段示例背后有三个源码级机制值得理解1. 类名前缀的生成规则。在 StyleSheet.kt 中StyleSheet构造器接受可选的customPrefix若不传则默认取实现类的简类名加连字符${this::class.simpleName}-。因此object AppStylesheet中声明val container by style { }得到的类名就是AppStylesheet-container这解释了示例 HTML 输出。CSSHolder.provideDelegate是这一过程的入口它用$prefix${property.name}拼出类名构造.className形式的选择器把构建出的 CSS 规则注册进样式表最后把类名字符串作为ReadOnlyProperty返回——所以你在组件上写的AppStylesheet.container拿到的是一个可直接传给classes(...)的字符串。2.style { }与Style(...)的区分。类名声明用的是protected fun style(cssRule)生成绑定类而把样式表注入页面的是可组合函数Style(styleSheet: CSSRulesHolder)见 StyleSheet.kt 底部的 inline fun Style。后者委托给 dom/Style.kt 中的 DOM 实现把规则列表写入浏览器CSSStyleSheet——即教程中所说的“Stylesheet needs to be mounted”。StyleSheet同时实现了CSSRulesHolder其cssRules是一个mutableStateListOf从源码结构看这意味着规则列表是可被 Compose 状态系统感知的规则变化时挂载点会随之更新这是“根据 Compose 应用状态修改样式”的底层支撑。3. 最终如何落到浏览器。Style.kt 通过insertRule逐条插入规则头selector {}再由fillRule把每条CSSStyleRuleDeclaration的properties与variables分别调用setProperty/setProperty(--name, ...)写入CSSStyleDeclaration。也就是说DSL 声明先被编译为中间规则对象CSSRuleDeclaration见 CSSRules.kt最后才经由浏览器原生 DOM API 变成真实 CSS——规则对象的stringPresentation()方法Style.kt甚至能把整张表还原为可读 CSS 文本可用于调试与测试。选择器组合SelectorsStyle DSL 同时提供在StyleSheet的init块中声明全局选择器规则的能力并支持把多个选择器组合、统一object AppStylesheet : StyleSheet() { init { // universal 可以替代 *universal style {} * style { fontSize(15.px) padding(0.px) } // 原始选择器raw selector h1, h2, h3, h4, h5, h6 style { property(font-family, Arial, Helvetica, sans-serif) } // 组合选择器选择所有 href 等于 jetbrains 的 a 标签 type(A) attr( name href, value jetbrains, operator CSSSelector.Attribute.Operator.Equals ) style { fontSize(25.px) } } // 便捷地创建 class 选择器 // AppStylesheet.container 可作为 class 用在组件 attrs 中 val container by style { color(Color.red) // 针对该 class 的 hover 伪类 self hover() style { // self 就是 container 自身的选择器 color(Color.green) } } }对应到源码String style { }与selector style { }的重载定义在 GenericStyleSheetBuilder字符串会被包装为RawSelector原样输出type()、attr()、id()、className()等构造器以及plus组合运算符都在 SelectorsScope 中定义。属性选择器支持六种操作符Equals()、ListContains(~)、Hyphened(|)、Prefixed(^)、Suffixed($)、Contains(*)见 CSSSelector.Attribute.Operator运算符生成的Combine选择器会把子选择器拼接输出desc/child/sibling/adjacent则分别对应 CSS 的后代、子代、兄弟、相邻兄弟组合器伪类/伪元素以属性形式暴露hover、active、focus、firstChild、nthChild(...)、before、after、selection等。注意 CSSSelectors.kt 中旧的CSSSelector.PseudoClass.hover等常量已被标记为Deprecated官方建议改为使用SelectorsScope中的属性如上面的hover()函数 /hover属性。其中self是本作用域的“自引用”选择器self hover()即“本类名处于 hover 状态”编译后等价于.AppStylesheet-container:hover。内部实现用CSSSelfSelector占位、在规则构建时再解析为真实选择器StyleSheet.kt因此不能把包含self的选择器与String做拼接——toString()会主动抛出IllegalStateException提示改用selector(your string)见 CSSSelfSelector这是一个值得注意的约束。CSSBuilderImpl.style还有一个细节如果子选择器本身不包含self/根选择器会自动包一层desc(self, selector)CSSBuilder.kt保证在类样式内部写嵌套选择器时作用域正确。媒体查询Media Query响应式布局用media函数表达传入查询条件与一个规则块object AppStylesheet : StyleSheet() { val container by style { padding(48.px) media(mediaMaxWidth(640.px)) { self style { padding(12.px) } } } }实现在 CSSMediaRule.kt常用特性有现成函数mediaMinWidth、mediaMaxWidth、mediaMinHeight、mediaMaxHeightL141-L160底层都是CSSMediaQuery.MediaFeature(name, value)查询条件支持and组合mediaMinWidth(200.px).and(mediaMaxWidth(400.px))源码 KDoc 中给出的示例即为该形式、not(...)取反、combine(...)逗号并列、Raw(字符串)直接写任意查询串L76-L119规则被包装为CSSMediaRuleDeclarationheader渲染为media $query作为分组规则CSSGroupingRuleDeclaration嵌套进样式表最终由 Style.kt 的 CSSGroupingRule.addRule 递归写入浏览器。源码注释中有一个实用提醒Oror连接与NotFeature在 Chrome 中至少当前不可靠“looks like it doesnt work at least in chrome”需要复杂或逻辑时优先用combine(...)的逗号并列写法。CSS 变量Style DSL 对 CSS 自定义属性CSS variables提供了一等支持用variableT()委托声明变量在样式作用域中直接以函数调用形式赋值读取时用.value()或.value(默认值)object MyVariables { // 声明一个变量 val contentBackgroundColor by variableCSSColorValue() } object MyStyleSheet : StyleSheet() { val container by style { // 在 container 作用域内设置变量值 MyVariables.contentBackgroundColor(Color(blue)) } val content by style { // 读取变量值 backgroundColor(MyVariables.contentBackgroundColor.value()) } val contentWithDefaultBgColor by style { // 也可提供默认值 // 当变量此前未被设置时使用默认值 backgroundColor(MyVariables.contentBackgroundColor.value(Color(#333))) } }源码层面StyleScope.ktvariableT()是一个属性委托返回以属性名为变量名的CSSStyleVariableTValue泛型TValue限定取值类型如CSSColorValue、CSSUnitValue、StylePropertyString、StylePropertyNumber赋值走operator fun CSSStyleVariable.invoke(value)最终落到StyleScope.variable(name, value)写入variables列表DOM 层以--name: value;形式输出见 setVariable读取走value(fallback)内部由 variableValue 生成字符串var(--name)或带回退值的var(--name, fallback)——这正是 CSS 变量级联语义的标准写法content类取到container上设置的blue而contentWithDefaultBgColor在变量未设置时回退到#333。完整可运行示例将上述能力组合起来教程给出的完整入口可直接作为 HTML 模板应用的main如下import androidx.compose.runtime.Composable import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.renderComposable object MyVariables { // 声明一个变量 val contentBackgroundColor by variableCSSColorValue() } object MyStyleSheet: StyleSheet() { val container by style { // 在 container 作用域内设置变量值 MyVariables.contentBackgroundColor(Color(blue)) } val content by style { // 读取变量值 backgroundColor(MyVariables.contentBackgroundColor.value()) } val contentWithDefaultBgColor by style { // 也可提供默认值 // 当变量此前未被设置时使用默认值 backgroundColor(MyVariables.contentBackgroundColor.value(Color(#333))) } } object AppStylesheet : StyleSheet() { val container by style { // container 是一个类class display(DisplayStyle.Flex) padding(20.px) // 自定义属性或 DSL 未内置支持、需按 spec 指定的属性名写入的属性 property(font-family, Arial, Helvetica, sans-serif) } } Composable fun Container(content: Composable () - Unit) { Div( attrs { classes(AppStylesheet.container) } ) { content() } } fun main() { renderComposable(rootElementId root) { Div({ style { display(DisplayStyle.Flex) padding(20.px) // 自定义属性或 DSL 未内置支持、需按 spec 指定的属性名写入的属性 property(font-family, Arial, Helvetica, sans-serif) } }) { /* content goes here */ } Style(AppStylesheet) Container { Text(Content) } } }注意Style(AppStylesheet)必须出现在renderComposable内容中教程强调 “Stylesheet needs to be mounted”且只挂载了AppStylesheetMyStyleSheet若要生效也需要同样被Style(...)挂载。仓库中的真实用法参考examples/html/landing示例展示了 Style DSL 在完整应用中的组织方式可以作为落地参考style/Stylesheet.kt 定义全局AppStylesheet : StyleSheet()如val composeLogo by style { }、val composeTitleTag by style { }等具名类style/WtCard.kt 中把卡片主题拆成wtCard、wtCardThemeLight、wtCardThemeDark等多个by style类便于按主题切换组合style/WtCol.kt 则用wtCol2wtCol12与wtColMd3等“断点列数”命名的类实现栅格布局体现类名选择器与响应式命名的实际配合。此外html/core的测试用例html/core/src/jsTest以及 html/compose-compiler-integration 目录覆盖了对该 API 的回归验证html模块自身的构建与运行说明见 html/README.md。小结Style DSL 的设计可以概括为三层声明层StyleScope提供类型化的属性方法display、padding、fontSize…与逃生舱property()variableT()提供类型化的 CSS 变量组织层StyleSheet把属性块编译为CSSRuleDeclaration样式规则、媒体分组、关键帧CSSHolder委托机制自动生成带前缀类名选择器系统SelectorsScopeCSSSelector支持原始字符串、组合、伪类与self自引用挂载层Style(...)可组合函数把规则列表经浏览器CSSStyleSheet.insertRule写入页面且规则列表基于 Compose 可观察状态可随应用状态更新。掌握这套机制后你就可以在 Kotlin 侧完成从行内样式到全局样式表、响应式媒体查询、变量级联的全部样式工作且不再需要手写字符串 CSS。【免费下载链接】compose-multiplatformCompose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门