在掌握了Canvas的基础知识和动画技术后,我们可以将这些技能应用到实际项目中。本章将介绍几个典型的高级应用场景,帮助你将Canvas技术转化为实际生产力。
Canvas在现代Web开发中有着广泛的应用场景:
| 应用领域 | 典型场景 | 核心技术 |
|---|---|---|
| 游戏开发 | 2D游戏、休闲游戏、教育游戏 | 动画循环、碰撞检测、事件处理 |
| 图表绑制 | 数据报表、仪表盘、实时监控 | 数据绑定、动画过渡、交互响应 |
| 绘图板 | 在线绘图、签名板、白板协作 | 路径绑制、撤销重做、图像导出 |
| 图片编辑 | 滤镜处理、裁剪旋转、标注工具 | 像素操作、变换、合成 |
| 数据可视化 | 地图可视化、关系图、科学可视化 | 大量图形、性能优化、交互 |
| 视频处理 | 视频特效、实时滤镜、视频编辑 | 视频帧处理、WebGL加速 |
开发复杂Canvas应用需要良好的架构设计:
class CanvasApplication {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId)
this.ctx = this.canvas.getContext('2d')
this.state = this.getInitialState()
this.entities = []
this.isRunning = false
this.init()
}
getInitialState() {
return {
width: 800,
height: 600,
scale: 1,
offsetX: 0,
offsetY: 0
}
}
init() {
this.setupCanvas()
this.bindEvents()
this.loadResources().then(() => {
this.start()
})
}
setupCanvas() {
this.canvas.width = this.state.width
this.canvas.height = this.state.height
}
bindEvents() {
window.addEventListener('resize', this.onResize.bind(this))
this.canvas.addEventListener('mousedown', this.onMouseDown.bind(this))
this.canvas.addEventListener('mousemove', this.onMouseMove.bind(this))
this.canvas.addEventListener('mouseup', this.onMouseUp.bind(this))
}
async loadResources() {
// 加载图片、音频等资源
}
start() {
this.isRunning = true
this.loop()
}
stop() {
this.isRunning = false
}
loop() {
if (!this.isRunning) return
this.update()
this.render()
requestAnimationFrame(() => this.loop())
}
update() {
// 更新所有实体状态
this.entities.forEach(entity => entity.update())
}
render() {
// 清除画布
this.ctx.clearRect(0, 0, this.state.width, this.state.height)
// 渲染所有实体
this.entities.forEach(entity => entity.render(this.ctx))
}
onResize() {
// 处理窗口大小变化
}
onMouseDown(e) {
// 处理鼠标按下
}
onMouseMove(e) {
// 处理鼠标移动
}
onMouseUp(e) {
// 处理鼠标释放
}
}
使用实体系统管理游戏对象:
class Entity {
constructor(options = {}) {
this.id = Entity.nextId++
this.x = options.x || 0
this.y = options.y || 0
this.width = options.width || 0
this.height = options.height || 0
this.rotation = options.rotation || 0
this.scale = options.scale || 1
this.visible = options.visible !== false
this.active = options.active !== false
}
update(deltaTime) {
// 子类实现
}
render(ctx) {
if (!this.visible) return
ctx.save()
ctx.translate(this.x, this.y)
ctx.rotate(this.rotation)
ctx.scale(this.scale, this.scale)
this.draw(ctx)
ctx.restore()
}
draw(ctx) {
// 子类实现具体绑制逻辑
}
containsPoint(px, py) {
return px >= this.x - this.width / 2 &&
px <= this.x + this.width / 2 &&
py >= this.y - this.height / 2 &&
py <= this.y + this.height / 2
}
}
Entity.nextId = 1
class SceneManager {
constructor() {
this.scenes = new Map()
this.currentScene = null
}
add(name, scene) {
this.scenes.set(name, scene)
}
switchTo(name) {
if (this.currentScene) {
this.currentScene.exit()
}
this.currentScene = this.scenes.get(name)
if (this.currentScene) {
this.currentScene.enter()
}
}
update(deltaTime) {
if (this.currentScene) {
this.currentScene.update(deltaTime)
}
}
render(ctx) {
if (this.currentScene) {
this.currentScene.render(ctx)
}
}
}
class Scene {
constructor(name) {
this.name = name
this.entities = []
}
enter() {
console.log(`进入场景: ${this.name}`)
}
exit() {
console.log(`退出场景: ${this.name}`)
}
addEntity(entity) {
this.entities.push(entity)
}
removeEntity(entity) {
const index = this.entities.indexOf(entity)
if (index > -1) {
this.entities.splice(index, 1)
}
}
update(deltaTime) {
this.entities.forEach(entity => entity.update(deltaTime))
}
render(ctx) {
this.entities.forEach(entity => entity.render(ctx))
}
}
class ResourceManager {
constructor() {
this.images = new Map()
this.sounds = new Map()
this.loaded = false
}
loadImage(key, src) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
this.images.set(key, img)
resolve(img)
}
img.onerror = reject
img.src = src
})
}
loadSound(key, src) {
return new Promise((resolve, reject) => {
const audio = new Audio()
audio.oncanplaythrough = () => {
this.sounds.set(key, audio)
resolve(audio)
}
audio.onerror = reject
audio.src = src
})
}
async loadAll(resources) {
const promises = []
for (const [key, config] of Object.entries(resources.images || {})) {
promises.push(this.loadImage(key, config.src))
}
for (const [key, config] of Object.entries(resources.sounds || {})) {
promises.push(this.loadSound(key, config.src))
}
await Promise.all(promises)
this.loaded = true
}
getImage(key) {
return this.images.get(key)
}
getSound(key) {
return this.sounds.get(key)
}
}
class StateManager {
constructor(initialState = {}) {
this.state = initialState
this.listeners = new Map()
this.history = []
this.maxHistory = 50
}
getState() {
return { ...this.state }
}
setState(updates, saveHistory = true) {
if (saveHistory) {
this.history.push({ ...this.state })
if (this.history.length > this.maxHistory) {
this.history.shift()
}
}
const oldState = this.state
this.state = { ...this.state, ...updates }
this.notify(oldState, this.state)
}
subscribe(key, callback) {
if (!this.listeners.has(key)) {
this.listeners.set(key, [])
}
this.listeners.get(key).push(callback)
}
unsubscribe(key, callback) {
const callbacks = this.listeners.get(key)
if (callbacks) {
const index = callbacks.indexOf(callback)
if (index > -1) {
callbacks.splice(index, 1)
}
}
}
notify(oldState, newState) {
for (const [key, callbacks] of this.listeners) {
if (oldState[key] !== newState[key]) {
callbacks.forEach(cb => cb(newState[key], oldState[key]))
}
}
}
undo() {
if (this.history.length > 0) {
this.state = this.history.pop()
return true
}
return false
}
}
开发高级Canvas应用时,性能是关键考虑因素:
本章将详细介绍以下高级应用: