單人操作白板,或是在瀏覽器間交錯操作,對運作的影響都不太大,但是我們要做的是多人同時操作,這時會發生什麼事呢?
昨天其實只試做出一個透過WebSocket協同運作的繪圖方法,在測試多人協同的問題前,要先把所有的繪圖方法完成。
function wsCallBack(obj) {
switch(obj.type) {
case 'drawText':
obj.type = 'wsDrawText';
break;
case 'freestyle':
obj.type = 'wsDrawLine';
break;
case 'strokeRect':
obj.type = 'wsStrokeRect';
break;
case 'fillRect':
obj.type = 'wsFillRect';
break;
case 'strokeCircle':
obj.type = 'wsStrokeCircle';
break;
case 'fillCircle':
obj.type = 'wsFillCircle';
break;
case 'strokeEclipse':
obj.type = 'wsStrokeEclipse';
break;
case 'fillEclipse':
obj.type = 'wsFillEclipse';
break;
case 'eraser':
obj.type = 'wsEraser';
break;
}
socket.emit('wsdraw', obj);
}
首先,就是調整一下在drawText使用的Callback,讓他對所有的繪圖方法都有效。由於在繪圖方法中傳給callback是自己的名字,在這裡把它改成實際會透過WebSocket觸發的繪圖方法的名字。
另外,就是把所有透過WebSocket繪圖的方法都實作出來:
(function(window, undefined) {
// define new drawtype and method
if(window['$C']) {
var tool = window['$C'];
} else {
return;
}
///////////////////////////
var wsDrawText = {
type: 'wsDrawText',
run: function(text, x, y) {
this.ctx.textAlign = 'left';
this.ctx.fillText(text, x, y, this.ctx.measureText(text).width);
},
handlers: {
wsdraw: function(e) {
this.ctx.save();
for(var i in e.globals) {
this.ctx[i] = e.globals[i];
}
this.do(e.data.text, e.data.pos[0], e.data.pos[1]);
this.ctx.restore();
}
}
};
tool.regist(wsDrawText);
///////////////////////////
var wsDrawLine = {
type: 'wsDrawLine',
run: function(x, y, x1, y1) {
this.ctx.beginPath();
this.ctx.moveTo(x,y);
this.ctx.lineTo(x1,y1);
this.ctx.closePath();
this.ctx.stroke();
},
handlers: {
wsdraw: function(e) {
this.ctx.save();
for(var i in e.globals) {
this.ctx[i] = e.globals[i];
}
this.do(e.data[0], e.data[1], e.data[2], e.data[3]);
this.ctx.restore();
}
}
};
tool.regist(wsDrawLine);
///////////////////////////
var wsStrokeRect = {
type: 'wsStrokeRect',
run: function(x, y, x1, y1) {
this.ctx.beginPath();
this.ctx.rect(x, y, x1-x, y1-y);
this.ctx.closePath();
this.ctx.stroke();
},
handlers: {
wsdraw: function(e) {
this.ctx.save();
for(var i in e.globals) {
this.ctx[i] = e.globals[i];
}
this.do(e.data[0], e.data[1], e.data[2], e.data[3]);
this.ctx.restore();
}
}
};
tool.regist(wsStrokeRect);
///////////////////////////
var wsFillRect = {
type: 'wsFillRect',
run: function(x, y, x1, y1) {
this.ctx.beginPath();
this.ctx.rect(x, y, x1-x, y1-y);
this.ctx.closePath();
this.ctx.fill();
},
handlers: {
wsdraw: function(e) {
this.ctx.save();
for(var i in e.globals) {
this.ctx[i] = e.globals[i];
}
this.do(e.data[0], e.data[1], e.data[2], e.data[3]);
this.ctx.restore();
}
}
};
tool.regist(wsFillRect);
///////////////////////////
var wsStrokeCircle = {
type: 'wsStrokeCircle',
run: function(x1, y1, x2, y2) {
var radius = Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2), 2);
this.ctx.beginPath();
this.ctx.arc(x1, y1, radius, 0, 2*Math.PI, true);
this.ctx.closePath();
this.ctx.stroke();
},
handlers: {
wsdraw: function(e) {
this.ctx.save();
for(var i in e.globals) {
this.ctx[i] = e.globals[i];
}
this.do(e.data[0], e.data[1], e.data[2], e.data[3]);
this.ctx.restore();
}
}
};
tool.regist(wsStrokeCircle);
///////////////////////////
var wsFillCircle = {
type: 'wsFillCircle',
run: function(x1, y1, x2, y2) {
var radius = Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2), 2);
this.ctx.beginPath();
this.ctx.arc(x1, y1, radius, 0, 2*Math.PI, true);
this.ctx.closePath();
this.ctx.fill();
},
handlers: {
wsdraw: function(e) {
this.ctx.save();
for(var i in e.globals) {
this.ctx[i] = e.globals[i];
}
this.do(e.data[0], e.data[1], e.data[2], e.data[3]);
this.ctx.restore();
}
}
};
tool.regist(wsFillCircle);
///////////////////////////
var wsStrokeEclipse = {
type: 'wsStrokeEclipse',
run: function(x1, y1, x2, y2) {
this.ctx.beginPath();
var xc1 = x1, yc1 = y1 - Math.abs(y2-y1), xc2 = x2, yc2 = y1 - Math.abs(y2-y1), xx2 = x2, yy2 = y1,
xc3 = x2, yc3 = y1 + Math.abs(y2-y1), xc4 = x1, yc4 = y1 + Math.abs(y2-y1);
this.ctx.moveTo(x1, y1);
this.ctx.bezierCurveTo(xc1, yc1, xc2, yc2, xx2, yy2);
this.ctx.bezierCurveTo(xc3, yc3, xc4, yc4, x1, y1);
this.ctx.closePath();
this.ctx.stroke();
},
handlers: {
wsdraw: function(e) {
this.ctx.save();
for(var i in e.globals) {
this.ctx[i] = e.globals[i];
}
this.do(e.data[0], e.data[1], e.data[2], e.data[3]);
this.ctx.restore();
}
}
};
tool.regist(wsStrokeEclipse);
///////////////////////////
var wsFillEclipse = {
type: 'wsFillEclipse',
run: function(x1, y1, x2, y2) {
this.ctx.beginPath();
var xc1 = x1, yc1 = y1 - Math.abs(y2-y1), xc2 = x2, yc2 = y1 - Math.abs(y2-y1), xx2 = x2, yy2 = y1,
xc3 = x2, yc3 = y1 + Math.abs(y2-y1), xc4 = x1, yc4 = y1 + Math.abs(y2-y1);
this.ctx.moveTo(x1, y1);
this.ctx.bezierCurveTo(xc1, yc1, xc2, yc2, xx2, yy2);
this.ctx.bezierCurveTo(xc3, yc3, xc4, yc4, x1, y1);
this.ctx.closePath();
this.ctx.fill();
},
handlers: {
wsdraw: function(e) {
this.ctx.save();
for(var i in e.globals) {
this.ctx[i] = e.globals[i];
}
this.do(e.data[0], e.data[1], e.data[2], e.data[3]);
this.ctx.restore();
}
}
};
tool.regist(wsFillEclipse);
///////////////////////////
var wsEraser = {
type: 'wsEraser',
run: function(x, y, x1, y1) {
this.ctx.clearRect(x-8, y-8, 16, 16);
var d = Math.abs(x1-x);
var xt = x, yt = y;
for(var i=0; i<d; i++) {
xt += (x1-x)/d;
yt += (y1-y)/d;
this.ctx.clearRect(xt-8, Math.round(yt)-8, 16, 16);
}
this.ctx.clearRect(x1-8, y1-8, 16, 16);
},
handlers: {
wsdraw: function(e) {
this.ctx.save();
for(var i in e.globals) {
this.ctx[i] = e.globals[i];
}
this.do(e.data[0], e.data[1], e.data[2], e.data[3]);
this.ctx.restore();
}
}
};
tool.regist(wsEraser);
})(window);
這樣,就可以透過WebSocket觸發所有的繪圖動作。
接下來就要測試了,測試方法很簡單,就是在Socket.IO伺服器中加一段程式,每隔一秒畫一條隨機的直線:
setInterval(function() {
var x1 = Math.round(Math.random()*1000)%640;
var y1 = Math.round(Math.random()*1000)%480;
var x2 = Math.round(Math.random()*1000)%640;
var y2 = Math.round(Math.random()*1000)%480;
socket.emit('wsdraw', {type:'wsDrawLine', globals:{strokeStyle: '#336699',lineWidth:1}, data:[x1,y1,x2,y2]});
}, 1000);
然後在WebSocket觸發繪圖的同時,操作白板。
雖然從靜態的抓圖中看不太出來,但是在做拉出圖形的效果時,是先把canvas存起來,然後拉圖形的過程中,先把存起來的canvas寫回,再畫出拉到目前座標的圖形。但是這樣一來,在拉圖形的過程中,WebSocket觸發的繪圖就不斷消失
先嘗試一個解法,是讓WebSocket觸發的繪圖最後,更新存起來的canvas。這樣看起來好像比較好,至少WebSocket觸發的繪圖不會消失,但是這樣又發生另外一個問題...我們拉的圖形只是為了看到圖形畫成怎樣,直到mouseup或mouseleave,畫出的才是要真正畫出的圖形。但是...由於WebSocket觸發的繪圖更新存起來的Canvas,就影響到這個過程,結果中途拉的這些圖形就被畫到白板上而不會消失,這並不是我們要的結果。
既然是這樣,如果在WebSocket觸發的繪圖之初,先恢復(清掉拉圖形過程中出現的繪圖)Canvas,這樣就不會把這些過程中的示意用的繪圖寫入,但是這樣拉的圖形如果暫時沒拖動,就會消失(拖動的話又會出現)。
要讓示意用的繪圖畫在最終要繪出的Canvas上,就會造成這樣的衝突,而且單靠這樣簡單的儲存、寫回Canvas的動作,是沒辦法解決的。看來唯一的解法,是用兩個Canvas來實作。疊在上方的Canvas,負責顯示繪圖過程中出現的示意用圖形,到了決定(mouseup或mouseleave)時,才清掉臨時的Canvas把圖形畫到最終顯示的Canvas,這樣兩種繪圖的動作才不會衝突。
這樣又需要大幅改動PaintTools的架構了,等明天再來做吧。