> 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_button_events.md).

# UI 按钮事件

处理 UI 实体上的按钮事件。

要在 UI 中创建按钮，请创建一个 `按钮` 具有以下属性的 UI 元素：

* `值`：包含要在按钮上显示的文本的字符串。
* `onMouseDown`：每当用户在实体上按下指针按钮时运行的回调函数。
* `uiTransform`：UI 元素的定位属性。

以下示例展示如何创建可点击的 UI 按钮。

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

```tsx
import { Button } from '@dcl/sdk/react-ecs'

export const uiMenu = () => (
	<Button
		value="点击我"
		uiTransform={{ width: 100, height: 100 }}
		onMouseDown={() => {
			console.log('点击了 UI')
		}}
	/>
)
```

***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 定义外部编写点击时执行的函数，并通过名称引用它。这有助于保持 UI 代码更易读；如果多个可点击的 UI 实体需要调用同一个函数，这也很有用。

```tsx
import { Button } from '@dcl/sdk/react-ecs'

function handleClick() {
	// 在 onClick 时执行操作
	console.log('点击了 UI')
}
export const uiMenu = () => (
	<Button
		value="点击我"
		uiTransform={{ width: 100 }}
		onMouseDown={handleClick}
	/>
)
```

以下字段可以添加到 `按钮` UI 元素：

* `onMouseDown`：每当用户在实体上按下指针按钮时运行的回调函数。
* `onMouseUp`：每当指针指向实体时松开指针按钮所运行的回调函数。
* `onMouseEnter`：每当指针开始悬停在按钮上时运行的回调函数。
* `onMouseLeave`：每当指针停止悬停在按钮上时运行的回调函数。
* `颜色`：按钮上文本的颜色。
* `字体`：按钮上文本的字体。
* `文本对齐`：按钮内文本的对齐方式
* `uiTransform`：UI 元素的定位属性。
* `uiBackground`：设置 UI 元素的颜色或纹理。
* `变体`：使用此属性将按钮样式设为某个默认样式。 `主要` 和 `次要` 可用。
* `已禁用`：用于将按钮设为禁用状态的布尔值。当 `已禁用` 被设为 *真*时， `onMouseDown` 和 `onMouseUp` 操作将不再被调用，且按钮不再响应任何指针交互。按钮还会以“灰显”状态绘制：文本和背景都会以其 `透明度` 值的一半进行渲染。这仅是显示上的变化。场景传入的 `Color4` 值绝不会被修改，因此你可以安全地在多个元素之间复用共享的调色板对象。

## 按钮样式

将变体设置为 `主要` 或 `次要` ，以利用按钮的默认样式选项。 `主要` 使你的按钮呈红色并带有白色文本， `次要` 使你的按钮呈白色并带有红色文本。

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

export const uiMenu = () => (
	<UiEntity
		uiTransform={{
			width: 500,
			height: 230,
			margin: '16px 0 8px 270px',
			padding: 4,
			alignSelf: 'center',
		}}
		uiBackground={{ color: Color4.Gray() }}
	>
		<Button
			value="点击我"
			variant="primary"
			uiTransform={{ width: 80, height: 20, margin: 4 }}
			onMouseDown={() => {
				console.log('点击了 UI')
			}}
		/>
		<Button
			value="点击我"
			variant="secondary"
			uiTransform={{ width: 80, height: 20, margin: 4 }}
			onMouseDown={() => {
				console.log('点击了 UI')
			}}
		/>
	</UiEntity>
)
```

你也可以自由使用背景的所有属性。还可以设置一个变体，然后覆盖其中的某些属性。此示例使用 `主要` 变体，但将颜色覆盖为绿色：

```tsx
import { Button } from '@dcl/sdk/react-ecs'
import { Color4 } from '@dcl/sdk/math'

