HTML5創(chuàng)意畫板的設(shè)計(jì)教程

1 評(píng)論 15182 瀏覽 2 收藏 20 分鐘

在HTML5備受期待和矚目的今天,越來越多的人已經(jīng)感受到它帶來的無限魅力與震撼力,許多的技術(shù)人員、設(shè)計(jì)者、互聯(lián)網(wǎng)愛好者們紛紛加入了HTML5的研究與設(shè)計(jì)中。

首先我先為大家介紹一下一個(gè)功能很強(qiáng)大的HTML5在線繪畫應(yīng)用,它還擁有多種筆刷和濾鏡,具有類似于photoshop的圖層功能,可調(diào)節(jié)透明度隱藏等,還有漸變、油漆桶、拾色器、選擇工具,大家一定會(huì)為此感到驚訝吧。

但這樣復(fù)雜的應(yīng)用并沒有使用flash實(shí)現(xiàn),在canvas標(biāo)記還沒有出現(xiàn)之前,要想實(shí)現(xiàn)復(fù)雜的網(wǎng)頁(yè)應(yīng)用,或者直接在網(wǎng)頁(yè)上進(jìn)行繪圖,只能借助于第三方的插件,比如Flash或Java,而現(xiàn)在,借助于canvas標(biāo)記,我們可以實(shí)現(xiàn)圖像顯示和處理了,那么現(xiàn)在就讓我拋磚引玉,講解一下我的一些開發(fā)思路吧。

想要制作一個(gè)簡(jiǎn)單的畫板并不是太難,但我建議您掌握一定的canvas基礎(chǔ)和javascript基礎(chǔ),這樣更便于理解和學(xué)習(xí)本教程。而如果你canvas技術(shù)比較好的話,你一定會(huì)覺得本教程又長(zhǎng)又啰嗦,但是教程不可能顧及到所有的閱讀者,所以麻煩你跳過你了解的部分,只關(guān)注重要的部分就好了。

首先,我講解一下我的開發(fā)思路。我們需要在頁(yè)面中添加一個(gè)canvas標(biāo)記作為我們的畫布,也就是我們將來要繪畫的畫板。由于需要用戶使用鼠標(biāo)點(diǎn)擊、滑動(dòng)、釋放鼠標(biāo)等操作來實(shí)現(xiàn)繪畫,所以我們也必須要使用鼠標(biāo)的幾個(gè)基本的監(jiān)聽事件mousemove、mouseup、mousedown。

  document.addEventListener('mousemove', mouseMove, false);
        document.addEventListener('mousedown', mouseDown, false);
        document.addEventListener('mouseup', mouseUp, false);

為了使繪畫出來的線條更流暢,兼顧性能問題,我們可以采用setInterval來設(shè)置監(jiān)聽事件的時(shí)間間隔。 setInterval(函數(shù)名,1000/60); 其中1000/60為時(shí)間間隔。

  setInterval(loop, 1000 / 60);
function loop() {
    $pos_display.innerHTML='你當(dāng)前鼠標(biāo)的位置為('+pos.x+','+pos.y+')';
    if (isMouseDown) draw(context);}

loop為循環(huán)執(zhí)行的函數(shù)。

當(dāng)然,你也可以采用requestAnimationFrame(如果不了解該屬性可以自行百度^_^)。這取決于你的習(xí)慣。

那么現(xiàn)在我們需要獲取用戶鼠標(biāo)點(diǎn)擊的位置,在這里我們需要區(qū)分pageX,clientX,offsetX,layerX等概念 ,這里有篇文章講解,你可以看看http://www.funnyhao.com/pagex-clientx-offsetx-layerx-of-those-things/

由于我們現(xiàn)在畫布直接放在頁(yè)面上左上部,padding和margin都為0,因此我們直接用clientX和clientY即可.當(dāng)用戶第一次點(diǎn)擊鼠標(biāo)時(shí),我們?cè)O(shè)置isMouseDown為true,開啟繪畫模式。

function mouseDown(e) {
    isMouseDown = true;
}

獲取了用戶點(diǎn)擊的位置后,我們?cè)诩s定的時(shí)間間隔后(1/60秒)再次獲取用戶所在的位置,并進(jìn)行更新

function loop() {
    if (isMouseDown) draw(context);//繪制鼠標(biāo)點(diǎn)擊位置
}
function mouseMove(e) {
    pos.x=e.clientX;//設(shè)置x坐標(biāo)
	pos.y=e.clientY;//設(shè)置y坐標(biāo)
	$pos_display.innerHTML='你當(dāng)前點(diǎn)擊鼠標(biāo)的位置為('+pos.x+','+pos.y+')';//更新當(dāng)前鼠標(biāo)點(diǎn)擊的位置
}

