HTML5創意畫板的設計教程
在HTML5備受期待和矚目的今天,越來越多的人已經感受到它帶來的無限魅力與震撼力,許多的技術人員、設計者、互聯網愛好者們紛紛加入了HTML5的研究與設計中。
首先我先為大家介紹一下一個功能很強大的HTML5在線繪畫應用,它還擁有多種筆刷和濾鏡,具有類似于photoshop的圖層功能,可調節透明度隱藏等,還有漸變、油漆桶、拾色器、選擇工具,大家一定會為此感到驚訝吧。
但這樣復雜的應用并沒有使用flash實現,在canvas標記還沒有出現之前,要想實現復雜的網頁應用,或者直接在網頁上進行繪圖,只能借助于第三方的插件,比如Flash或Java,而現在,借助于canvas標記,我們可以實現圖像顯示和處理了,那么現在就讓我拋磚引玉,講解一下我的一些開發思路吧。 想要制作一個簡單的畫板并不是太難,但我建議您掌握一定的canvas基礎和javascript基礎,這樣更便于理解和學習本教程。而如果你canvas技術比較好的話,你一定會覺得本教程又長又啰嗦,但是教程不可能顧及到所有的閱讀者,所以麻煩你跳過你了解的部分,只關注重要的部分就好了。 首先,我講解一下我的開發思路。我們需要在頁面中添加一個canvas標記作為我們的畫布,也就是我們將來要繪畫的畫板。由于需要用戶使用鼠標點擊、滑動、釋放鼠標等操作來實現繪畫,所以我們也必須要使用鼠標的幾個基本的監聽事件mousemove、mouseup、mousedown。 為了使繪畫出來的線條更流暢,兼顧性能問題,我們可以采用setInterval來設置監聽事件的時間間隔。 setInterval(函數名,1000/60); 其中1000/60為時間間隔。 loop為循環執行的函數。 當然,你也可以采用requestAnimationFrame(如果不了解該屬性可以自行百度^_^)。這取決于你的習慣。 那么現在我們需要獲取用戶鼠標點擊的位置,在這里我們需要區分pageX,clientX,offsetX,layerX等概念 ,這里有篇文章講解,你可以看看http://www.funnyhao.com/pagex-clientx-offsetx-layerx-of-those-things/ 由于我們現在畫布直接放在頁面上左上部,padding和margin都為0,因此我們直接用clientX和clientY即可.當用戶第一次點擊鼠標時,我們設置isMouseDown為true,開啟繪畫模式。 獲取了用戶點擊的位置后,我們在約定的時間間隔后(1/60秒)再次獲取用戶所在的位置,并進行更新 接下來我們就可以繪制了 似乎這樣的大功告成了??催@里的演示代碼:DEMO1(http://runjs.cn/detail/gxeeyocw) 當我們畫畫時,如果繪制筆移動的較快的時候,就會發現出現了斷斷續續的情況,這是怎么回事呢?原來我們只設置了一個點每過1/60秒就更新一下位置,當我們繪圖時如果畫筆移動的速度夠快時繪制的不夠密集,繪制的點久不能連接起來,從而引起斷續的現象。 可能會有些人說可以設置時間間隔更小,比如設置為1/1000秒,也就是將頁面中的代碼 改為 甚至無窮小,這樣不就解決了嗎。但是相信很多人都不會推薦這樣的方法,因為這不僅僅會影響到頁面的效率,而且也沒有從根本上解決問題,setinterval調用間隔的時間往往會有諸多限制,所以這樣的方法是行不通的。 要讓線連貫起來最簡單的方法:那就用線連起來吧。(旁白:廢話,⊙﹏⊙b汗)我們知道兩點確定一條直線,所以只要我們確定兩個點的坐標即可。亦即每個時間間隔單位,我們獲取一次當前點的坐標就好了。然后使用canvas的moveTo函數移動下一個點,記錄當前點坐標和上一個點的坐標,并使用canvas的lineTo函數將線連起來,然后不要忘了用stroke函數繪制出來,具體看這里的代碼:DEMO2(http://runjs.cn/detail/r52qaltg)。 表格中明顯看出方案一都是孤立的點,而方案二每個點都會有兩種狀態,將這兩種狀態下的點連起來就會形成銜接的較好的效果。 因為基礎的內容在上面已經講述了,所以在這里我也不重復了,需要注意的是當前點與上一個點重復時需要做一下處理,否則頁面無法繪制出來。 為了方便講解,我這里采用的都是面向過程的方法,在比較大的應用中,我們要盡可能采用面向對象的方法,好處是不言而喻的,不僅能讓代碼條理清晰,更有較好的擴展性,方便二次開發和模塊復用。使用面向對象方法的代碼請查看這里(這里會使用了point函數類,覆蓋了set和update等方法)請查看DEMO3_1(http://runjs.cn/detail/gvfyrswu)。 研究技術的時候,我們需要舉一反三,顯然現在的方法還是不夠完善。能不能將所有的點都記錄下來,因為每個時間間隔單位,都會損失掉很多的點,為了讓畫出來的圖更加圓滑,我們要將所有的點都記錄下來,并且效率又能得到優化,我在這里提出一個解決方案。 用數組記錄下所有的路徑,然后用堆棧的push方法將點添加到數組中,為了達到更好的效率,我們可以采用一維數組,分別用兩個數組記錄橫坐標和縱坐標,具體的實現我就不貼代碼了,大家有余力的話可以當作一個小小的作業,參照我的這個頁面例子自己編寫代碼實現,頁面中會有代碼注釋的。 我們實現了繪制功能,我們還需要對繪制的圖片進行擦除。不要嘗試采用transparent或者rgba(x,x,x,0)這樣的顏色值繪制,因為這樣頁面便不會繪制出任何東西,最實用的方法就是繪制背景顏色,如果背景是圖片,那就重繪背景圖片,然后就原來的內容的其他部分繪制到畫布中。具體查看demo3_2(http://runjs.cn/detail/jywf4qv1) 那如果我們要實現蠟筆的效果。要怎么處理呢,如果我們將蠟筆畫放大后看就會知道那是一些很小的分散顆粒狀大小的粒子,這樣我們就有了思路了。我們還是沿用DEMO3的例子,在其基礎上進行開發,需要注意的一點是粒子的分布問題,如何才能將粒子均勻的分布呢,不知道大伙們這么久沒學數學是不是都將知識還給老師了。這里我們會用到一些基本的數學知識, 具體思路請看下圖, 請參照源代碼 進行分析比較。思路的重點是在一定間隔后對粒子進行隨機分散排布,并能處理在畫筆移動的比較快的時候的的繪制問題。為了得到更好的展示效果,我們一般還會控制透明度進行調整。 溫馨提示:在鍵盤上輸入p鍵可以導出圖片,圖片導出功能由于在新窗口中打開,請使用全屏預覽模式并允許窗口彈出。 其實我們還可以有更多變化,只要你去構想,去思考。很多時候你都要去嘗試,往往多次嘗試才會有新的ideas。接下來我們可是做出以下特殊的畫筆,比如說鋼筆效果,邊緣會比較筆觸比較重的。鋼筆效果需要將畫筆尺寸調小,減弱抽絲的效果,并且邊緣的鋸齒會比較明顯,所以需要做一下陰影模糊處理,讓其過渡更平滑。 具體源碼看這里:demo5(http://runjs.cn/detail/df5u6cb5) 之前曾在這里見過一個毛筆畫網站,所以也模擬了一下毛筆的效果,毛筆的特點是筆觸比較大,當收筆較快時邊緣要凹凸不平,筆尖寫字鋒棱易出。當收筆有停頓時則會圓潤而渾厚。有個類似的線上應用,大家可以去研究研究:http://www.theshodo.com/Write。 根據這些特點,我給大家提供一個DEMO源碼,是在鋼筆效果的基礎上做些小小的調整的。 如果大家還覺得這樣的效果是否還可以添加點特效什么的,對,可以做雜點斑點的效果,還有墨水過多而流下的效果。 具體可以參看DEMO6(http://runjs.cn/detail/ully3puv)和DEMO7。 源碼我就不進行分析了,留待大家自己去研究,將畫筆顏色調為黑色就差不多可以模擬出毛筆的效果了。在我看來,技術的專研不是一味的讓別人教你,而是讓你自己去領悟的。只有那樣你才真正學到技術,領略到不一樣的樂趣。 說些設計以外的東西,設計、編程都需要有自己的思想和靈魂,真正讓別人也能感受到你的思路,而這一切都需要磨練,需要舉一反三。不應該滿足于現狀,我在這里只列舉了其中一些效果,我相信還有很多效果可以實現,比如說類似于這樣的噴霧效果,鉛筆字效果,藝術畫效果,等等。既然說HTML5創意畫板,那就要嘗試脫離這個畫板的束縛,學到更多的東西,比如說你可以用這個畫板做什么,可以做一個記事本、涂鴉工具、處理和分享圖片,個性簽名,你還可以做一些小游戲,涂鴉類的游戲,你畫我猜(需要采用websocket實現服務器端雙向通信)等等,甚至可以做一些canvas動畫,這些基礎上做些修改和調整,完全是可以實現的。有了目標和思路,那就沿著這個方向去學習,我相信你一定會有所收獲的。 這次的HTML5繪圖教程就到這里,大家還可以嘗試為此添加更多的個性化的功能,同時歡迎大家留言提問或者提出批評建議。 源碼下載: 擴展閱讀: 來源:微博UDC document.addEventListener('mousemove', mouseMove, false);
document.addEventListener('mousedown', mouseDown, false);
document.addEventListener('mouseup', mouseUp, false);
setInterval(loop, 1000 / 60);
function loop() {
$pos_display.innerHTML='你當前鼠標的位置為('+pos.x+','+pos.y+')';
if (isMouseDown) draw(context);}
function mouseDown(e) {
isMouseDown = true;
}
function loop() {
if (isMouseDown) draw(context);//繪制鼠標點擊位置
}
function mouseMove(e) {
pos.x=e.clientX;//設置x坐標
pos.y=e.clientY;//設置y坐標
$pos_display.innerHTML='你當前點擊鼠標的位置為('+pos.x+','+pos.y+')';//更新當前鼠標點擊的位置
}
function draw(ctx) {
ctx.save();//保存當前繪圖狀態
ctx.fillStyle = DEFAULT_BRUSH_COLOR;//設置填充的背景顏色
ctx.lineWidth =DEFAULT_BRUSH_SIZE; //設置畫筆的大小
ctx.lineCap = "round"; //設置線條,讓線條邊緣更圓滑
ctx.beginPath();
ctx.arc(pos.x,pos.y,DEFAULT_BRUSH_SIZE,0,Math.PI * 2,true);
/****
*context.arc(x, y, radius, startAngle, endAngle, anticlockwise)
*參數 x,y表示圓心
*radius半徑
*startAngle起始弧度
*endAngle終止弧度
*anticlockwise是否為逆時針方向
***/
ctx.fill();//填充繪畫路徑
ctx.restore();//恢復繪畫狀態
}
setInterval(loop, 1000 / 60);
setInterval(loop, 1000 / 1000);
if(pos.x==next_pos.x&&pos.y==next_pos.y){
ctx.arc(pos.x,pos.y,DEFAULT_BRUSH_SIZE/1.7,0,Math.PI * 2,true);
ctx.fill();//填充繪畫路徑}
else{
ctx.moveTo(pos.x,pos.y);
ctx.lineTo(next_pos.x,next_pos.y);
ctx.stroke();
}
draw: function(ctx) {
var v = this.subtract(this._latest);//當前點與下一個點的距離的橫縱坐標
var s = Math.ceil(this.size / 2); //算出粒子的單位長度
var stepNum = Math.floor(v.length() / s) + 1; //算出步長 v.length()為斜線長度
v.normalize(s);//當前點與下一個點的
var sep = 1.5; // 分割數 控制畫筆的濃密程度 關鍵所在
//粒子的大小 根據畫筆描繪的速度(畫筆的停留時間)進行調整
var dotSize = sep * Math.min(this.inkAmount / this._latestStrokeLength * 3, 1);
var dotNum = Math.ceil(this.size * sep);
var range = this.size / 2;
var i, j, p, r, c, x, y;
$whitemode_display.innerHTML="繪制的畫筆顏色是"+brush_color;
ctx.save();
ctx.fillStyle = currentColor;
$pos_display.innerHTML='你上一點鼠標的位置為('+this.x+','+this.y+').
你當前鼠標的位置為('+this._latest.x+','+this._latest.y+')';//更新當前鼠標點擊的位置
ctx.beginPath();
if(wmode=="擦除模式"){
ctx.strokeStyle=brush_color;
ctx.lineWidth =DEFAULT_BRUSH_SIZE;
ctx.lineCap = "round";
ctx.beginPath();
p = this._latest;//獲取下一個點位置
ctx.moveTo(this.x,this.y);
ctx.lineTo(p.x,p.y);
ctx.stroke();
}
else{
for (i = 0; i < dotNum; i++) {
for (j = 0; j < stepNum; j++) {
p = this._latest.add(v.scale(j));
r = random(range);
c = random(Math.PI * 2);
w = random(dotSize, dotSize / 2);
h = random(dotSize, dotSize / 2);
x = p.x + r * Math.sin(c) - w / 2;
y = p.y + r * Math.cos(c) - h / 2;
ctx.rect(x, y, w, h);//邊緣不要太平滑,不要使用arc
}
}
}
ctx.fill();
ctx.restore();
}
});
處理完這些后,我們如果喜歡這樣的圖片,還可以使用圖片導出功能,方法也挺簡單。去掉監聽事件,使用canvas的toDataURL內置函數,然后展示到新打開的窗口中。
我們就可以運行一下源代碼看看帶擦出功能和圖片導出功能的實際效果如何:
runJS: http://runjs.cn/detail/spxs2kxq
微盤:http://vdisk.weibo.com/s/otSnZ
百度網盤:http://pan.baidu.com/share/link?shareid=194573&uk=3744164386(github實在太高端了,不符合國情,所以無視了)
在線毛筆畫板:http://www.theshodo.com/Write
來自deviants的在線畫板:http://sta.sh/muro/
如果想查看之前的FLASH版本,可以點擊這里http://www.inzrb.com/blog/?page_id=211
請問DEMO6如何改成可以在移動端上玩,我試過把mousedown,mouseup,mousemove,改成touchstart,touchmove,touchend也不行 ??