當前位置:
首頁 > 最新 > Socket.io+canvas實現實時繪畫

Socket.io+canvas實現實時繪畫

前言

前段時間比較迷戀【你畫我猜】小遊戲,於是自己也動手寫個一個類似的demo。【你畫我猜】原理就是藉助socket.io技術實現同步繪畫。

WebSocketSocket.io介紹

WebSocket

WebSocket是HTML5一種新通信協議。它實現了瀏覽器與伺服器之間的雙向通信。瀏覽器通過javaScript向伺服器發出建立WebSocket連接的請求,連接建立以後,客戶端和服務端就可以通過TCP連接直接交換數據。

Socket.io

實際上Socket.io與websocket並不完全等同。它完全由JavaScript實現、基於Node.js、支持WebSocket協議用於實時通信、跨平台的開源框架,它包括了客戶端的JavaScript和伺服器端的Node.js

Socket.io是將Websocket和輪詢(Polling)機制以及其它的實時通信方式封裝成了通用的介面,並且在服務端實現了這些實時通信機制。Websocket僅僅是Socket.io實現實時通信的一個子集

【同步繪畫】整理架構

整體框架非常簡單,需要一台伺服器和多台客戶端

繪圖客戶端:進行canvas繪圖,把繪畫生成的base64數據流,傳給伺服器。

WebSocket伺服器:接收到的數據流,又將數據分發到指定的客戶端。

猜圖客戶端:接收到伺服器傳的base64數據流,作為img標籤src屬性值,生成圖片。

創建項目

1、創建express項目(已確保你安裝了nodeexpress)

$ express --view=ejs項目名

$ npm install

$ cd項目名

$ node ./bin/www

運行成功後,在瀏覽器中打開http://localhost:3000

默認埠號為3000,可修改)展示效果如下:

2、安裝socket.io依賴包

Socket.IO由兩部分組成:

一個服務端用於集成(或掛載) 到Node.JS HTTP 伺服器: socket.io

一個載入到瀏覽器中的客戶端:socket.io-client

開發環境下,socket.io會自動提供客戶端,只需要安裝一個模塊:

$ npm socket.io

3、服務端引入socket.io,客戶端引入socket.io-client