接下來我們就可以繪制了

function draw(ctx) {

            ctx.save();//保存當(dāng)前繪圖狀態(tài)
            ctx.fillStyle = DEFAULT_BRUSH_COLOR;//設(shè)置填充的背景顏色
		  	ctx.lineWidth =DEFAULT_BRUSH_SIZE;  //設(shè)置畫筆的大小
		    ctx.lineCap = "round"; //設(shè)置線條,讓線條邊緣更圓滑
            ctx.beginPath();
			ctx.arc(pos.x,pos.y,DEFAULT_BRUSH_SIZE,0,Math.PI * 2,true);
			/****
			*context.arc(x, y, radius, startAngle, endAngle, anticlockwise)
			*參數(shù) x,y表示圓心
			*radius半徑
			*startAngle起始弧度
			*endAngle終止弧度
			*anticlockwise是否為逆時(shí)針方向
			***/
            ctx.fill();//填充繪畫路徑
            ctx.restore();//恢復(fù)繪畫狀態(tài)

}

似乎這樣的大功告成了??催@里的演示代碼:DEMO1(http://runjs.cn/detail/gxeeyocw)

當(dāng)我們畫畫時(shí),如果繪制筆移動(dòng)的較快的時(shí)候,就會(huì)發(fā)現(xiàn)出現(xiàn)了斷斷續(xù)續(xù)的情況,這是怎么回事呢?原來我們只設(shè)置了一個(gè)點(diǎn)每過1/60秒就更新一下位置,當(dāng)我們繪圖時(shí)如果畫筆移動(dòng)的速度夠快時(shí)繪制的不夠密集,繪制的點(diǎn)久不能連接起來,從而引起斷續(xù)的現(xiàn)象。

可能會(huì)有些人說可以設(shè)置時(shí)間間隔更小,比如設(shè)置為1/1000秒,也就是將頁(yè)面中的代碼

  setInterval(loop, 1000 / 60);

改為

  setInterval(loop, 1000 / 1000);

甚至無窮小,這樣不就解決了嗎。但是相信很多人都不會(huì)推薦這樣的方法,因?yàn)檫@不僅僅會(huì)影響到頁(yè)面的效率,而且也沒有從根本上解決問題,setinterval調(diào)用間隔的時(shí)間往往會(huì)有諸多限制,所以這樣的方法是行不通的。

要讓線連貫起來最簡(jiǎn)單的方法:那就用線連起來吧。(旁白:廢話,⊙﹏⊙b汗)我們知道兩點(diǎn)確定一條直線,所以只要我們確定兩個(gè)點(diǎn)的坐標(biāo)即可。亦即每個(gè)時(shí)間間隔單位,我們獲取一次當(dāng)前點(diǎn)的坐標(biāo)就好了。然后使用canvas的moveTo函數(shù)移動(dòng)下一個(gè)點(diǎn),記錄當(dāng)前點(diǎn)坐標(biāo)和上一個(gè)點(diǎn)的坐標(biāo),并使用canvas的lineTo函數(shù)將線連起來,然后不要忘了用stroke函數(shù)繪制出來,具體看這里的代碼:DEMO2(http://runjs.cn/detail/r52qaltg)。

我們通過表格比較一下這兩種方案的區(qū)別:

表格中明顯看出方案一都是孤立的點(diǎn),而方案二每個(gè)點(diǎn)都會(huì)有兩種狀態(tài),將這兩種狀態(tài)下的點(diǎn)連起來就會(huì)形成銜接的較好的效果。

因?yàn)榛A(chǔ)的內(nèi)容在上面已經(jīng)講述了,所以在這里我也不重復(fù)了,需要注意的是當(dāng)前點(diǎn)與上一個(gè)點(diǎn)重復(fù)時(shí)需要做一下處理,否則頁(yè)面無法繪制出來。

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();
}

為了方便講解,我這里采用的都是面向過程的方法,在比較大的應(yīng)用中,我們要盡可能采用面向?qū)ο蟮姆椒?,好處是不言而喻的,不僅能讓代碼條理清晰,更有較好的擴(kuò)展性,方便二次開發(fā)和模塊復(fù)用。使用面向?qū)ο蠓椒ǖ拇a請(qǐng)查看這里(這里會(huì)使用了point函數(shù)類,覆蓋了set和update等方法)請(qǐng)查看DEMO3_1(http://runjs.cn/detail/gvfyrswu)。

研究技術(shù)的時(shí)候,我們需要舉一反三,顯然現(xiàn)在的方法還是不夠完善。能不能將所有的點(diǎn)都記錄下來,因?yàn)槊總€(gè)時(shí)間間隔單位,都會(huì)損失掉很多的點(diǎn),為了讓畫出來的圖更加圓滑,我們要將所有的點(diǎn)都記錄下來,并且效率又能得到優(yōu)化,我在這里提出一個(gè)解決方案。

