DirectSurface UIDirectSurface UI
开始使用
文档/开始使用

快速开始

本页从一个空的 Vite + TypeScript 项目开始,完成 Canvas 宿主创建、页面与主窗口实例化、应用挂载、首帧运行和资源释放。完成后,你会得到一个可以点击的最小 DirectSurface UI 应用。

启动链路

DirectSurface UI 不是把组件挂到普通 DOM 节点,而是把一棵 Render Tree 挂到已有的 HTMLCanvasElement

<canvas id="app">
  → Application.mount('#app')
  → AppHost / RuntimeHost
  → MainWindow
  → RenderPage
  → Layout 与 Widgets
  → Canvas 2D 像素输出

业务应用通常只直接使用 Application 和返回的 AppHost。不要为了启动普通页面自行实例化底层 RuntimeHost

1. 创建项目并安装

先把团队提供的实际版本 .tgz 文件复制到业务项目,例如 packages/ds-ui-0.1.0.tgz。文件名中的版本号以收到的制品为准;DirectSurface UI 不要求发布到公共 npm registry。

npm create vite@latest ds-ui-hello -- --template vanilla-ts
cd ds-ui-hello
npm install
npm install ./packages/ds-ui-0.1.0.tgz

如需让安装来源随项目一起固定,可以在 package.json 中写入:

{
  "dependencies": {
    "ds-ui": "file:./packages/ds-ui-0.1.0.tgz"
  }
}

提交 package.json 后执行 npm install。无论 .tgz 放在本地目录还是由内部制品系统下载,业务代码都从 ds-ui 包入口导入,不应依赖包内路径。

2. 创建 Canvas 宿主

将项目根目录的 index.html 改为:

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DirectSurface UI Hello</title>
  </head>
  <body>
    <canvas id="app" aria-label="DirectSurface UI 应用"></canvas>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

Application.mount() 只接收已经存在的 HTMLCanvasElement。它不会替业务创建 Canvas,也不能挂到普通 <div>

3. 让 Canvas 填满宿主

创建 src/style.css

html,
body,
#app {
  width: 100%;
  height: 100%;
  margin: 0;
}

body {
  overflow: hidden;
}

#app {
  display: block;
}

CSS 尺寸决定 Canvas 在页面中的显示区域;runtime 会根据逻辑视口和 DPR 同步实际像素尺寸。

4. 创建页面、主窗口并挂载应用

创建 src/main.ts

import './style.css'import {  Application,  MedicalCompactLightTheme,  RenderButton,  RenderPage,  RenderStackPanel,  RenderText,  RenderWindow,  loadDirectSurfaceFonts,  type AppHost,} from 'ds-ui' class HelloPage extends RenderPage {  constructor() {    const status = new RenderText('应用已经挂载', { role: 'secondary' })    const content = new RenderStackPanel({      orientation: 'vertical',      spacing: 12,      padding: 24,      crossAxisAlignment: 'stretch',    })     content.addChild(new RenderText('Hello DirectSurface UI', { role: 'title' }))    content.addChild(status)    content.addChild(new RenderButton({      label: '执行',      onClick: () => {        status.text = `最近点击:${new Date().toLocaleTimeString()}`      },    }))     super({ child: content })  }} class MainWindow extends RenderWindow {  constructor() {    super({      title: 'DirectSurface UI Hello',      chrome: 'none',      contentPadding: 0,      contentSpacing: 0,    })     this.setChildren([new HelloPage()])  }} let host: AppHost | undefined async function bootstrap(): Promise<void> {  await loadDirectSurfaceFonts()   const mainWindow = new MainWindow()  host = Application.mount('#app').run(mainWindow, {    theme: MedicalCompactLightTheme,  })} function dispose(): void {  host?.dispose()  host = undefined} window.addEventListener('beforeunload', dispose, { once: true })const hot = (import.meta as ImportMeta & {  hot?: { dispose(callback: () => void): void }}).hothot?.dispose(dispose) void bootstrap()

上面的源码是独立业务项目的完整入口;文档末尾的运行区会复用同一份页面工厂,并由文档站的共享 Application 承载,因此不会在同一个 Canvas 上重复挂载 runtime。