export const uiMenu = () => (
	<Button
		value="我的按钮！"
		variant="primary"
		uiTransform={{ width: 100, height: 100 }}
		onMouseDown={() => {
			console.log('点击了我的按钮！')
		}}
		uiBackground={{
			color: Color4.Green(),
		}}
	/>
)
```

## 可切换按钮

一种常见用例是让按钮像开关一样在两种状态之间切换。下面的示例会在每次按下按钮时切换两种颜色：

```tsx
import { Button } from '@dcl/sdk/react-ecs'
import { Color4 } from '@dcl/sdk/math'

let buttonEnabled = false

export const uiMenu = () => (
	<Button
		value="我的按钮"
		variant="primary"
		uiTransform={{ width: 100, height: 100 }}
		onMouseDown={() => {
			console.log('点击了我的按钮！')
			buttonEnabled = !buttonEnabled
			if (buttonEnabled) {
				// 执行某项操作
			} else {
				// 执行其他操作
			}
		}}
		uiBackground={{
			color: buttonEnabled ? Color4.Green() : Color4.Red(),
		}}
	/>
)
```

请注意，在上面的示例中，颜色取决于一个 `buttonEnabled` 变量。每当此变量的值发生变化时，它会立即影响背景颜色。

## 悬停反馈

另一个常见用例是在按钮上悬停时显示某种视觉提示，以表明它可交互，或者显示解释此按钮功能的悬停提示。使用 `onMouseEnter` 和 `onMouseLeave` 回调来检测玩家的光标何时位于按钮上，并作出相应反应。

```tsx
import { Button } from '@dcl/sdk/react-ecs'

let buttonEnabled = false

export const uiMenu = () => (
	<Button
		value="我的按钮"
		uiTransform={{ width: 100, height: 100 }}
		onMouseDown={() => {
			// 按钮函数
		}}
		onMouseEnter={() => {
			// 显示提示
		}}
		onMouseLeave={() => {
			// 隐藏提示
		}}
	/>
)
```

## 使其他元素可点击

UI 中的任何元素都可以通过添加一个 `onMouseDown` 属性而变为可点击，其工作方式与按钮完全相同。以下示例添加 `onMouseDown` 属性到背景图像和文本。

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

export const uiMenu = () => (
	<UiEntity
		onMouseDown={() => {
			console.log('背景被点击！')
		}}
		uiTransform={{
			width: 400,
			height: 230,
		}}
		uiBackground={{ color: Color4.create(0.5, 0.8, 0.1, 0.6) }}
	>
		<Label
			onMouseDown={() => {
				console.log('标签被点击！')
			}}
			value={`玩家：${getPlayerPosition()}`}
			fontSize={18}
			uiTransform={{ width: '100%', height: 30 }}
		/>
	</UiEntity>
)

function getPlayerPosition() {
	const playerPosition = Transform.getOrNull(engine.PlayerEntity)
	if (!playerPosition) return '未知'
	const { x, y, z } = playerPosition.position
	return `{x: ${x.toFixed(2)}, y: ${y.toFixed(2)}, z: ${z.toFixed(2)} }`
}
```

## 指针拦截

默认情况下，所有 UI 实体都不会拦截指针，这意味着玩家的点击会穿过它们，与其后方 3D 世界空间中的对象交互。如果一个实体具有 `onMouseDown` 回调，它就会拦截指针，因此玩家的点击不会影响该 UI 实体后方的内容。

你可以通过更改 `pointerFilter` 属性的值来更改此默认行为，该属性位于任何 UI 实体的 `uiTransform` 组件上。例如，将一个没有 `onMouseDown` 的实体设为拦截指针。

支持的 `pointerFilter` 值为：

* `拦截`：该 UI 元素会拦截指针，玩家无法点击此 UI 元素后方的任何内容。
* `无`：该 UI 元素不会拦截指针。该元素不可点击，并且可以点击其后方的任何内容。

下面是一个简单的 UI，它没有 `onMouseDown`，但通过设置 `pointerFilter` 为 `拦截`.

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

