> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/2d-ui/ui-positioning.md).

# UI 定位

设置 UI 实体的位置、缩放、内边距和其他属性。

对于各种 UI 内容，请使用 `uiTransform` 组件来设置实体的大小、位置以及与对齐相关的其他属性。

该 `uiTransform` 组件在屏幕的 2D 空间中工作，很像 `Transform` 组件在场景的 3D 空间中工作。

***ui.tsx 文件：***

```ts
import { UiEntity, ReactEcs } from '@dcl/sdk/react-ecs'
import { Color4 } from '@dcl/sdk/math'

export const uiMenu = () => (
	<UiEntity
		uiTransform={{
			width: '200px',
			height: '100px',
			justifyContent: 'center',
			alignItems: 'center',
		}}
		uiBackground={{ color: Color4.Green() }}
	/>
)
```

***index.ts 文件：***

```ts
import { ReactEcsRenderer } from '@dcl/sdk/react-ecs'
import { uiMenu } from './ui'

export function main() {
    ReactEcsRenderer.setUiRenderer(uiMenu)
}
```

{% hint style="warning" %}
**📔 注意**：本页下面的所有代码片段都假设你有一个 `.ts` 类似于上面的内容，正在运行 `ReactEcsRenderer.setUiRenderer()` 函数。
{% endhint %}

## 定位属性

UI 实体的对齐基于 Flexbox 对齐模型。这是一个非常强大的模型，可用于在大小可能不同的模态窗口中动态组织嵌套实体。

{% hint style="info" %}
**💡 提示**：Decentraland 的 UI 实现基于 [Yoga](https://yogalayout.com/docs/)。请阅读 [本文](https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/) ，以获得对 Flexbox 中可用属性的非常易懂且深入的介绍。
{% endhint %}

### 实体大小

使用 `width` 和 `height` 来设置实体的大小。支持以下几种值：

* `auto`：大小会自适应以适合内部内容。这对于长度可能变化的文本非常方便。将值写为“auto”。
* **百分比**：作为父级测量值的百分比。将值写成以“%”结尾的字符串，例如 `10 %`.
* **像素**：将值写成数字。
* **屏幕宽度或高度**：可使用 vw（视口宽度）和 vh（视口高度）来表示运行 Decentraland 的窗口总尺寸的一部分。例如 `10vw` 表示窗口宽度的 10%， `25vh` 表示窗口高度的 25%。

请注意，这些属性会影响 **default** 该项的大小，即在执行任何 flex grow 和 flex shrink 计算之前该项的大小。最终大小可能会根据父实体的大小以及所设置的 Flexbox 属性而以不同方式解释。

{% hint style="warning" %}
**📔 注意**：在同时支持数字和字符串的属性中，如果要将值设置为像素，请写数字。如果要将这些字段设置为父级测量值的百分比，请将值写成以“%”结尾的字符串，例如 `10 %`。你也可以通过将字符串结尾写成 `px`来将像素值写成字符串，例如 `200px`.

* 当值以百分比表示时，它们始终是相对于父级容器的。你的 UI 的顶层实体也有父级：渲染器会将其放入 [`screenInset`](/creator/content-creator-zh/chang-jing-sdk7/2d-ui/onscreen-ui.md#screen-inset-area)所选区域中，默认情况下该区域是设备安全区域。因此，根级 `100%` 是该区域的 100%——在比屏幕窄的手机上如此，在桌面端二者重合。如果你需要相对于整个屏幕计算百分比，请传入 `screenInset: 'none'` 。
* 如果值以像素表示，它们不会受到父级缩放的影响，但它们也不是 **不是** 原始屏幕像素：它们会乘以从 [虚拟屏幕](/creator/content-creator-zh/chang-jing-sdk7/2d-ui/onscreen-ui.md#screen-virtual-scale)派生出的 UI 缩放因子，该因子默认处于启用状态。A `width: 200` 表示“虚拟屏幕大小下屏幕上的 200 px”，并会在任何其他尺寸上按比例放大或缩小。
* 如果值以 `vh` 或 `vw`表示，则它们是整个窗口的百分比，不受父级缩放、虚拟屏幕或 `screenInset`.

对于 `auto` 影响。为了使 width/height 生效，适用以下规则：

* 将 width/height 设为“auto”的 UiTransform 应该具有 `alignSelf`: `“center”`/`“flex-start”`/`“flex-end”` 或 `positionType: “absolute”`
* 如果子元素的 UiTransform 使用 `positionType: “absolute”`，父级不会随其大小/位置调整
* 如果子元素的 UiTransform 使用任何位置覆盖，父级不会随其大小/位置调整
  {% endhint %}

还可以使用以下其他属性以更高级的方式调整大小：

* `maxWidth` 和 `maxHeight`: *数值* 或字符串（如 height 和 width）。实体可具有的最大尺寸。
* `minWidth` 和 `minHeight`: *数值* 或字符串（如 height 和 width）。实体可具有的最小尺寸。如果父级太小，无法容纳实体的最小尺寸，它们会溢出父级。
* `flexBasis`：这是一种与轴无关的方式，用于提供沿主轴方向的项目默认大小。设置子项的 flex basis 类似于：如果其父级是 flex direction: row 的容器，则设置该子项的 width；如果其父级是 flex direction: column 的容器，则类似于设置子项的 height。

```ts
import { UiEntity, ReactEcs } from '@dcl/sdk/react-ecs'
import { Color4 } from '@dcl/sdk/math'

export const uiMenu = () => (
	<UiEntity
		uiTransform={{
			alignSelf: 'center',
			alignContent: 'center',
			width: '80%',
			height: '30%',
			minWidth: 300,
			maxWidth: 2500,
			margin: { left: '10%', right: '10%' },
		}}
		uiBackground={{ color: Color4.Green() }}
	/>
)
```

### 排列子实体

默认情况下，子实体相对于其父级的左上角定位。你可以使用 `justifyContent` 和 `alignItems` 等属性来改变这种行为。

{% hint style="info" %}
**💡 提示**：任何引用 *内容* 的属性都指向主轴上的实体（由 `flexDirection`决定）。任何引用
{% endhint %}

* `flexDirection`：Flex direction 控制节点子项的布局方向。这也称为主轴。主轴是子项布局的方向。交叉轴是与主轴垂直的轴，或者说是换行排列所沿用的轴。它的值来自 `FlexDirectionType` 类型。可用选项如下：
  * `row` （默认）
  * `row-reverse`
  * `column`
  * `column-reverse`
* `justifyContent`：此属性描述了如何在容器的主轴上对齐子项。例如，你可以使用此属性将子项在 `flexDirection` 设为 row 的容器中水平居中，或在 `flexDirection` 设为 column 的容器中垂直居中。此属性的值必须来自 `JustifyType` 类型。可能的值有：
  * `flex-start` （默认）：将容器的子项对齐到容器主轴的起点。
  * `flex-end`：将容器的子项对齐到容器主轴的终点。
  * `center`：将容器的子项居中对齐到容器主轴。
  * `space-between`：在容器主轴上均匀分布子项，并将剩余空间分配在子项之间。
  * `space-around`：在容器主轴上均匀分布子项，并在子项周围分配剩余空间。与 space-between 相比，使用 space-around 会将空间分配到第一个子项的开头和最后一个子项的结尾。
  * `space-evenly`：在容器主轴上均匀分布子项，并分配剩余空间，使子项之间以及子项与容器边缘之间的间隙都相等。
* `alignItems`：描述如何沿容器的交叉轴对齐子项。align items 与 justify content 非常相似，但它不是应用于主轴，而是应用于交叉轴。此属性需要来自 `AlignType` 类型。可用选项如下：
  * `拉伸`： （默认）拉伸容器的子项以匹配容器交叉轴的高度。
  * `flex-start`：将容器的子项对齐到容器交叉轴的起点。
  * `flex-end`：将容器的子项对齐到容器交叉轴的终点。
  * `center`：将容器的子项居中对齐到容器交叉轴。
  * `baseline`：沿共同基线对齐容器的子项。单个子项可以设置为其父级的参考基线。
* `alignSelf`：align self 具有与 `alignItems` 相同的选项和效果，但不是影响容器中的子项，而是可以将此属性应用于单个子项，以改变其在父级中的对齐方式。align self 会覆盖父级通过 align items 设置的任何选项。它的值来自 `AlignType`以无代码方式触发声音播放，参见 `alignItems` 。有关这些选项的详细信息，请参见上文。
* `alignContent`：align content 定义了沿交叉轴的行分布。只有在使用 `flexWrap`将项目换行到多行时，这才会生效。它的值来自 `AlignType` 类型。可用选项如下：
  * `flex-start`： （默认）将换行对齐到容器交叉轴的起点。
  * `flex-end`：将换行对齐到容器交叉轴的终点。
  * `拉伸`：拉伸换行以匹配容器交叉轴的高度。
  * `center`：将换行居中对齐到容器交叉轴。
  * `space-between`：在容器主轴上均匀分布换行，并将剩余空间分配在各行之间。
  * `space-around`：在容器主轴上均匀分布换行，并在各行周围分配剩余空间。与 space-between 相比，使用 space-around 会将空间分配到第一行的开头和最后一行的结尾。
* `flexGrow`：这描述了容器内的任何空间应如何沿主轴分配给其子项。在布局完子项后，容器会根据子项指定的 flex grow 值分配任何剩余空间。Flex grow 接受任何大于等于 0 的浮点值，0 为默认值。容器会按照子项的 flex grow 值加权分配剩余空间。
* `flexShrink`：描述当子项总大小在主轴上超过容器大小时，如何沿主轴缩小子项。flex shrink 与 flex grow 非常相似，如果把任何溢出的大小视为负的剩余空间，也可以用同样的方式理解。这两个属性也可以很好地协同工作，允许子项按需增长和收缩。Flex shrink 接受任何大于等于 0 的浮点值，1 为默认值。容器会按照子项的 flex shrink 值加权缩小子项。
* `overflow`：决定当实体的子项大小超过其父级时会发生什么。它使用来自 `OverflowType` 类型。
  * `hidden`：溢出的实体会变得不可见。
  * `visible`：溢出的实体会超出父级的边界。
  * `scroll`：该区域会变为可滚动，允许玩家滚动查看溢出的内容。请参见 [可滚动容器](#scrollable-containers) 了解详情。
* `flexWrap`：flex wrap 属性设置在容器上，用于控制当子项在主轴方向上超过容器大小时会发生什么。默认情况下，如有需要，子项会沿主轴换行成多行。如果使用 `nowrap`禁用换行，子项会被强制放入单行（这可能会缩小实体）。wrap reverse 的行为与 wrap 相同，但行顺序相反。此属性的值来自 `FlexWrapType` 类型。
  * `wrap`
  * `nowrap`
  * `wrap-reverse`

### 外边距和内边距

* `margin`：此属性影响节点外侧的间距。带有 margin 的节点会将自身相对于父级边界偏移，同时也会影响任何兄弟节点的位置。若父级采用自动大小，节点的 margin 会计入父级的总大小。用于设置实体与其父级边距之间的空间。预期值是一个包含以下属性的对象： `顶部`, `左侧`, `底部`、以及 `右侧`.
* `padding`：此属性影响其所应用节点的大小。在 Yoga 中，padding 的行为就像设置了 box-sizing: border-box;。也就是说，如果实体已显式设置大小，padding 不会增加实体总大小。对于自动大小的节点，padding 会增加节点大小并偏移任何子项的位置。预期值是一个包含以下属性的对象： `顶部`, `左侧`, `底部`、以及 `右侧`.

### 精细调整位置

在 Flexbox 中，实体的位置主要由其父子关系以及父级和子级上设置的排列属性决定。你通常不需要完全设置 `位置` 属性。但如果你确实想微调它，或完全覆盖 Flexbox 的正常流并设置绝对位置，以下是相关属性：

* `positionType`：定义实体如何定位。它使用来自 `PositionType` 枚举。
  * `relative`： （默认）默认情况下，实体是相对定位的。这意味着实体会按照布局的正常流进行定位，然后根据 `顶部`, `右侧`, `底部`、以及 `左侧`的值相对于该位置进行偏移。该偏移不会影响任何兄弟实体或父实体的位置。
  * `absolute`：当绝对定位时，实体不参与正常的布局流。它会独立于其兄弟节点进行布局。其位置由 `顶部`, `右侧`, `底部`、以及 `左侧` 值列表。
* `位置`：位置值 `顶部`, `右侧`, `底部`、以及 `左侧` 的行为会因 `positionType`而不同。对于相对实体，它们会在指定方向上偏移实体的位置。对于绝对实体，这些属性则指定实体某一侧与父级相同一侧之间的偏移。预期值是一个包含以下属性的对象： `顶部`, `左侧`, `底部`、以及 `右侧`.

{% hint style="warning" %}
**📔 注意** ： `顶部` 或 `左侧` 的正值表示从父级同一边缘向内测量的距离。示例：若要将组件定位为相对于父级的顶部和左侧保留 20 像素边距，请设置 `位置` 为 `{ top: 20, left: 20 }`.
{% endhint %}

### 可见性

* `显示`：确定实体是否可见。若要使实体不可见，请设置 `显示` 为 `none`.

### Z Index

该 `zIndex` 属性用于 `UiEntity` 确定实体的渲染顺序。具有更高 `zIndex` 的实体会渲染在较低 `zIndex`的实体之上。默认 `zIndex` 是 0。

```ts
import { UiEntity, ReactEcs } from '@dcl/sdk/react-ecs'
import { Color4 } from '@dcl/sdk/math'

export const uiMenu = () => (
	<UiEntity
		uiTransform={{
			zIndex: 4
		}}
		uiBackground={{ color: Color4.Green() }}
	/>
)
```

{% hint style="warning" %}
**📔 注意** ：该 `zIndex` 属性只能对直接兄弟元素进行相对排序，不能用于将实体渲染到布局树其他部分之上。用 HTML/CSS 的术语来说，每个 DCL UI 元素都会创建一个新的 [堆叠上下文](https://web.dev/learn/css/z-index#stacking_context)。若要让通过 `addUiRenderer()` 添加的独立 UI 模块彼此排序，请使用 `zIndex` 的 [渲染器选项](/creator/content-creator-zh/chang-jing-sdk7/2d-ui/onscreen-ui.md#stacking-order-between-ui-modules) 。

默认的 Decentraland UI，包括地图、聊天等，始终会渲染在所有其他 UI 元素之上。
{% endhint %}

## 可滚动容器

当某个 UI 实体的内容超过其分配大小时，你可以通过设置 `overflow` 为 `scroll` 在实体的 `uiTransform`中将该区域设为可滚动。然后玩家就可以通过拖动或使用鼠标滚轮来滚动内容。

要创建可滚动容器，父实体必须具有固定大小（使用 `width` 和 `height`），并且子项必须超过该大小。

```ts
import { UiEntity, Label, ReactEcs } from '@dcl/sdk/react-ecs'
import { Color4 } from '@dcl/sdk/math'

export const scrollableMenu = () => (
	<UiEntity
		uiTransform={{
			width: 300,
			height: 400,
			overflow: 'scroll',
			flexDirection: 'column',
		}}
		uiBackground={{ color: Color4.fromHexString('#1a1a1a') }}
	>
		{/* 这些子项超过了父级 400px 的高度，使该区域可滚动 */}
		<Label value="条目 1" fontSize={18} uiTransform={{ width: '100%', height: 80 }} />
		<Label value="条目 2" fontSize={18} uiTransform={{ width: '100%', height: 80 }} />
		<Label value="条目 3" fontSize={18} uiTransform={{ width: '100%', height: 80 }} />
		<Label value="条目 4" fontSize={18} uiTransform={{ width: '100%', height: 80 }} />
		<Label value="条目 5" fontSize={18} uiTransform={{ width: '100%', height: 80 }} />
		<Label value="条目 6" fontSize={18} uiTransform={{ width: '100%', height: 80 }} />
		<Label value="条目 7" fontSize={18} uiTransform={{ width: '100%', height: 80 }} />
	</UiEntity>
)
```

这对于构建长列表、库存、聊天记录、排行榜，或任何内容可能增长到超出屏幕显示范围的面板都很有用。

你也可以在其他 UI 布局中嵌套可滚动容器。例如，一个带有固定头部和可滚动主体的对话模态框：

```ts
import { UiEntity, Label, ReactEcs } from '@dcl/sdk/react-ecs'
import { Color4 } from '@dcl/sdk/math'

export const dialogWithScroll = () => (
	<UiEntity
		uiTransform={{
			width: 400,
			height: 500,
			flexDirection: 'column',
		}}
		uiBackground={{ color: Color4.fromHexString('#2a2a2a') }}
	>
		{/* 固定头部 */}
		<Label
			value="排行榜"
			fontSize={22}
			uiTransform={{ width: '100%', height: 60 }}
		/>

		{/* 可滚动主体 */}
		<UiEntity
			uiTransform={{
				width: '100%',
				flexGrow: 1,
				overflow: 'scroll',
				flexDirection: 'column',
			}}
		>
			<Label value="1. Alice - 9500" fontSize={16} uiTransform={{ width: '100%', height: 50 }} />
			<Label value="2. Bob - 8200" fontSize={16} uiTransform={{ width: '100%', height: 50 }} />
			<Label value="3. Charlie - 7800" fontSize={16} uiTransform={{ width: '100%', height: 50 }} />
			<Label value="4. Diana - 6100" fontSize={16} uiTransform={{ width: '100%', height: 50 }} />
			<Label value="5. Eve - 5500" fontSize={16} uiTransform={{ width: '100%', height: 50 }} />
			<Label value="6. Frank - 4900" fontSize={16} uiTransform={{ width: '100%', height: 50 }} />
			<Label value="7. Grace - 4200" fontSize={16} uiTransform={{ width: '100%', height: 50 }} />
			<Label value="8. Hank - 3800" fontSize={16} uiTransform={{ width: '100%', height: 50 }} />
			<Label value="9. Ivy - 3100" fontSize={16} uiTransform={{ width: '100%', height: 50 }} />
			<Label value="10. Jack - 2700" fontSize={16} uiTransform={{ width: '100%', height: 50 }} />
		</UiEntity>
	</UiEntity>
)
```

{% hint style="info" %}
**💡 提示**：使用 `flexGrow: 1` 将其设置在可滚动实体上，使其填满父级剩余空间，这样当其他兄弟元素（如头部或底部）大小变化时，它也会随之适配。
{% endhint %}

## 响应式 UI 大小

不同屏幕大小的玩家可能会以不同方式看到你的 UI 布局。像素值会相对于 [虚拟屏幕](/creator/content-creator-zh/chang-jing-sdk7/2d-ui/onscreen-ui.md#screen-virtual-scale) 为你进行缩放，因此同一个 UI 会在不同分辨率下保持其比例—— **你不需要自己计算缩放因子**，否则会导致缩放执行两次。

{% hint style="warning" %}
**📔 注意**: `devicePixelRatio` 不会参与 UI 布局。它只是一个显示密度提示——可用于在 1x、2x 或 3x 纹理版本之间进行选择——除此之外别无他用。如果你的场景是基于较早的 SDK 版本设定大小的，那么在高密度（视网膜和移动）屏幕上，像素大小的 UI 可能会渲染得高出 2–3 倍，并且需要重新检查任何手动微调过的内容。
{% endhint %}

{% hint style="danger" %}
**📔 删除你自己的缩放因子。** 如果你的场景会将大小乘以一个它从 `UiCanvasInformation` 计算出的因子——通常是 `Math.min(width / 1920, height / 1080)` ——请移除该乘数。它与 SDK 现在默认应用的因子相同，因此同时保留两者会让你的 UI 随屏幕尺寸呈二次方增长。如果你更愿意只保留自己的因子，请使用 `setUiRenderer(ui, { virtualWidth: 0, virtualHeight: 0 })`.
{% endhint %}

`UiCanvasInformation`，它默认添加到场景的根实体中，仍然是处理缩放无法表达的布局决策的正确工具——例如在窄屏上使用不同的对话布局，从 `devicePixelRatio`中选择纹理分辨率，或者自行读取内边距区域。它不是用于大小设定的正确工具。

该 `UiCanvasInformation` 组件保存以下信息：

* `height`：画布高度（像素）
* `width`：画布宽度（像素）
* `devicePixelRatio`：设备物理像素分辨率与画布像素之间的比例。可作为显示密度提示，例如在 1x、2x 或 3x 纹理版本之间进行选择。
* `interactableArea`：一个 `BorderRect` 对象，详细说明场景 UI 元素所分配的区域。该对象包含以下值： `顶部`, `底部`, `左侧` 和 `右侧`，其中每一项都是屏幕该边缘上被浏览器 UI 占用的像素数。
* `screenInsetArea`：一个 `BorderRect` 对象，详细说明设备或平台 UI 预留的屏幕内边距区域（安全边距），例如移动端的刘海、状态栏、主页指示器或圆角。该对象包含以下值： `顶部`, `底部`, `左侧` 和 `右侧`，其中每一项都是该屏幕边缘上预留的像素数。在桌面端这通常是 `0` 四周都如此。

{% hint style="warning" %}
**📔 注意** 不同的 Decentraland 浏览器在这些值上会有所不同，因为平台的全局 UI 可能不同，而且当用户展开或隐藏不同的全局 UI 菜单时，这些值也可能动态变化。
{% endhint %}

```ts
import { engine, UiCanvasInformation } from "@dcl/sdk/ecs"

export function Main() {
  const canvas = UiCanvasInformation.getOrNull(engine.RootEntity)
  if (!canvas) return
  console.log("CANVAS DIMENSIONS: ", canvas.width, canvas.height)
}
```

关于 UI 大小的其他最佳实践：

* 如果任何 UI 元素的宽度或高度是动态的，也最好使用 `maxWidth`, `minWidth`, `maxHeight`、以及 `minHeight` 参数来确保它们保持在合理范围内。
* 数字字体大小是一个虚拟像素值，会像其他值一样按比例缩放。如果你希望改为以画布为基准来度量，从而不受虚拟屏幕影响，请传入 `vw`/`vh` 字符串——请参见 [响应式文本大小](/creator/content-creator-zh/chang-jing-sdk7/2d-ui/ui_text.md#responsive-text-size)

{% hint style="info" %}
**💡 提示**：有关 UI 大小设置的工作示例，请参见 [`81,-2-ui-screen-inset-area`](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/81,-2-ui-screen-inset-area) 测试场景，它会同时运行全部三个 `screenInset` 模式（`'none'`, `'device'`, `'interactable'`）作为三个并存的渲染器，并输出实时 `screenInsetArea` 和 `interactableArea` 值，以及 [`76,-10-UiCanvasInformation`](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/76,-10-UiCanvasInformation)，它会在每一帧读取 `UiCanvasInformation` 以自适应地设置 UI 大小。
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/2d-ui/ui-positioning.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