用數(shù)組記錄下所有的路徑,然后用堆棧的push方法將點(diǎn)添加到數(shù)組中,為了達(dá)到更好的效率,我們可以采用一維數(shù)組,分別用兩個(gè)數(shù)組記錄橫坐標(biāo)和縱坐標(biāo),具體的實(shí)現(xiàn)我就不貼代碼了,大家有余力的話可以當(dāng)作一個(gè)小小的作業(yè),參照我的這個(gè)頁(yè)面例子自己編寫代碼實(shí)現(xiàn),頁(yè)面中會(huì)有代碼注釋的。

我們實(shí)現(xiàn)了繪制功能,我們還需要對(duì)繪制的圖片進(jìn)行擦除。不要嘗試采用transparent或者rgba(x,x,x,0)這樣的顏色值繪制,因?yàn)檫@樣頁(yè)面便不會(huì)繪制出任何東西,最實(shí)用的方法就是繪制背景顏色,如果背景是圖片,那就重繪背景圖片,然后就原來的內(nèi)容的其他部分繪制到畫布中。具體查看demo3_2(http://runjs.cn/detail/jywf4qv1

那如果我們要實(shí)現(xiàn)蠟筆的效果。要怎么處理呢,如果我們將蠟筆畫放大后看就會(huì)知道那是一些很小的分散顆粒狀大小的粒子,這樣我們就有了思路了。我們還是沿用DEMO3的例子,在其基礎(chǔ)上進(jìn)行開發(fā),需要注意的一點(diǎn)是粒子的分布問題,如何才能將粒子均勻的分布呢,不知道大伙們這么久沒學(xué)數(shù)學(xué)是不是都將知識(shí)還給老師了。這里我們會(huì)用到一些基本的數(shù)學(xué)知識(shí), 具體思路請(qǐng)看下圖,

請(qǐng)參照源代碼

  draw: function(ctx) {
            var v = this.subtract(this._latest);//當(dāng)前點(diǎn)與下一個(gè)點(diǎn)的距離的橫縱坐標(biāo)
            var s = Math.ceil(this.size / 2);		//算出粒子的單位長(zhǎng)度
            var stepNum = Math.floor(v.length() / s) + 1;	//算出步長(zhǎng)  v.length()為斜線長(zhǎng)度
            v.normalize(s);//當(dāng)前點(diǎn)與下一個(gè)點(diǎn)的

            var sep = 1.5; // 分割數(shù)  控制畫筆的濃密程度  關(guān)鍵所在
			//粒子的大小 根據(jù)畫筆描繪的速度(畫筆的停留時(shí)間)進(jìn)行調(diào)整
            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='你上一點(diǎn)鼠標(biāo)的位置為('+this.x+','+this.y+').
你當(dāng)前鼠標(biāo)的位置為('+this._latest.x+','+this._latest.y+')';//更新當(dāng)前鼠標(biāo)點(diǎn)擊的位置
            ctx.beginPath();
			if(wmode=="擦除模式"){
				ctx.strokeStyle=brush_color;
				ctx.lineWidth =DEFAULT_BRUSH_SIZE;
				ctx.lineCap = "round";
				ctx.beginPath();
				p = this._latest;//獲取下一個(gè)點(diǎn)位置
				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();
        }
  });

進(jìn)行分析比較。思路的重點(diǎn)是在一定間隔后對(duì)粒子進(jìn)行隨機(jī)分散排布,并能處理在畫筆移動(dòng)的比較快的時(shí)候的的繪制問題。為了得到更好的展示效果,我們一般還會(huì)控制透明度進(jìn)行調(diào)整。
處理完這些后,我們?nèi)绻矚g這樣的圖片,還可以使用圖片導(dǎo)出功能,方法也挺簡(jiǎn)單。去掉監(jiān)聽事件,使用canvas的toDataURL內(nèi)置函數(shù),然后展示到新打開的窗口中。
我們就可以運(yùn)行一下源代碼看看帶擦出功能和圖片導(dǎo)出功能的實(shí)際效果如何:

溫馨提示:在鍵盤上輸入p鍵可以導(dǎo)出圖片,圖片導(dǎo)出功能由于在新窗口中打開,請(qǐng)使用全屏預(yù)覽模式并允許窗口彈出。

其實(shí)我們還可以有更多變化,只要你去構(gòu)想,去思考。很多時(shí)候你都要去嘗試,往往多次嘗試才會(huì)有新的ideas。接下來我們可是做出以下特殊的畫筆,比如說鋼筆效果,邊緣會(huì)比較筆觸比較重的。鋼筆效果需要將畫筆尺寸調(diào)小,減弱抽絲的效果,并且邊緣的鋸齒會(huì)比較明顯,所以需要做一下陰影模糊處理,讓其過渡更平滑。

具體源碼看這里:demo5(http://runjs.cn/detail/df5u6cb5

之前曾在這里見過一個(gè)毛筆畫網(wǎng)站,所以也模擬了一下毛筆的效果,毛筆的特點(diǎn)是筆觸比較大,當(dāng)收筆較快時(shí)邊緣要凹凸不平,筆尖寫字鋒棱易出。當(dāng)收筆有停頓時(shí)則會(huì)圓潤(rùn)而渾厚。有個(gè)類似的線上應(yīng)用,大家可以去研究研究:http://www.theshodo.com/Write。

根據(jù)這些特點(diǎn),我給大家提供一個(gè)DEMO源碼,是在鋼筆效果的基礎(chǔ)上做些小小的調(diào)整的。

如果大家還覺得這樣的效果是否還可以添加點(diǎn)特效什么的,對(duì),可以做雜點(diǎn)斑點(diǎn)的效果,還有墨水過多而流下的效果。

具體可以參看DEMO6(http://runjs.cn/detail/ully3puv)和DEMO7。

源碼我就不進(jìn)行分析了,留待大家自己去研究,將畫筆顏色調(diào)為黑色就差不多可以模擬出毛筆的效果了。在我看來,技術(shù)的專研不是一味的讓別人教你,而是讓你自己去領(lǐng)悟的。只有那樣你才真正學(xué)到技術(shù),領(lǐng)略到不一樣的樂趣。

說些設(shè)計(jì)以外的東西,設(shè)計(jì)、編程都需要有自己的思想和靈魂,真正讓別人也能感受到你的思路,而這一切都需要磨練,需要舉一反三。不應(yīng)該滿足于現(xiàn)狀,我在這里只列舉了其中一些效果,我相信還有很多效果可以實(shí)現(xiàn),比如說類似于這樣的噴霧效果,鉛筆字效果,藝術(shù)畫效果,等等。既然說HTML5創(chuàng)意畫板,那就要嘗試脫離這個(gè)畫板的束縛,學(xué)到更多的東西,比如說你可以用這個(gè)畫板做什么,可以做一個(gè)記事本、涂鴉工具、處理和分享圖片,個(gè)性簽名,你還可以做一些小游戲,涂鴉類的游戲,你畫我猜(需要采用websocket實(shí)現(xiàn)服務(wù)器端雙向通信)等等,甚至可以做一些canvas動(dòng)畫,這些基礎(chǔ)上做些修改和調(diào)整,完全是可以實(shí)現(xiàn)的。有了目標(biāo)和思路,那就沿著這個(gè)方向去學(xué)習(xí),我相信你一定會(huì)有所收獲的。

這次的HTML5繪圖教程就到這里,大家還可以嘗試為此添加更多的個(gè)性化的功能,同時(shí)歡迎大家留言提問或者提出批評(píng)建議。

源碼下載:
runJS: http://runjs.cn/detail/spxs2kxq
微盤:http://vdisk.weibo.com/s/otSnZ
百度網(wǎng)盤:http://pan.baidu.com/share/link?shareid=194573&uk=3744164386(github實(shí)在太高端了,不符合國(guó)情,所以無視了)

擴(kuò)展閱讀:
在線毛筆畫板:http://www.theshodo.com/Write
來自deviants的在線畫板:http://sta.sh/muro/
如果想查看之前的FLASH版本,可以點(diǎn)擊這里http://www.inzrb.com/blog/?page_id=211

來源:微博UDC

更多精彩內(nèi)容,請(qǐng)關(guān)注人人都是產(chǎn)品經(jīng)理微信公眾號(hào)或下載App
評(píng)論
評(píng)論請(qǐng)登錄
  1. 請(qǐng)問DEMO6如何改成可以在移動(dòng)端上玩,我試過把mousedown,mouseup,mousemove,改成touchstart,touchmove,touchend也不行 ??

    來自廣東 回復(fù)