这里完成了最小应用的全部关键步骤:

  1. new HelloPage() 创建业务页面及其 Render Tree。
  2. new MainWindow() 创建唯一的主窗口,并通过 setChildren() 接入页面。
  3. Application.mount('#app') 找到宿主 Canvas。
  4. .run(mainWindow, options) 创建 runtime、挂载主窗口、初始化主题并调度首帧。
  5. .run() 返回的 AppHost 保存整个应用实例;退出、热更新或宿主卸载时调用 host.dispose()

Application.mount() 是推荐入口。不要写成 new Application().run(...),因为未经过 mount() 的 Application 没有 Canvas,调用 run() 会直接报错。

5. 运行

npm run dev

打开 Vite 输出的本地地址。正常情况下会看到浅色主界面、“Hello DirectSurface UI”和一个可点击按钮。

挂载参数和实例职责

const diagnosticsWindow = new RenderWindow({  title: '诊断示例',  chrome: 'none',})const isDevelopment = location.hostname === 'localhost'const host = Application.mount('#app').run(diagnosticsWindow, {  theme: MedicalCompactLightTheme,  enableLayoutInspector: isDevelopment,  enablePerformanceOverlay: isDevelopment,  context: {    currentUser: { id: 'u-1', name: 'Demo User' },  },})
对象 谁创建 主要职责
HTMLCanvasElement Vue、React、原生 HTML 或其他宿主 提供绘制目标并决定 DOM 生命周期。
HelloPage 业务模块 组合布局、组件和业务交互。
MainWindow 业务应用壳 承载应用根页面、工作区或导航。
Application 框架静态入口 查找 Canvas 并启动应用。
AppHost Application.run() 返回 持有 runtime、应用上下文和浮层服务,并负责统一释放。
RuntimeHost 框架内部创建 管理尺寸、事件、layout、paint、合成和帧调度。

如果 Canvas 位于 Vue、React 或微前端页面内,应在宿主的 mounted/effect 生命周期中执行 Application.mount(...).run(...),并在 unmounted/effect cleanup 中调用同一个 host.dispose()。Canvas DOM 仍由宿主框架负责创建和移除。

常见启动错误

Canvas element "#app" was not found

  • 检查 index.html 中是否存在 <canvas id="app">
  • 确认启动代码在 Canvas 插入 DOM 后执行。
  • 确认选择器指向 Canvas,而不是同名的 <div>

页面存在但没有内容

  • 确认主窗口调用了 setChildren([page])
  • 确认 Canvas 和父容器有非零宽高。
  • 确认没有在启动后立即调用 host.dispose()

热更新后出现重复事件或重复应用

保存 .run() 返回的 AppHost,并在 Vite HMR dispose、React effect cleanup 或 Vue onUnmounted 中调用 host.dispose()

示例与公共 API 的边界

文档中的运行示例会包含演示数据和页面装配代码。它们用于说明公共 API 的组合方式,不会随 ds-ui 包一起导出;业务项目应按自己的服务、状态和模块边界重新组织这些代码。

最终运行效果

完成以上步骤后,应用应显示浅色主界面、“Hello DirectSurface UI”、当前挂载状态和一个可以点击的操作按钮。你可以先在下方直接交互,再使用“全屏运行”检查独立页面效果。

LIVE CANVAS可运行组件示例
ON-DEMAND RUNTIME交互式示例将在进入视区时启动避免文档首屏同时初始化多个 Canvas Runtime

这个运行区与上面的业务入口使用同一个页面工厂,但由文档站已经存在的 Application 承载,避免在同一个 Canvas 上重复创建 runtime。点击“执行操作”后,状态文字应更新时间;这也同时验证了页面挂载、布局、绘制和事件链路。

下一步

  1. 创建第一个页面:继续学习页面、布局和页面注册。
  2. Runtime API:查看 ApplicationRunOptionsAppHost 和运行时能力。
  3. 组件手册总览:按业务场景选择控件。
  4. 布局系统总览:掌握约束、滚动和常用布局组件。
  5. 应用上下文:注入登录态、服务和应用级依赖。