服務端(app.js/www

varapp =require("express")();

varhttp =require("http").createServer(app);

vario =require("socket.io")(http);

app.get("/",function(req, res){

res.sendFile(__dirname +"/index.html");

});

io.on("connection",function(socket){

console.log("a user connected");

});

http.listen(3000,function(){

console.log("listening on *:3000");

})

這段代碼作用如下:

Express初始化app作為HTTP伺服器的回調函數。

我們通過傳入http(HTTP伺服器)對象初始化了socket.io的一個實例。

定義了一個路由/來處理首頁訪問。

然後監聽connection事件來接收sockets,並將連接信息列印到控制台。

使http伺服器監聽埠3000。

客戶端(index.ejs)

var socket = io()

這樣就載入了socket.io-client。socket.io-client暴露了一個io全局變數,然後連接伺服器。

4、編寫【繪畫】客戶端、服務端代碼

新配置一個canvas路由

新增相應文件:canvas.ejs;canvas.js;canvas.css

在伺服器(www文件)編寫接受與發布繪畫數據流邏輯

代碼展示區

canvas.ejs

您的瀏覽器不支持canvas

清除

//連接伺服器

var socket = io()

//繪畫客戶端與猜圖客戶端渲染的頁面都是一樣的,現在根據url中的username的參數值做判斷

//如果當前是username為lsp,則展示canvas繪畫區,即為繪畫客戶端,其他為猜圖客戶端

if (username !== "lsp") {

$(".pro-canvas").css("display", "none")

}

socket.on("drawCanvas", function (data) {

//接收到服務端傳的數據流,作為img標籤src屬性值,生成圖片展示

$("#drawCanvas").attr("src", data)

})

cancvas.js

//定義寬和高

varcanvasWidth =Math.min(800, $(window).width() -20)//適配移動端

varcanvasHeight = canvasWidth

varstrokeColor ="black"//當前筆的顏色

varisMouseDown =false//定義滑鼠是否按下

varlastLoc =//定義上一次滑鼠的位置,

varlastTimestamp =//定義時間戳

varlastLineWidth =-1//定義上一次線條的寬度

varcanvas =document.getElementById("canvas")//拿到canvas

vardrawCanvas =document.getElementById("drawCanvas")//拿到img

varcontext = canvas.getContext("2d")//拿到相應的上下文繪圖環境

//設定畫布的寬和高

canvas.width = canvasWidth

canvas.height = canvasHeight

//圖片與畫布展示一致,寬高一致

drawCanvas.width = canvasWidth

drawCanvas.height =canvasHeight

//適配移動端

$("#controller").css("width", canvasWidth +"px")

//繪製米字格

drawGrid()

//canvas導出數據流,傳值給後台

functionreturnData() {

//觸發服務端"startConnect"事件,傳值給後台

socket.emit("startConnect", canvas.toDataURL())

}

//輪循

varlongPolling

functionpolling() {

longPolling = setInterval(function() {

returnData() },200)

}

//清除按鈕操作

$("#clear_btn").click(

function(e) {

context.clearRect(,, canvasWidth, canvasHeight)

drawGrid()//重新繪製米字格

returnData()//發送數據流給伺服器

}

)

//選擇繪畫顏色

$(".color_btn").click(

function(e){

$(".color_btn").removeClass("color_btn_selected")

$(this).addClass("color_btn_selected")

strokeColor = $(this).css("background-color")

}

)

//邏輯整合

functionbeginStroke(point) {

isMouseDown =true

lastLoc = windowToCanvas(point.x, point.y )

lastTimestamp =newDate().getTime()

polling()

}

functionendStroke() {

isMouseDown =false

clearInterval(longPolling)//清除輪詢

}

//繪畫

functionmoveStroke(point){

//核心代碼

varcurLoc = windowToCanvas(point.x, point.y )

varcurTimestamp =newDate().getTime()

/****Draw Start****/

context.beginPath()

context.moveTo(lastLoc.x, lastLoc.y)

context.lineTo(curLoc.x, curLoc.y)

//計算速度

vars = calcDistance(curLoc, lastLoc)

vart = curTimestamp - lastTimestamp

varlineWidth = calcLineWidth( t, s )

context.strokeStyle = strokeColor

context.lineWidth = lineWidth

context.lineCap ="round"

context.lineJoin ="round"

context.stroke()

/****Draw End****/

lastLoc = curLoc

lastTimestamp = curTimestamp

lastLineWidth = lineWidth

}

//滑鼠事件,web端

canvas.onmousedown =function(e){

e.preventDefault()//阻止默認的動作發生

beginStroke({ x: e.clientX, y: e.clientY })

}

canvas.onmouseup =function(e){

e.preventDefault()

endStroke()

}

canvas.onmouseout =function(e){

e.preventDefault()

endStroke()

}

canvas.onmousemove =function(e){

if(isMouseDown) {//確定滑鼠按下

e.preventDefault()

moveStroke()//可以繪圖了

}

}

//觸控事件,移動端

canvas.addEventListener("touchstart",function(e){

e.preventDefault()

touch = e.touches[]

beginStroke({ x: touch.pageX, y: touch.pageY })

})

canvas.addEventListener("touchmove",function(e){

e.preventDefault()

if(isMouseDown) {//確定滑鼠按下

touch = e.touches[]

moveStroke({ x: touch.pageX, y: touch.pageY })//可以繪圖了

}

})

canvas.addEventListener("touchend",function(e){

e.preventDefault()

endStroke()

})

/**

*計算筆的寬度

*/

functioncalcLineWidth(t, s) {

varv = s / t

varresultLineWidth =

if( v

resultLineWidth =10

}elseif( v >=10) {

resultLineWidth =1

}else{

resultLineWidth =10- (v -0.1) / (10-0.1) * (10-1)

}

if(lastLineWidth ==-1) {

returnresultLineWidth

}else{

returnlastLineWidth *2/3+ resultLineWidth *1/3

}

}

/**

*計算距離

*/

functioncalcDistance(loc1, loc2) {

returnMath.sqrt((loc1.x - loc2.x)*(loc1.x - loc2.x) + (loc1.y - loc2.y)*(loc1.y - loc2.y))

}

/**

*窗口到畫布的位置

*/

functionwindowToCanvas(x, y) {

varbox = canvas.getBoundingClientRect()

return

}

/**繪製米字格**/

functiondrawGrid() {

context.save()

//繪製紅色的正方形邊框

context.strokeStyle ="rgb(230, 11, 9)"

context.beginPath()

context.moveTo(3,3)

context.lineTo(canvasWidth -3,3)

context.lineTo(canvasWidth -3, canvasHeight -3)

context.lineTo(3, canvasHeight -3)

context.closePath()

context.lineWidth =6

context.stroke()

//繪製米字格

context.beginPath()

context.moveTo(,)

context.lineTo(canvasWidth, canvasHeight)

context.moveTo(canvasWidth,)

context.lineTo(, canvasHeight)

context.moveTo(canvasWidth /2,)

context.lineTo(canvasWidth /2, canvasHeight)

context.moveTo(, canvasHeight /2)

context.lineTo(canvasWidth, canvasHeight /2)

context.closePath()

context.lineWidth =1

context.stroke()

context.restore()

}

服務端代碼(www

#!/usr/bin/env node

/**

* Module dependencies.

*/

varapp =require("../app");

vardebug =require("debug")("drawguess:server");

varhttp =require("http");

/**

* Get port from environment and store in Express.

*/

varport = normalizePort(process.env.PORT ||"3000");

app.set("port", port);

/**

* Create HTTP server.

*/

varserver = http.createServer(app);

vario =require("socket.io")(server)

io.on("connection",function(socket) {

console.log("a user connected");

//監聽"startConnect"事件,接受數據流

socket.on("startConnect",function(data) {

// console.log("startConnect", data)

//向客戶端廣播"drawCanvas"事件,返回數據流

})

});

/**

* Listen on provided port, on all network interfaces.

*/

server.listen(port);

server.on("error", onError);

server.on("listening", onListening);

/**

* Normalize a port into a number, string, or false.

*/

functionnormalizePort(val) {

varport =parseInt(val,10);

if(isNaN(port)) {

// named pipe

returnval;

}

if(port >=) {

// port number

returnport;

}

returnfalse;

}

/**

* Event listener for HTTP server "error" event.

*/

functiononError(error) {

if(error.syscall !=="listen") {

throwerror;

}

varbind =typeofport ==="string"

?"Pipe "+ port

:"Port "+ port;

// handle specific listen errors with friendly messages

switch(error.code) {

case"EACCES":

console.error(bind +" requires elevated privileges");

process.exit(1);

break;

case"EADDRINUSE":

console.error(bind +" is already in use");

process.exit(1);

break;

default:

throwerror;

}

}

/**

* Event listener for HTTP server "listening" event.

*/

functiononListening() {

varaddr = server.address();

varbind =typeofaddr ==="string"

?"pipe "+ addr

:"port "+ addr.port;

debug("Listening on "+ bind);

}

頁面展示效果

githunb地址:https://github.com/Liao640/guessCavas

項目部署地址:http://193.112.106.197:8085/

用戶名為lsp為繪畫客戶端,其他用戶登陸為猜圖客戶端。功能後續更新


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 興海開放平台 的精彩文章:

TAG:興海開放平台 |