学习Canvas触摸事件处理,掌握触摸事件、手势识别和移动端交互开发方法。触摸事件是移动端交互的核心,正确处理触摸事件可以创建流畅的移动端体验。
| 事件 | 说明 | 触发时机 |
|---|---|---|
| touchstart | 触摸开始 | 手指触摸屏幕 |
| touchmove | 触摸移动 | 手指在屏幕上移动 |
| touchend | 触摸结束 | 手指离开屏幕 |
| touchcancel | 触摸取消 | 触摸被中断(如来电) |
const canvas = document.getElementById('myCanvas')
canvas.addEventListener('touchstart', (e) => {
e.preventDefault()
console.log('触摸开始')
})
canvas.addEventListener('touchmove', (e) => {
e.preventDefault()
console.log('触摸移动')
})
canvas.addEventListener('touchend', (e) => {
console.log('触摸结束')
})
canvas.addEventListener('touchcancel', (e) => {
console.log('触摸取消')
})
canvas.addEventListener('touchstart', (e) => {
const touch = e.touches[0]
console.log('触摸ID:', touch.identifier)
console.log('相对于视口:', touch.clientX, touch.clientY)
console.log('相对于页面:', touch.pageX, touch.pageY)
console.log('相对于屏幕:', touch.screenX, touch.screenY)
})
canvas.addEventListener('touchstart', (e) => {
console.log('当前触摸点数量:', e.touches.length)
console.log('当前元素上的触摸点:', e.targetTouches.length)
console.log('本次触摸变化的点:', e.changedTouches.length)
})
function getTouchPos(canvas, e) {
const rect = canvas.getBoundingClientRect()
const touch = e.touches[0] || e.changedTouches[0]
return {
x: touch.clientX - rect.left,
y: touch.clientY - rect.top
}
}
canvas.addEventListener('touchstart', (e) => {
e.preventDefault()
const pos = getTouchPos(canvas, e)
console.log('触摸位置:', pos.x, pos.y)
})
const activeTouches = new Map()
canvas.addEventListener('touchstart', (e) => {
e.preventDefault()
for (const touch of e.changedTouches) {
activeTouches.set(touch.identifier, {
x: touch.clientX,
y: touch.clientY,
startX: touch.clientX,
startY: touch.clientY
})
}
console.log('活跃触摸点:', activeTouches.size)
})
canvas.addEventListener('touchmove', (e) => {
e.preventDefault()
for (const touch of e.changedTouches) {
const data = activeTouches.get(touch.identifier)
if (data) {
data.x = touch.clientX
data.y = touch.clientY
}
}
})
canvas.addEventListener('touchend', (e) => {
for (const touch of e.changedTouches) {
activeTouches.delete(touch.identifier)
}
})
let lastDistance = 0
let scale = 1
canvas.addEventListener('touchmove', (e) => {
e.preventDefault()
if (e.touches.length === 2) {
const dx = e.touches[0].clientX - e.touches[1].clientX
const dy = e.touches[0].clientY - e.touches[1].clientY
const distance = Math.sqrt(dx * dx + dy * dy)
if (lastDistance > 0) {
const delta = distance / lastDistance
scale *= delta
console.log('缩放:', scale)
}
lastDistance = distance
}
})
canvas.addEventListener('touchend', (e) => {
if (e.touches.length < 2) {
lastDistance = 0
}
})
let lastAngle = 0
let rotation = 0
canvas.addEventListener('touchmove', (e) => {
e.preventDefault()
if (e.touches.length === 2) {
const dx = e.touches[1].clientX - e.touches[0].clientX
const dy = e.touches[1].clientY - e.touches[0].clientY
const angle = Math.atan2(dy, dx)
if (lastAngle !== 0) {
const delta = angle - lastAngle
rotation += delta
console.log('旋转角度:', rotation * 180 / Math.PI)
}
lastAngle = angle
}
})
canvas.addEventListener('touchend', (e) => {
if (e.touches.length < 2) {
lastAngle = 0
}
})
class TapRecognizer {
constructor(options = {}) {
this.maxDuration = options.maxDuration || 300
this.maxDistance = options.maxDistance || 10
this.tapCount = options.tapCount || 1
this.taps = []
}
recognize(e) {
const touch = e.changedTouches[0]
if (e.type === 'touchstart') {
this.startTime = Date.now()
this.startX = touch.clientX
this.startY = touch.clientY
}
if (e.type === 'touchend') {
const duration = Date.now() - this.startTime
const dx = touch.clientX - this.startX
const dy = touch.clientY - this.startY
const distance = Math.sqrt(dx * dx + dy * dy)
if (duration < this.maxDuration && distance < this.maxDistance) {
this.taps.push(Date.now())
if (this.taps.length >= this.tapCount) {
const timeDiff = this.taps[this.taps.length - 1] - this.taps[0]
if (timeDiff < this.maxDuration * this.tapCount) {
return true
}
this.taps = [this.taps[this.taps.length - 1]]
}
}
}
return false
}
}
const singleTap = new TapRecognizer({ tapCount: 1 })
const doubleTap = new TapRecognizer({ tapCount: 2 })
canvas.addEventListener('touchend', (e) => {
if (singleTap.recognize(e)) {
console.log('单击')
}
if (doubleTap.recognize(e)) {
console.log('双击')
}
})
class SwipeRecognizer {
constructor(options = {}) {
this.minDistance = options.minDistance || 50
this.maxDuration = options.maxDuration || 500
this.direction = options.direction || 'all'
}
recognize(e) {
const touch = e.changedTouches[0]
if (e.type === 'touchstart') {
this.startTime = Date.now()
this.startX = touch.clientX
this.startY = touch.clientY
return null
}
if (e.type === 'touchend') {
const duration = Date.now() - this.startTime
if (duration > this.maxDuration) return null
const dx = touch.clientX - this.startX
const dy = touch.clientY - this.startY
const distance = Math.sqrt(dx * dx + dy * dy)
if (distance < this.minDistance) return null
const angle = Math.atan2(dy, dx) * 180 / Math.PI
let direction
if (angle >= -45 && angle < 45) direction = 'right'
else if (angle >= 45 && angle < 135) direction = 'down'
else if (angle >= -135 && angle < -45) direction = 'up'
else direction = 'left'
return { direction, distance, dx, dy }
}
return null
}
}
const swiper = new SwipeRecognizer()
canvas.addEventListener('touchstart', (e) => swiper.recognize(e))
canvas.addEventListener('touchend', (e) => {
const result = swiper.recognize(e)
if (result) {
console.log('滑动方向:', result.direction)
}
})
class LongPressRecognizer {
constructor(options = {}) {
this.minDuration = options.minDuration || 500
this.maxDistance = options.maxDistance || 10
this.timer = null
this.recognized = false
}
start(e, callback) {
const touch = e.touches[0]
this.startX = touch.clientX
this.startY = touch.clientY
this.recognized = false
this.timer = setTimeout(() => {
this.recognized = true
callback()
}, this.minDuration)
}
move(e) {
const touch = e.touches[0]
const dx = touch.clientX - this.startX
const dy = touch.clientY - this.startY
const distance = Math.sqrt(dx * dx + dy * dy)
if (distance > this.maxDistance) {
this.cancel()
}
}
cancel() {
clearTimeout(this.timer)
this.timer = null
}
}
const longPress = new LongPressRecognizer()
canvas.addEventListener('touchstart', (e) => {
longPress.start(e, () => {
console.log('长按触发')
})
})
canvas.addEventListener('touchmove', (e) => {
longPress.move(e)
})
canvas.addEventListener('touchend', () => {
longPress.cancel()
})
class UnifiedPointer {
constructor(canvas) {
this.canvas = canvas
this.handlers = {
start: [],
move: [],
end: []
}
this.bindEvents()
}
bindEvents() {
this.canvas.addEventListener('mousedown', this.onMouseDown.bind(this))
this.canvas.addEventListener('mousemove', this.onMouseMove.bind(this))
this.canvas.addEventListener('mouseup', this.onMouseUp.bind(this))
this.canvas.addEventListener('touchstart', this.onTouchStart.bind(this))
this.canvas.addEventListener('touchmove', this.onTouchMove.bind(this))
this.canvas.addEventListener('touchend', this.onTouchEnd.bind(this))
}
getPos(e) {
const rect = this.canvas.getBoundingClientRect()
if (e.touches) {
return {
x: e.touches[0].clientX - rect.left,
y: e.touches[0].clientY - rect.top
}
}
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
}
}
onMouseDown(e) {
this.trigger('start', this.getPos(e), e)
}
onMouseMove(e) {
this.trigger('move', this.getPos(e), e)
}
onMouseUp(e) {
this.trigger('end', this.getPos(e), e)
}
onTouchStart(e) {
e.preventDefault()
this.trigger('start', this.getPos(e), e)
}
onTouchMove(e) {
e.preventDefault()
this.trigger('move', this.getPos(e), e)
}
onTouchEnd(e) {
this.trigger('end', null, e)
}
on(type, handler) {
if (this.handlers[type]) {
this.handlers[type].push(handler)
}
}
trigger(type, pos, e) {
this.handlers[type].forEach(handler => handler(pos, e))
}
}
const pointer = new UnifiedPointer(canvas)
pointer.on('start', (pos) => {
console.log('开始:', pos)
})
pointer.on('move', (pos) => {
console.log('移动:', pos)
})
pointer.on('end', () => {
console.log('结束')
})
canvas.addEventListener('touchstart', handler, { passive: true })
canvas.addEventListener('touchmove', handler, { passive: true })
let needsRedraw = false
canvas.addEventListener('touchmove', (e) => {
e.preventDefault()
updateState(e)
needsRedraw = true
})
function animationLoop() {
if (needsRedraw) {
redraw()
needsRedraw = false
}
requestAnimationFrame(animationLoop)
}
animationLoop()