# Vue3组合式API实战:从零构建一个实时协作在线白板
## 引言
在线白板工具已经成为远程协作和在线教育的重要工具。本文将带领大家使用Vue3的组合式API,从零开始构建一个功能完整的实时协作在线白板。通过这个过程,你将深入了解Vue3组合式API的核心概念,以及如何将其应用于实际项目中。我们将涵盖画布绘制、实时同步、用户状态管理等关键功能,让你在实战中掌握Vue3的魅力。
## 准备工作
在开始之前,确保你已经安装了Node.js和Vue CLI。创建一个新的Vue3项目:
“`bash
npm create vue@latest whiteboard-app
cd whiteboard-app
npm install
“`
安装必要的依赖:
“`bash
npm install socket.io-client
“`
## 项目结构
我们的项目将包含以下核心组件:
– `Whiteboard.vue` – 主画板组件
– `Toolbar.vue` – 工具栏组件
– `UserList.vue` – 在线用户列表
– `App.vue` – 根组件
## 实现画板核心功能
### 1. 画布初始化
首先创建`Whiteboard.vue`组件:
“`vue
import { ref, onMounted, onBeforeUnmount } from \’vue\’
const canvas = ref(null)
const width = ref(window.innerWidth * 0.8)
const height = ref(window.innerHeight * 0.8)
onMounted(() => {
const ctx = canvas.value.getContext(\’2d\’)
ctx.lineCap = \’round\’
ctx.lineJoin = \’round\’
ctx.lineWidth = 2
ctx.strokeStyle = \’#000\’
})
onBeforeUnmount(() => {
// 清理工作
})
“`
### 2. 绘图功能实现
添加绘图逻辑:
“`javascript
const isDrawing = ref(false)
const lastX = ref(0)
const lastY = ref(0)
const startDrawing = (e) => {
isDrawing.value = true
[lastX.value, lastY.value] = [e.offsetX, e.offsetY]
}
const draw = (e) => {
if (!isDrawing.value) return
const ctx = canvas.value.getContext(\’2d\’)
ctx.beginPath()
ctx.moveTo(lastX.value, lastY.value)
ctx.lineTo(e.offsetX, e.offsetY)
ctx.stroke()
[lastX.value, lastY.value] = [e.offsetX, e.offsetY]
}
const stopDrawing = () => {
isDrawing.value = false
}
// 在template中添加事件监听
/*
*/
“`
### 3. 工具栏实现
创建`Toolbar.vue`组件:
“`vue
import { ref } from \’vue\’
const emit = defineEmits([\’tool-change\’, \’clear\’])
const currentTool = ref(\’pen\’)
const color = ref(\’#000000\’)
const lineWidth = ref(2)
const changeTool = (tool) => {
currentTool.value = tool
emit(\’tool-change\’, { tool, color: color.value, width: lineWidth.value })
}
const clearCanvas = () => {
emit(\’clear\’)
}
“`
在`Whiteboard.vue`中集成工具栏:
“`vue
// …之前的代码
const updateDrawingSettings = ({ tool, color, width }) => {
const ctx = canvas.value.getContext(\’2d\’)
if (tool === \’eraser\’) {
ctx.globalCompositeOperation = \’destination-out\’
} else {
ctx.globalCompositeOperation = \’source-over\’
ctx.strokeStyle = color
}
ctx.lineWidth = width
}
const clearCanvas = () => {
const ctx = canvas.value.getContext(\’2d\’)
ctx.clearRect(0, 0, width.value, height.value)
}
“`
## 实现实时协作功能
### 1. 连接WebSocket服务器
使用`socket.io-client`实现实时通信:
“`javascript
import { ref, onMounted, onBeforeUnmount } from \’vue\’
import { io } from \’socket.io-client\’
const socket = io(\’http://localhost:3000\’)
const userId = ref(Date.now().toString())
const users = ref([])
onMounted(() => {
socket.on(\’connect\’, () => {
socket.emit(\’join\’, userId.value)
})
socket.on(\’user-connected\’, (id) => {
users.value.push(id)
})
socket.on(\’user-disconnected\’, (id) => {
users.value = users.value.filter(uid => uid !== id)
})
socket.on(\’drawing\’, (data) => {
drawOnCanvas(data)
})
})
onBeforeUnmount(() => {
socket.disconnect()
})
“`
### 2. 发送绘图数据
修改绘图函数,添加实时同步:
“`javascript
const draw = (e) => {
if (!isDrawing.value) return
const ctx = canvas.value.getContext(\’2d\’)
ctx.beginPath()
ctx.moveTo(lastX.value, lastY.value)
ctx.lineTo(e.offsetX, e.offsetY)
ctx.stroke()
const drawingData = {
userId: userId.value,
fromX: lastX.value,
fromY: lastY.value,
toX: e.offsetX,
toY: e.offsetY,
color: ctx.strokeStyle,
width: ctx.lineWidth,
tool: ctx.globalCompositeOperation === \’destination-out\’ ? \’eraser\’ : \’pen\’
}
socket.emit(\’drawing\’, drawingData)
[lastX.value, lastY.value] = [e.offsetX, e.offsetY]
}
const drawOnCanvas = (data) => {
const ctx = canvas.value.getContext(\’2d\’)
if (data.tool === \’eraser\’) {
ctx.globalCompositeOperation = \’destination-out\’
} else {
ctx.globalCompositeOperation = \’source-over\’
ctx.strokeStyle = data.color
}
ctx.lineWidth = data.width
ctx.beginPath()
ctx.moveTo(data.fromX, data.fromY)
ctx.lineTo(data.toX, data.toY)
ctx.stroke()
}
“`
### 3. 实现用户列表
创建`UserList.vue`组件:
“`vue
在线用户
-
{{ user === currentUser ? \’我\’ : `用户 ${user.slice(-4)}` }}
import { defineProps } from \’vue\’
defineProps({
users: Array,
currentUser: String
})
“`
在`App.vue`中集成所有组件:
“`vue
import { ref } from \’vue\’
import Whiteboard from \’./components/Whiteboard.vue\’
import UserList from \’./components/UserList.vue\’
const userId = ref(Date.now().toString())
const users = ref([])
“`
## 优化与扩展
### 1. 撤销/重做功能
使用命令模式实现撤销/重做:
“`javascript
const commandHistory = ref([])
const historyStep = ref(-1)
const saveState = () => {
historyStep.value++
if (historyStep.value {
if (historyStep.value > 0) {
historyStep.value–
restoreState()
}
}
const redo = () => {
if (historyStep.value {
const img = new Image()
img.src = commandHistory.value[historyStep.value]
img.onload = () => {
const ctx = canvas.value.getContext(\’2d\’)
ctx.clearRect(0, 0, width.value, height.value)
ctx.drawImage(img, 0, 0)
}
}
“`
### 2. 画布保存与导出
添加保存功能:
“`javascript
const saveCanvas = () => {
const link = document.createElement(\’a\’)
link.download = \’whiteboard.png\’
link.href = canvas.value.toDataURL()
link.click()
}
“`
### 3. 响应式设计
调整画布大小以适应不同屏幕:
“`javascript
const resizeCanvas = () => {
width.value = window.innerWidth * 0.8
height.value = window.innerHeight * 0.8
}
onMounted(() => {
window.addEventListener(\’resize\’, resizeCanvas)
})
onBeforeUnmount(() => {
window.removeEventListener(\’resize\’, resizeCanvas)
})
“`
## 总结
通过这个项目,我们使用Vue3的组合式API构建了一个功能完整的实时协作在线白板。在这个过程中,我们学习了:
1. 使用组合式API管理状态和生命周期
2. 实现Canvas绘图功能
3. 通过WebSocket实现实时协作
4. 设计可复用的组件
5. 添加撤销/重做等高级功能
Vue3的组合式API提供了更灵活、更直观的代码组织方式,特别适合处理复杂的状态逻辑和组件通信。这个项目展示了如何将Vue3的特性与实际需求相结合,创建出功能强大的应用。你可以基于这个基础继续扩展功能,如添加图层支持、更多绘图工具或云端存储等。