// 绘制 UI
export const uiMenu = () => (
	<UiEntity
		uiTransform={{
			width: '100%',
			height: '100px',
			pointerFilter: `block`,
		}}
		uiText={{ value: `此元素会拦截指针`, fontSize: 40 }}
		uiBackground={{ color: Color4.create(0.5, 0.8, 0.1, 0.6) }}
	/>
)
```

### 拦截遵循布局框，而非可见像素

拦截元素会在其 **整个矩形区域**内捕获点击，无论该区域是否绘制了内容。完全透明的背景也不会改变这一点。

{% hint style="danger" %}
**警告：** 切勿将指针处理程序或 `pointerFilter: 'block'` 放在尺寸为 `100%` 乘以 `100%`.

的全屏包装器上。它的矩形是整个屏幕，因此布局根节点上一个多余的 `onMouseDown` 会使所有其他 UI 元素以及 3D 世界中的所有内容都无法点击。UI 看起来仍然完全正确，因为你能看到的面板只覆盖了屏幕的一小部分，这使得问题非常难以发现。
{% endhint %}

将处理程序附加到需要它们的最小元素上：面板、按钮或行。布局包装器应不带处理程序。

有两种情况下全屏拦截元素是正确的选择，而且两者都是有意为之：

* 一个 **模态背景层**，它用于吞掉点击，并且只在模态框打开时渲染。
* 一个 **拖拽释放捕获器**，它只在拖拽进行时存在。请参阅下方的 [拖拽交互](#drag-interactions) 。

如果场景中的任何地方点击都失效了，这是首先要检查的内容。

## 拖拽交互

UI 指针处理程序（`onMouseDown`, `onMouseUp`, `onMouseEnter`, `onMouseLeave`）不接受任何参数。它们会作为简单的 `() => void` 回调触发，不提供位置或坐标数据。UI 系统中没有 `onMouseDrag` 或 `onMouseMove` 处理程序。

要构建基于拖拽的 UI（滑块、进度条、拖动手柄），请使用 `PrimaryPointerInfo.screenDelta` 来自 `@dcl/sdk/ecs`。它会提供自上一帧以来以像素为单位的鼠标移动量，并且每帧更新，不受光标所在位置影响。

该模式的工作方式如下：

1. `onMouseDown` 在拖拽目标上会开始拖拽并记录初始值。
2. 一个系统会读取 `screenDelta` ，并在拖拽激活时每帧将其累积到该值中。
3. 带有 `pointerFilter: 'block'` 的全屏隐形覆盖层会捕获鼠标释放，因此即使在狭窄目标外松开鼠标，也会结束拖拽。

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

let dragging = false
let sliderValue = 0.5

// 每帧调用此系统以累积拖拽移动量
engine.addSystem(() => {
	if (!dragging) return

	const delta = PrimaryPointerInfo.getOrNull(engine.RootEntity)?.screenDelta
	if (!delta || delta.x === 0) return

	// 将屏幕像素转换为 0–1 范围
	// 除以 UI 缩放系数，使拖拽速度与光标匹配
	const canvas = UiCanvasInformation.getOrNull(engine.RootEntity)
	const scale = canvas ? Math.min(canvas.width / 1920, canvas.height / 1080) : 1
	const trackWidth = 200 // 滑块轨道的虚拟像素宽度

	sliderValue = Math.max(0, Math.min(1, sliderValue + delta.x / scale / trackWidth))
})
```

{% hint style="warning" %}
**注意：** 在移动设备上， `screenDelta` 始终报告为 0（不存在可自由移动的光标）。对于兼容移动设备的滑块，请在拖拽轨道旁添加步进按钮（`-` / `+`）。使用 [`isMobile()`](/creator/content-creator-zh/wei-yi-dong-duan-gou-jian/kai-fa/detect-platform.md) 来自 `@dcl/sdk/platform` 来分支你的 UI。
{% endhint %}

{% hint style="info" %}
**提示：** 始终将 `screenDelta` 除以 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
