You've already forked Pandona-Engine
605 lines
13 KiB
Markdown
605 lines
13 KiB
Markdown
# 事件处理
|
||
|
||
PE引擎提供了简洁而强大的事件处理机制,让你能够轻松响应用户的交互操作。事件处理是创建交互式应用的关键部分。
|
||
|
||
## 事件绑定语法
|
||
|
||
在PE的场景模板中,你可以使用`@`前缀直接绑定事件处理器:
|
||
|
||
```html
|
||
<sence>
|
||
<!-- 基本事件绑定 -->
|
||
<box @click="handleClick">点击我</box>
|
||
|
||
<!-- 多个事件绑定 -->
|
||
<box @click="handleClick" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
|
||
悬停我
|
||
</box>
|
||
|
||
<!-- 传递参数 -->
|
||
<box @click="handleClickWithParam('button1')">按钮1</box>
|
||
<box @click="handleClickWithParam('button2')">按钮2</box>
|
||
</sence>
|
||
|
||
<script>
|
||
function handleClick() {
|
||
console.log('元素被点击了')
|
||
}
|
||
|
||
function handleMouseEnter() {
|
||
console.log('鼠标进入元素')
|
||
}
|
||
|
||
function handleMouseLeave() {
|
||
console.log('鼠标离开元素')
|
||
}
|
||
|
||
function handleClickWithParam(buttonId) {
|
||
console.log('点击了按钮:', buttonId)
|
||
}
|
||
</script>
|
||
```
|
||
|
||
## 支持的事件类型
|
||
|
||
PE支持所有标准的DOM事件类型:
|
||
|
||
### 鼠标事件
|
||
|
||
```html
|
||
<sence>
|
||
<box @click="handleClick"
|
||
@dblclick="handleDoubleClick"
|
||
@mouseenter="handleMouseEnter"
|
||
@mouseleave="handleMouseLeave"
|
||
@mousedown="handleMouseDown"
|
||
@mouseup="handleMouseUp"
|
||
@contextmenu="handleContextMenu">
|
||
鼠标事件示例
|
||
</box>
|
||
</sence>
|
||
|
||
<script>
|
||
function handleClick(event) {
|
||
console.log('点击事件:', event)
|
||
}
|
||
|
||
function handleDoubleClick(event) {
|
||
console.log('双击事件:', event)
|
||
}
|
||
|
||
function handleMouseEnter(event) {
|
||
console.log('鼠标进入:', event)
|
||
}
|
||
|
||
function handleMouseLeave(event) {
|
||
console.log('鼠标离开:', event)
|
||
}
|
||
|
||
function handleMouseDown(event) {
|
||
console.log('鼠标按下:', event)
|
||
}
|
||
|
||
function handleMouseUp(event) {
|
||
console.log('鼠标释放:', event)
|
||
}
|
||
|
||
function handleContextMenu(event) {
|
||
event.preventDefault() // 阻止右键菜单
|
||
console.log('右键点击:', event)
|
||
}
|
||
</script>
|
||
```
|
||
|
||
### 键盘事件
|
||
|
||
```html
|
||
<sence>
|
||
<text @keydown="handleKeyDown"
|
||
@keyup="handleKeyUp"
|
||
@keypress="handleKeyPress"
|
||
tabindex="0">
|
||
按键事件示例(点击后按键盘)
|
||
</text>
|
||
</sence>
|
||
|
||
<script>
|
||
function handleKeyDown(event) {
|
||
console.log('按键按下:', event.key, event.code)
|
||
}
|
||
|
||
function handleKeyUp(event) {
|
||
console.log('按键释放:', event.key, event.code)
|
||
}
|
||
|
||
function handleKeyPress(event) {
|
||
console.log('按键按压:', event.key)
|
||
}
|
||
</script>
|
||
```
|
||
|
||
### 表单事件
|
||
|
||
```html
|
||
<sence>
|
||
<html @input="handleInput"
|
||
@change="handleChange"
|
||
@focus="handleFocus"
|
||
@blur="handleBlur"
|
||
html="<input type='text' placeholder='输入内容...' />">
|
||
</html>
|
||
</sence>
|
||
|
||
<script>
|
||
function handleInput(event) {
|
||
console.log('输入事件:', event.target.value)
|
||
}
|
||
|
||
function handleChange(event) {
|
||
console.log('值改变事件:', event.target.value)
|
||
}
|
||
|
||
function handleFocus(event) {
|
||
console.log('获得焦点')
|
||
}
|
||
|
||
function handleBlur(event) {
|
||
console.log('失去焦点')
|
||
}
|
||
</script>
|
||
```
|
||
|
||
### 触摸事件
|
||
|
||
```html
|
||
<sence>
|
||
<box @touchstart="handleTouchStart"
|
||
@touchmove="handleTouchMove"
|
||
@touchend="handleTouchEnd"
|
||
@touchcancel="handleTouchCancel">
|
||
触摸事件示例
|
||
</box>
|
||
</sence>
|
||
|
||
<script>
|
||
function handleTouchStart(event) {
|
||
console.log('触摸开始:', event.touches.length, '个触摸点')
|
||
}
|
||
|
||
function handleTouchMove(event) {
|
||
console.log('触摸移动:', event.touches[0].clientX, event.touches[0].clientY)
|
||
}
|
||
|
||
function handleTouchEnd(event) {
|
||
console.log('触摸结束')
|
||
}
|
||
|
||
function handleTouchCancel(event) {
|
||
console.log('触摸取消')
|
||
}
|
||
</script>
|
||
```
|
||
|
||
## 事件对象
|
||
|
||
所有事件处理器都会接收到一个事件对象,包含有关事件的详细信息:
|
||
|
||
```javascript
|
||
function handleClick(event) {
|
||
// 事件基本信息
|
||
console.log('事件类型:', event.type)
|
||
console.log('目标元素:', event.target)
|
||
console.log('当前元素:', event.currentTarget)
|
||
|
||
// 鼠标位置
|
||
console.log('页面坐标:', event.pageX, event.pageY)
|
||
console.log('客户端坐标:', event.clientX, event.clientY)
|
||
|
||
// 键盘信息
|
||
console.log('是否按下Ctrl键:', event.ctrlKey)
|
||
console.log('是否按下Alt键:', event.altKey)
|
||
console.log('是否按下Shift键:', event.shiftKey)
|
||
|
||
// 阻止默认行为
|
||
event.preventDefault()
|
||
|
||
// 阻止事件冒泡
|
||
event.stopPropagation()
|
||
}
|
||
```
|
||
|
||
## 在JavaScript中绑定事件
|
||
|
||
除了在模板中绑定事件,你也可以在JavaScript代码中直接绑定事件:
|
||
|
||
```javascript
|
||
// 获取场景元素
|
||
const element = game.getSceneElement('my-button')
|
||
|
||
// 添加事件监听器
|
||
element.element.addEventListener('click', function(event) {
|
||
console.log('元素被点击了')
|
||
})
|
||
|
||
// 添加多个事件监听器
|
||
element.element.addEventListener('mouseenter', handleMouseEnter)
|
||
element.element.addEventListener('mouseleave', handleMouseLeave)
|
||
|
||
// 移除事件监听器
|
||
element.element.removeEventListener('click', handleClick)
|
||
```
|
||
|
||
## 事件修饰符
|
||
|
||
PE支持一些常用的事件修饰符,类似于Vue.js的语法:
|
||
|
||
```html
|
||
<sence>
|
||
<!-- 阻止默认行为 -->
|
||
<box @click.prevent="handleClick">阻止默认行为</box>
|
||
|
||
<!-- 阻止事件冒泡 -->
|
||
<box @click.stop="handleClick">阻止事件冒泡</box>
|
||
|
||
<!-- 只触发一次 -->
|
||
<box @click.once="handleClick">只触发一次</box>
|
||
|
||
<!-- 组合修饰符 -->
|
||
<box @click.stop.prevent="handleClick">阻止冒泡和默认行为</box>
|
||
</sence>
|
||
```
|
||
|
||
## 自定义事件
|
||
|
||
你也可以创建和触发自定义事件:
|
||
|
||
```javascript
|
||
// 创建自定义事件
|
||
const customEvent = new CustomEvent('myCustomEvent', {
|
||
detail: { message: '这是自定义事件数据' }
|
||
})
|
||
|
||
// 触发自定义事件
|
||
element.element.dispatchEvent(customEvent)
|
||
|
||
// 监听自定义事件
|
||
element.element.addEventListener('myCustomEvent', function(event) {
|
||
console.log('接收到自定义事件:', event.detail.message)
|
||
})
|
||
```
|
||
|
||
## 事件总线
|
||
|
||
PE引擎内置了事件总线,允许你在不同组件间进行通信:
|
||
|
||
```javascript
|
||
// 发送事件
|
||
game.eventBus.emit('user-login', { username: 'john' })
|
||
|
||
// 监听事件
|
||
game.eventBus.on('user-login', function(data) {
|
||
console.log('用户登录:', data.username)
|
||
})
|
||
|
||
// 移除事件监听器
|
||
game.eventBus.off('user-login', handlerFunction)
|
||
```
|
||
|
||
## 在场景脚本中处理事件
|
||
|
||
在场景的`<script>`部分,你可以定义事件处理函数:
|
||
|
||
```html
|
||
<sence>
|
||
<box class="counter-button" @click="incrementCounter">计数器: {{ counter }}</box>
|
||
<box class="reset-button" @click="resetCounter">重置</box>
|
||
</sence>
|
||
|
||
<script>
|
||
// 数据定义
|
||
let counter = 0
|
||
|
||
// 事件处理函数
|
||
function incrementCounter() {
|
||
counter++
|
||
console.log('计数器:', counter)
|
||
|
||
// 更新UI
|
||
const button = game.getSceneElement('counter-button')
|
||
button.element.textContent = `计数器: ${counter}`
|
||
}
|
||
|
||
function resetCounter() {
|
||
counter = 0
|
||
console.log('计数器已重置')
|
||
|
||
// 更新UI
|
||
const button = game.getSceneElement('counter-button')
|
||
button.element.textContent = `计数器: ${counter}`
|
||
}
|
||
|
||
// 生命周期钩子中绑定事件
|
||
onLoad(() => {
|
||
console.log('场景加载完成,计数器初始化为', counter)
|
||
})
|
||
|
||
onShow(() => {
|
||
console.log('场景显示,当前计数器值为', counter)
|
||
})
|
||
</script>
|
||
```
|
||
|
||
## 事件处理最佳实践
|
||
|
||
### 1. 合理使用事件委托
|
||
|
||
对于大量相似元素,使用事件委托可以提高性能:
|
||
|
||
```html
|
||
<sence>
|
||
<!-- 使用事件委托处理列表项点击 -->
|
||
<box class="list-container" @click="handleListItemClick">
|
||
<box class="list-item" data-id="1">项目1</box>
|
||
<box class="list-item" data-id="2">项目2</box>
|
||
<box class="list-item" data-id="3">项目3</box>
|
||
</box>
|
||
</sence>
|
||
|
||
<script>
|
||
function handleListItemClick(event) {
|
||
// 检查点击的是否是列表项
|
||
if (event.target.classList.contains('list-item')) {
|
||
const id = event.target.getAttribute('data-id')
|
||
console.log('点击了项目:', id)
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
### 2. 及时移除事件监听器
|
||
|
||
在适当的时机移除事件监听器,避免内存泄漏:
|
||
|
||
```javascript
|
||
onLoad(() => {
|
||
// 添加事件监听器
|
||
document.addEventListener('keydown', handleGlobalKeyDown)
|
||
})
|
||
|
||
onDestory(() => {
|
||
// 移除事件监听器
|
||
document.removeEventListener('keydown', handleGlobalKeyDown)
|
||
})
|
||
|
||
function handleGlobalKeyDown(event) {
|
||
// 全局键盘事件处理
|
||
if (event.key === 'Escape') {
|
||
PE.navigateTo('/')
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 使用防抖和节流
|
||
|
||
对于频繁触发的事件(如滚动、输入),使用防抖和节流优化性能:
|
||
|
||
```javascript
|
||
// 防抖函数
|
||
function debounce(func, wait) {
|
||
let timeout
|
||
return function(...args) {
|
||
clearTimeout(timeout)
|
||
timeout = setTimeout(() => func.apply(this, args), wait)
|
||
}
|
||
}
|
||
|
||
// 节流函数
|
||
function throttle(func, limit) {
|
||
let inThrottle
|
||
return function(...args) {
|
||
if (!inThrottle) {
|
||
func.apply(this, args)
|
||
inThrottle = true
|
||
setTimeout(() => inThrottle = false, limit)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 使用防抖处理输入事件
|
||
const handleInput = debounce(function(event) {
|
||
console.log('输入内容:', event.target.value)
|
||
}, 300)
|
||
|
||
// 使用节流处理滚动事件
|
||
const handleScroll = throttle(function(event) {
|
||
console.log('页面滚动')
|
||
}, 100)
|
||
```
|
||
|
||
## 完整示例
|
||
|
||
以下是一个完整的事件处理示例:
|
||
|
||
### scenes/game/index.pe
|
||
|
||
```html
|
||
<sence>
|
||
<box class="game-container">
|
||
<!-- 玩家角色 -->
|
||
<sprite class="player"
|
||
@mousedown="startDrag"
|
||
@mouseup="stopDrag"
|
||
@touchstart="startDrag"
|
||
@touchend="stopDrag">
|
||
</sprite>
|
||
|
||
<!-- 敌人 -->
|
||
<sprite for="{enemy} in enemies"
|
||
class="enemy"
|
||
:class="{ 'enemy-active': enemy.active }"
|
||
@click="hitEnemy(enemy.id)">
|
||
</sprite>
|
||
|
||
<!-- 控制按钮 -->
|
||
<box class="control-panel">
|
||
<box class="btn btn-primary" @click="startGame">开始游戏</box>
|
||
<box class="btn btn-secondary" @click="pauseGame">暂停</box>
|
||
<box class="btn btn-danger" @click="resetGame">重置</box>
|
||
</box>
|
||
|
||
<!-- 游戏信息 -->
|
||
<text class="score">得分: {{ score }}</text>
|
||
<text class="lives">生命: {{ lives }}</text>
|
||
</box>
|
||
</sence>
|
||
|
||
<script>
|
||
// 游戏状态
|
||
let score = 0
|
||
let lives = 3
|
||
let gameRunning = false
|
||
let dragging = false
|
||
|
||
// 敌人数据
|
||
const enemies = [
|
||
{ id: 1, active: true },
|
||
{ id: 2, active: true },
|
||
{ id: 3, active: true }
|
||
]
|
||
|
||
// 事件处理函数
|
||
function startDrag(event) {
|
||
if (!gameRunning) return
|
||
dragging = true
|
||
console.log('开始拖拽玩家')
|
||
|
||
// 添加鼠标移动事件监听器
|
||
document.addEventListener('mousemove', handleDrag)
|
||
document.addEventListener('touchmove', handleDrag)
|
||
}
|
||
|
||
function stopDrag(event) {
|
||
dragging = false
|
||
console.log('停止拖拽玩家')
|
||
|
||
// 移除鼠标移动事件监听器
|
||
document.removeEventListener('mousemove', handleDrag)
|
||
document.removeEventListener('touchmove', handleDrag)
|
||
}
|
||
|
||
function handleDrag(event) {
|
||
if (!dragging || !gameRunning) return
|
||
|
||
const player = game.getSceneElement('player')
|
||
if (player) {
|
||
// 获取鼠标或触摸位置
|
||
let x, y
|
||
if (event.type === 'touchmove') {
|
||
x = event.touches[0].clientX
|
||
y = event.touches[0].clientY
|
||
} else {
|
||
x = event.clientX
|
||
y = event.clientY
|
||
}
|
||
|
||
// 更新玩家位置
|
||
player.setPosition(x - 25, y - 25) // 考虑精灵尺寸
|
||
}
|
||
}
|
||
|
||
function hitEnemy(enemyId) {
|
||
if (!gameRunning) return
|
||
|
||
// 找到被击中的敌人
|
||
const enemy = enemies.find(e => e.id === enemyId)
|
||
if (enemy && enemy.active) {
|
||
// 标记敌人为非活跃状态
|
||
enemy.active = false
|
||
|
||
// 增加得分
|
||
score += 10
|
||
updateScore()
|
||
|
||
console.log('击中敌人:', enemyId)
|
||
}
|
||
}
|
||
|
||
function startGame() {
|
||
gameRunning = true
|
||
score = 0
|
||
lives = 3
|
||
updateScore()
|
||
updateLives()
|
||
|
||
// 重置敌人状态
|
||
enemies.forEach(enemy => {
|
||
enemy.active = true
|
||
})
|
||
|
||
console.log('游戏开始')
|
||
}
|
||
|
||
function pauseGame() {
|
||
gameRunning = !gameRunning
|
||
console.log(gameRunning ? '游戏继续' : '游戏暂停')
|
||
}
|
||
|
||
function resetGame() {
|
||
gameRunning = false
|
||
score = 0
|
||
lives = 3
|
||
updateScore()
|
||
updateLives()
|
||
|
||
// 重置敌人状态
|
||
enemies.forEach(enemy => {
|
||
enemy.active = true
|
||
})
|
||
|
||
console.log('游戏重置')
|
||
}
|
||
|
||
// 更新得分显示
|
||
function updateScore() {
|
||
const scoreElement = game.getSceneElement('score')
|
||
if (scoreElement) {
|
||
scoreElement.element.textContent = `得分: ${score}`
|
||
}
|
||
}
|
||
|
||
// 更新生命显示
|
||
function updateLives() {
|
||
const livesElement = game.getSceneElement('lives')
|
||
if (livesElement) {
|
||
livesElement.element.textContent = `生命: ${lives}`
|
||
}
|
||
}
|
||
|
||
// 生命周期钩子
|
||
onLoad(() => {
|
||
console.log('游戏场景加载完成')
|
||
})
|
||
|
||
onShow(() => {
|
||
console.log('游戏场景显示')
|
||
// 初始化游戏状态
|
||
updateScore()
|
||
updateLives()
|
||
})
|
||
|
||
onHide(() => {
|
||
console.log('游戏场景隐藏')
|
||
// 暂停游戏
|
||
gameRunning = false
|
||
})
|
||
|
||
onDestory(() => {
|
||
console.log('游戏场景销毁')
|
||
// 清理事件监听器
|
||
document.removeEventListener('mousemove', handleDrag)
|
||
document.removeEventListener('touchmove', handleDrag)
|
||
})
|
||
</script>
|
||
```
|
||
|
||
通过以上内容,你已经了解了PE引擎的事件处理机制。在下一章节中,我们将探讨生命周期的相关内容。 |