
基于 Compose HTML 的四个 Web 示例全解游戏、落地页与 React/JS 互操作实战【免费下载链接】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本文围绕 Compose Multiplatform 仓库中examples/html目录下的四类 HTML 示例展开基于 Composable DOM API 开发的 Flappy Bird 风格小游戏、使用 Stylesheet DSL 构建的落地页、Compose 与 React 双向互操作的演示以及通过JsExport把 Compose 组件导出给纯 JavaScript 调用的完整方案。读完本文你将掌握 Compose HTML 的工程搭建、renderComposable渲染入口、CSS 样式 DSL、跨框架互操作接口以及面向浏览器的构建与分发命令并可直接在仓库对应示例目录中复现运行。术语说明本文所指的 Compose HTMLorg.jetbrains.compose.web是 JetBrains 曾称为 Compose for Web 的 DOM 渲染方案如今官方语境中的 Compose for Web 通常指基于 WebAssembly 的实现两者是不同的技术方向阅读下文时请注意区分。目录总览四个 HTML 示例的定位仓库根目录 examples/html/README.md 将 HTML 示例划分为四个独立的 Gradle 工程每个示例都聚焦一个特定主题示例目录核心主题关键源码compose-bird用最基础的 Composable DOM API 写一个浏览器小游戏main.kt、ComposeBirdGame.ktlanding用 Compose 构建完整落地页演示 Stylesheet DSL 与响应式布局Main.kt、Stylesheet.ktwith-reactcompose-in-react 与 react-in-compose 双向互操作ComposeInReactApp.kt、ReactInComposeApp.ktcompose-in-js把Composable函数通过JsExport暴露给原生 JS 调用Composables.kt、configModuleName.js四个示例均以浏览器JS target为目标平台kotlin { js(IR) { browser() } }是它们共通的工程形态可参考 compose-in-js/build.gradle.kts 与 with-react/build.gradle.kts依赖compose.web.core/compose.html.core与compose.runtime。compose-bird用 Composable DOM API 写一个会飞的鸟compose-bird 是一个受 Flappy Bird 启发的极简小游戏其 READMEexamples/html/compose-bird/README.md明确了它的技术定位使用 Compose HTML 库和 radio button单选按钮渲染整个游戏画面。游戏通过方向键Up控制小鸟飞行画面由网格化的 radio input 构成——每个格子要么是被勾选的管道/小鸟要么是禁用状态的空位。渲染入口与键盘控制main.kt 展示了 Compose HTML 的标准启动流程fun main() { val game: Game ComposeBirdGame() val body document.getElementsByTagName(body)[0] as HTMLElement // 在原生 DOM 层注册键盘事件绕过 Composable 抽象 body.addEventListener(keyup, { when ((it as KeyboardEvent).keyCode) { 38 - game.moveBirdUp() // Arrow up } }) renderComposable(rootElementId root) { Div(attrs { style { property(text-align, center) } }) { val gameFrame by game.gameFrame // 游戏循环每 60ms 前进一帧 LaunchedEffect(Unit) { while (!gameFrame.isGameOver) { delay(60) game.step() } } Header(gameFrame) // ... 网格渲染 } } }这里出现了两个关键 APIrenderComposable(rootElementId root)把 Compose 组合挂载到 HTML 中 id 为root的元素内成为被 Compose runtime 管理的 DOM 子树LaunchedEffectdelay(60)用协程驱动游戏循环每 60ms 调用一次game.step()推进帧状态这是 Compose 声明式 UI 与命令式游戏循环结合的典型写法。游戏逻辑与碰撞检测游戏逻辑被抽象为 Game.kt 中的接口interface Game { val gameFrame: StateGameFrame fun step() fun moveBirdUp() }其实现 ComposeBirdGame.kt 使用mutableStateOf持有GameFrame每帧的完整快照小鸟位置、管道列表、得分、胜负状态游戏区域为 15 列 × 9 行的网格共 10 根管道TOTAL_TUBES。step()按时间差推进管道左移与小鸟下落moveBirdUp()仅置位标记真正的上移在下一帧step()中消费碰撞检测则检查小鸟当前列BIRD_COLUMN 1的管道坐标是否为true。全部游戏状态都由 Compose 状态驱动 UI 重绘没有直接操作 DOM 的代码。运行与分发命令compose-bird 的运行与打包命令来自其 README./gradlew jsBrowserRun # 开发模式运行 ./gradlew jsBrowserDistribution # 构建产物到 build/distributions直接打开其中的 index.html 即可分发其中jsBrowserDistribution会生成可供静态托管的完整前端产物这是所有 Compose HTML 项目通用的发布方式。landingStylesheet DSL 驱动的落地页landing 示例examples/html/landing/README.md说明其页面描述了 Compose HTML旧称 Compose for Web。它演示了 Compose HTML 中与声明式 UI 配套的CSS 样式 DSL不再手写 CSS 文件而是在 Kotlin 中以类型安全的方式声明样式。页面结构与入口Main.kt 用组合式组件拼出整页Header、Intro、ComposeWebLibraries、GetStarted、CodeSamples、JoinUs与页脚并通过Style(AppStylesheet)挂载全局样式表。样式类按网格系统、文字、间距、区块等维度拆分在 style/ 目录下WtRow、WtCol、WtText、WtSection、WtOffest等组件层components/再通过classes(...)复用这些类——这一样式 DSL 定义 组件组合引用的结构正是中大型 Compose HTML 页面的推荐组织方式。Stylesheet DSL 的核心能力Stylesheet.kt 展示了三个重要特性CSS 变量通过variableCSSColorValue()声明变量在universal style中赋值如wtColorGreyLight(Color(#f4f4f4))随后用.value()读取类型安全样式属性padding(5.px, 12.px)、maxWidth(100.percent)、borderRadius(4.px, 4.px, 4.px)等 Kotlin DSL 写法取代字符串拼接媒体查询media(mediaMaxWidth(640.px)) { self style { ... } }实现移动端响应式适配该示例中 640px 断点下会收紧间距单位。此外landing 的代码样例区CodeSnippets.kt本身就是一组可运行的 DSL 教学片段涵盖简单计数器、声明并使用StyleSheet、声明并使用 CSS 变量、hover(self)选择器与媒体查询、在组件内动态生成 CSS 类MyStyleSheet.css { self :hover style { ... } }并借助HighlightJs在前端高亮 Kotlin 代码。运行命令与其余示例一致landing 支持./gradlew :jsBrowserRun # 开发模式运行或在 IDE 中选择browser运行配置启动。with-reactCompose 与 React 双向互操作with-react 示例examples/html/with-react/README.md验证了一个关键场景在 React 应用中嵌入 Compose 渲染的内容同时也在 Compose 页面中渲染 React 组件。其入口 Main.kt 通过 URL 查询参数切换应用?appcomposeApp进入 React-in-Compose 示例?appreactApp进入 Compose-in-React 示例。Compose 渲染进 ReactuseComposeComposeInReactApp.kt 定义了一个自定义 React effect——useCompose。文档特别强调它不是任何库的一部分而是示例自定义的桥接封装。其工作机制为private fun T useCompose( containerRef: RMutableRefHTMLElement, stateInitialValue: T, stateValueProvider: () - T, composable: Composable (state: StateT) - Unit ) { val mutableState useRef(mutableStateOf(stateInitialValue)) useEffect { mutableState.current?.value stateValueProvider() } useLayoutEffectWithCleanup(dependencies emptyList()) { val composition renderComposable(containerRef.current!!) { composable(mutableState.current!!) } returnuseLayoutEffectWithCleanup { composition.dispose() } // 卸载时释放组合 } }核心思路React 组件用一个div挂载点ref通过renderComposable(containerRef.current!!)在该挂载点内启动 Compose 组合React 侧的状态变化经stateValueProvider同步进 Compose 的mutableStateOf从而驱动 Compose 内容更新。示例用滑块控制条目数量React 状态变化实时反映到 Compose 渲染的Item N列表上。React 渲染进 ComposeUseReactEffect反向的 ReactInComposeApp.kt 提供UseReactEffect同样为示例自带的桥接封装非库 APIComposable private fun ElementScopeHTMLElement.UseReactEffect( key: Any?, content: RBuilder.() - Unit ) { DisposableEffect(key) { render(scopeElement) { content() } // 在 Compose 管理的元素内挂载 React 根 onDispose { } } DisposableEffect(Unit) { onDispose { unmountComponentAtNode(scopeElement) } // 卸载 React 根 } }它把scopeElementCompose 当前作用域元素作为 React 的挂载容器key变化时强制 React 重新渲染新内容。示例用它包装了reactPlayer播放器组件点击按钮切换视频 URLReact 侧播放器随之刷新。使用现有 React 组件external 声明要复用 NPM 上的现成 React 组件需要声明external。以 ReactYoutubePlayer.kt 为例它通过文件级注解绑定 NPM 包并导出组件类型file:JsModule(react-youtube-lite) file:JsNonModule JsName(ReactYouTubeLite) external val reactPlayer: RClassReactYouTubeProps external interface ReactYouTubeProps : RProps { var url: String }对应的依赖声明见 with-react/build.gradle.kts除kotlin-react、kotlin-react-dom、kotlin-styled包装器外还需用implementation(npm(react, 17.0.2))、implementation(npm(react-youtube-lite, 1.0.1))声明对应的 NPM 依赖Kotlin/JS 构建会自动解析并打入 bundle。运行命令./gradlew :jsBrowserRun # 开发模式运行用 ?appcomposeApp / ?appreactApp 切换方向compose-in-js把 Composable 导出给纯 JavaScriptcompose-in-js 是四者中互操作最彻底的一个让完全没有 Kotlin 背景的 JS 开发者也能使用 Compose 组件。其 README.MD 给出了完整的理论推导与代码下文完整继承并展开。为什么不能直接调用 ComposableComposable函数无法从 JS 直接调用——编译器插件会给每个Composable函数注入隐式参数。解决方案分四步用JsExport注解包装Composable函数为普通函数使其可被 JS 直接调用每次调用包装函数都会创建一次 composition即 DOM 中由 Compose runtime 控制的一块区域不再需要时必须dispose该 composition释放其资源组合的状态由自定义Controller类控制对外暴露必要的状态更新方法。完整简化示例原文档给出的可复制示例与 Composables.kt 的实现完全一致// Composables.kt JsExport abstract class ComposeCounterAppController { abstract fun setCount(newCount: Int) abstract fun dispose() } JsExport fun ComposeCounterApp(rootId: String, onCountChange: (Int) - Unit {}): ComposeCounterAppController { var count: Int by mutableStateOf(0) val composition renderComposable(rootElementId rootId) { Counter(count) { count it onCountChange(count) } } return object : ComposeCounterAppController() { override fun setCount(newCount: Int) { count newCount } override fun dispose() { composition.dispose() } } }renderComposable(rootElementId rootId)要求rootId对应的 HTML 元素已存在于 DOM 中Compose 将接管该元素的内容管理。JS 侧则只需直接调用导出函数// 完整示例见 src/jsMain/resources/use_compose.js counterController MyComposables.ComposeCounterApp(counterByCompose, (newCount) { console.log(Counter was updated. New value ${newCount}); });JS 侧拿到的counterController可以随时setCount(newCount)从外部更新 Compose 状态或调用dispose()销毁组合——状态控制权被完整移交给了非 Compose 代码。覆盖 webpack 模块名为了让 JS 侧使用简洁的命名空间示例通过 webpack 配置覆盖模块名configModuleName.jsconfig.output config.output || {}; config.output.library MyComposables;这样ComposeCounterApp便以MyComposables.ComposeCounterApp(...)的形式暴露。这是 Kotlin/JS 的 webpack 配置目录约定放在webpack.config.d/下的.js文件会被自动合并进构建配置。运行与构建分发compose-in-js 的命令与其他示例略有不同覆盖开发运行与生产构建两个阶段注意jsRun是:jsBrowserDevelopmentRun的别名语义./gradlew jsRun # 开发模式运行 ./gradlew :jsBrowserDevelopmentRun # 与上等价开发模式 ./gradlew jsBrowserProductionWebpack # 生产构建生产构建产物输出到build/distributions其中compose-in-js.js的文件名由工程/模块名决定在 HTML 中按序引入即可使用导出函数script srccompose-in-js.js/script script srcuse_compose.js/script原文档同时给出了官方文档索引Kotlin/JS 与 JS 互操作、JavaScript modules、webpack bundling 三篇如需深入了解JsExport、模块系统与打包细节可循此路径继续查阅。四个示例的构建基座与 IDE 运行所有示例共享同一套 Gradle 配置骨架。以 compose-in-js 为例build.gradle.kts 展示了 HTML 工程的最小插件集合plugins { kotlin(multiplatform) kotlin(plugin.compose) id(org.jetbrains.compose) }并显式启用binaries.executable()导出函数可用的前提与浏览器 targetkotlin { js(IR) { browser { testTask { useKarma { useChromeHeadless() useFirefox() } } } binaries.executable() } sourceSets { val jsMain by getting { dependencies { implementation(compose.web.core) implementation(compose.runtime) } } } }compose-bird的 settings.gradle.kts 进一步揭示了版本来源kotlin(multiplatform)、kotlin(plugin.compose)与org.jetbrains.compose三个插件均由gradle.properties中声明的kotlin.version、compose.version统一管理并从 gradlePluginPortal、mavenCentral 及 JetBrains 的 cmp dev 仓库解析。四个示例的运行方式完全统一命令行./gradlew :jsBrowserRun或各示例 README 中列出的等价变体或在 IDE 中选择browser运行配置启动。小结通过examples/html下的四个示例Compose HTML 的能力版图可以完整串起来compose-bird证明仅凭 Composable DOM API 状态 协程循环就能实现完整游戏landing展示了 Stylesheet DSL 在真实页面中的工程化组织CSS 变量、媒体查询、组件化样式类with-react提供了与 React 生态双向共存的桥接范式useCompose/UseReactEffect均为示例自定义封装非库 APIcompose-in-js则借助JsExport与 webpack 模块名覆盖把 Compose 组件封装成 JS 可直接调用的黑盒。结合各示例的 README 与src/jsMain源码你可以在仓库中直接复现运行并将其中的模式迁移到自己的 Compose HTML 项目中。【免费下载链接】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),仅供参考