寫於 2016.06.26html
項目地址:github.com/jrainlau/dr…vue
git clone git@github.com:jrainlau/draw-something.git
cd draw-something
node ws-server.js // 開啓websocket服務器
npm run dev // 運行客戶端程序
而後瀏覽器打開localhost:8080便可
複製代碼
效果預覽:node
由於閒得慌,一直和朋友在玩你畫我猜之類的小遊戲,忽然想到能不能本身也作一個呢,反正閒着也是閒着,同時正好能夠學習一下websocket的用法。git
首先分析總體架構部分:github
能夠看到,總體架構很是簡單,僅僅是一臺服務器和兩個客戶端。web
下面來看具體的代碼實現。npm
服務器採用node.js
進行搭建,使用了ws
庫實現websocket功能。新建一個名爲ws-socket.js
的文件,代碼以下:canvas
/*** ws-socket.js ***/
'use strict'
// 實例化WebSocketServer對象,監聽8090端口
const WebSocketServer = require('ws').Server
, wss = new WebSocketServer({port: 8090})
// 定義關鍵詞數組
let wordArr = ['Monkey', 'Dog', 'Bear', 'Flower', 'Girl']
wss.on('connection', (ws) => {
console.log('connected.')
// 隨機獲取一個關鍵詞
let keyWord = ((arr) => {
let num = Math.floor(Math.random()*arr.length)
return arr[num]
})(wordArr)
// 當服務器接收到客戶端傳來的消息時
// 判斷消息內容與關鍵詞是否相等
// 同時向全部客戶端派發消息
ws.on('message', (message) => {
console.log('received: %s', message)
if (message == keyWord) {
console.log('correct')
wss.clients.forEach((client) => {
client.send('答對了!!')
})
} else {
console.log('wrong')
wss.clients.forEach((client) => {
client.send(message)
})
}
})
// 服務器初始化時即向客戶端提供一個關鍵詞
wss.clients.forEach((client) => {
client.send('keyword:' + keyWord)
})
})
複製代碼
使用方法基本按照ws
庫的文檔便可。其中ws.on('message', (message) => { .. })
方法會在接收到從客戶端傳來消息時執行,利用這個方法,咱們能夠從繪圖畫布不斷地向服務器發送繪圖位點的座標,再經過.send()
方法把座標分發出去,在猜圖畫布中獲取座標,實現繪圖數據的同步。數組
做爲客戶端,我選擇了vue
進行開發,緣由是由於vue
使用簡單快速。事先說明,本項目僅僅做爲平常學習練手的項目而非vue的使用,因此有蠻多地方我是圖方便暴力使用諸如document.getElementById()
之類的寫法的,之後有機會再改爲符合vue
審美的代碼吧~瀏覽器
客戶端結構以下:
|
|-- script
| |-- components
| | |-- drawing-board.vue
| | |-- showing-board.vue
| |
| |-- App.vue
| |
| |-- index.js
|
|-- index.html
複製代碼
詳細代碼請直接瀏覽項目,這裏僅對關鍵部分代碼進行剖析。
位於./script/components/
的drawing-board.vue
文件即爲繪圖畫布組件。首先咱們定義一個Draw
類,裏面是全部繪圖相關的功能。
/*** drawing-board.vue ***/
'use strict'
class Draw {
constructor(el) {
this.el = el
this.canvas = document.getElementById(this.el)
this.cxt = this.canvas.getContext('2d')
this.stage_info = canvas.getBoundingClientRect()
// 記錄繪圖位點的座標
this.path = {
beginX: 0,
beginY: 0,
endX: 0,
endY: 0
}
}
// 初始化
init(ws, btn) {
this.canvas.onmousedown = () => {
this.drawBegin(event, ws)
}
this.canvas.onmouseup = () => {
this.drawEnd()
ws.send('stop')
}
this.clearCanvas(ws, btn)
}
drawBegin(e, ws) {
window.getSelection() ? window.getSelection().removeAllRanges() : document.selection.empty()
this.cxt.strokeStyle = "#000"
// 開始新的路徑(這一句很關鍵,你能夠註釋掉看看有什麼不一樣)
this.cxt.beginPath()
this.cxt.moveTo(
e.clientX - this.stage_info.left,
e.clientY - this.stage_info.top
)
// 記錄起點
this.path.beginX = e.clientX - this.stage_info.left
this.path.beginY = e.clientY - this.stage_info.top
document.onmousemove = () => {
this.drawing(event, ws)
}
}
drawing(e, ws) {
this.cxt.lineTo(
e.clientX - this.stage_info.left,
e.clientY - this.stage_info.top
)
// 記錄終點
this.path.endX = e.clientX - this.stage_info.left
this.path.endY = e.clientY - this.stage_info.top
// 把位圖座標發送到服務器
ws.send(this.path.beginX + '.' + this.path.beginY + '.' + this.path.endX + '.' + this.path.endY)
this.cxt.stroke()
}
drawEnd() {
document.onmousemove = document.onmouseup = null
}
clearCanvas(ws, btn) {
// 點擊按鈕清空畫布
btn.onclick = () => {
this.cxt.clearRect(0, 0, 500, 500)
ws.send('clear')
}
}
}
複製代碼
嗯,相信看代碼很容易就看懂了當中邏輯,關鍵就是在drawing()
的時候要不斷地把座標發送到服務器。
定義好Draw
類之後,在ready
階段使用便可:
ready: () => {
const ws = new WebSocket('ws://localhost:8090')
let draw = new Draw('canvas')
// 清空畫布按鈕
let btn = document.getElementById('btn')
// 與服務器創建鏈接後執行
ws.onopen = () => {
draw.init(ws, btn)
}
// 判斷來自服務器的消息並操做
ws.onmessage = (msg) => {
msg.data.split(':')[0] == 'keyword' ?
document.getElementById('keyword').innerHTML = msg.data.split(':')[1] :
false
}
}
複製代碼
猜圖畫布很簡單,只須要定義一個canvas畫布,而後接收服務器發送來的座標並繪製便可。看代碼:
ready: () => {
'use strict'
const ws = new WebSocket('ws://localhost:8090');
const canvas = document.getElementById('showing')
const cxt = canvas.getContext('2d')
// 是否從新設定路徑起點
// 爲了不把路徑起點重複定義在同一個地方
let moveToSwitch = 1
ws.onmessage = (msg) => {
let pathObj = msg.data.split('.')
cxt.strokeStyle = "#000"
if (moveToSwitch && msg.data != 'stop' && msg.data != 'clear') {
cxt.beginPath()
cxt.moveTo(pathObj[0], pathObj[1])
moveToSwitch = 0
} else if (!moveToSwitch && msg.data == 'stop') {
cxt.beginPath()
cxt.moveTo(pathObj[0], pathObj[1])
moveToSwitch = 1
} else if (moveToSwitch && msg.data == 'clear') {
cxt.clearRect(0, 0, 500, 500)
} else if (msg.data == '答對了!!') {
alert('恭喜你答對了!!')
}
cxt.lineTo(pathObj[2], pathObj[3])
cxt.stroke()
}
ws.onopen = () => {
let submitBtn = document.getElementById('submit')
// 發送答案到服務器
submitBtn.onclick = () => {
let keyword = document.getElementById('answer').value
ws.send(keyword)
}
}
}
複製代碼
到這裏,遊戲已經能夠玩啦!不過還有不少細節是有待增強和修改的,好比能夠給畫筆選擇顏色啊,多個用戶搶答計分啊等等。
大半天時間鼓搗出來的玩意兒,雖然粗糙,可是學到的東西還真很多,尤爲是websocket和canvas這兩個我所不熟悉的領域,果真實踐才能出真知。
選擇ES6真的可以極大地提高工做效率,Class
語法的出現簡直不能更贊,做爲才學習jQuery
源碼沒多久的我來講,ES6真的很是小清新。
歡迎持續關注個人專欄,會不斷送出乾貨哦,盡請期待!