Files
Pandona-Engine/guide/essentials/event-handling.md
2025-10-03 16:49:53 +08:00

13 KiB
Raw Permalink Blame History

事件处理

PE引擎提供了简洁而强大的事件处理机制让你能够轻松响应用户的交互操作。事件处理是创建交互式应用的关键部分。

事件绑定语法

在PE的场景模板中你可以使用@前缀直接绑定事件处理器:

<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事件类型

鼠标事件

<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>

键盘事件

<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>

表单事件

<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>

触摸事件

<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>

事件对象

所有事件处理器都会接收到一个事件对象,包含有关事件的详细信息:

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代码中直接绑定事件

// 获取场景元素
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的语法

<sence>
  <!-- 阻止默认行为 -->
  <box @click.prevent="handleClick">阻止默认行为</box>
  
  <!-- 阻止事件冒泡 -->
  <box @click.stop="handleClick">阻止事件冒泡</box>
  
  <!-- 只触发一次 -->
  <box @click.once="handleClick">只触发一次</box>
  
  <!-- 组合修饰符 -->
  <box @click.stop.prevent="handleClick">阻止冒泡和默认行为</box>
</sence>

自定义事件

你也可以创建和触发自定义事件:

// 创建自定义事件
const customEvent = new CustomEvent('myCustomEvent', {
  detail: { message: '这是自定义事件数据' }
})

// 触发自定义事件
element.element.dispatchEvent(customEvent)

// 监听自定义事件
element.element.addEventListener('myCustomEvent', function(event) {
  console.log('接收到自定义事件:', event.detail.message)
})

事件总线

PE引擎内置了事件总线允许你在不同组件间进行通信

// 发送事件
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>部分,你可以定义事件处理函数:

<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. 合理使用事件委托

对于大量相似元素,使用事件委托可以提高性能:

<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. 及时移除事件监听器

在适当的时机移除事件监听器,避免内存泄漏:

onLoad(() => {
  // 添加事件监听器
  document.addEventListener('keydown', handleGlobalKeyDown)
})

onDestory(() => {
  // 移除事件监听器
  document.removeEventListener('keydown', handleGlobalKeyDown)
})

function handleGlobalKeyDown(event) {
  // 全局键盘事件处理
  if (event.key === 'Escape') {
    PE.navigateTo('/')
  }
}

3. 使用防抖和节流

对于频繁触发的事件(如滚动、输入),使用防抖和节流优化性能:

// 防抖函数
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

<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引擎的事件处理机制。在下一章节中我们将探讨生命周期的相关内容。