這篇教程將帶您從基礎(chǔ)知識(在畫布上繪制原始鼠標跟隨線),一直到那些和諧畫筆,以及復雜的曲線和筆觸,從邊緣跨越并卷曲成奇怪美麗的結(jié)構(gòu)。
下面我將介紹不同的刷子代碼實現(xiàn),以便您可以自己了解如何在畫布上實現(xiàn)自由繪圖。
當然,在繼續(xù)之前,對HTML5畫布有一定的了解是必要的。
基本
所以讓我們從一個非?;镜姆椒ㄩ_始。
簡單的鉛筆
我們在畫布上觀察“mousedown”,“mousemove”和“mouseup”事件。
在“mousedown”上,我們將指針移動到單擊的坐標(ctx.moveTo)。在“mousemove”上,我們畫一條線到鼠標的新坐標(ctx.lineTo)。最后,在“mouseup”上,我們通過將isDrawingflag 設(shè)置為false來結(jié)束繪圖。
此標志用于防止在畫布上移動鼠標時進行繪制(沒有單擊畫布的情況)。您可以通過在“onmousedown”中分配“onmousemove”事件處理程序(然后在“onmouseup”中刪除它)來避免標記,但flag是一個簡單的解決方案,也可以正常工作。
設(shè)置光滑連接
現(xiàn)在,我們可以通過改變值來控制線的厚度ctx.lineWidth。但是,粗線造成鋸齒狀的邊緣。
這通常發(fā)生在“急轉(zhuǎn)彎”的位置,并且可以通過設(shè)置ctx.lineJoin和ctx.lineCap成"round" 來解決(有關(guān)這些如何影響渲染的示例,請參閱MDN )。
借助陰影使邊緣平滑
現(xiàn)在線條沒有在拐角周圍出現(xiàn)鋸齒。但它們的邊緣也不是很光滑。這是因為這里沒有抗鋸齒化(在canvas上控制抗鋸齒從未如此簡單)。那么我們?nèi)绾涡Х履?
有種方法就是借助陰影使邊緣平滑。
我們添加的內(nèi)容都是ctx.shadowBlur和ctx.shadowColor。邊緣現(xiàn)在肯定更平滑,因為線條被陰影包圍。但還是有一點問題:你會注意到線條在開始時比較薄和模糊,但是隨著繪制的內(nèi)容增加逐漸變得更厚更堅固。
這是一個有趣的效果,但也許完全不是我們想要的。那么為什么會這樣呢?
原來這是由于陰影相互重疊,每增加一個點都重新stroke了,這樣當前筆劃的陰影與前一筆劃的陰影重疊,后者與前一筆劃的陰影重疊,依此類推。陰影越重疊,模糊越少,線越粗。那么我們?nèi)绾谓鉀Q這個問題呢?
基于點的方法
避免這類問題的一種方法是始終stroke一次。我們可以在一個數(shù)組中引入一個容器存儲鼠標點,并且一次性stroke它們。而不是盲目地在每一次接收到鼠標mousemove的時候都去stroke。
如您所見,它看起來與第一個示例效果相同?,F(xiàn)在我們可以嘗試在此基礎(chǔ)上添加陰影。注意它在整個路徑中是如何保持均勻的。
基于點的陰影
邊緣平滑與徑向漸變
另一種平滑的途徑是使用徑向漸變。漸變允許更均勻的顏色分布,不像陰影通常比“平滑”更模糊。
但是,正如您所看到的,stroke使用漸變還有其他問題。請注意我們?nèi)绾卧诿總€鼠標移動點上使用圓形漸變填充區(qū)域的。當快速移動鼠標時,我們得到一系列斷開連接的圓,而不是具有光滑邊緣的直線。
解決此問題的一種方法是在兩點間隔距離大時生成額外的點來填補斷開的空間。
最后這是一條相當平滑的曲線!
您可能會注意到上面示例中的一個小變化。我們只存儲上一個點,而不是存儲路徑的所有點。我們總是stroke從上一個點到當前的一個點。
有了上一個點那我們需要計算它與當前點之間的距離。如果距離太大,我們就在其中填充更多。這種方法的好處是我們不用存儲整個points陣列,這樣就減少了內(nèi)存的使用!
貝塞爾曲線
我遇到的一個有趣的概念是使用bezier線而不是直線。這允許自由繪制路徑的曲線更自然更平滑。
這個想法的實現(xiàn)是使用quadraticCurveTo來替換straight-line,用兩個連續(xù)點之間的中間點作為二次貝塞爾曲線控制點。來試試吧:
到現(xiàn)在你已經(jīng)知道:繪圖和平滑曲線的一些基本知識。從簡單的連線到更復雜的基于貝塞爾的曲線。讓我們繼續(xù)前進發(fā)現(xiàn)更有趣的事情吧。
刷子,毛皮,筆
刷子
現(xiàn)實的畫筆工具箱中的一個技巧是簡單地用圖像stroke。我在 blog post by Andrew Trice.遇到了這種技巧。這種想法是使用一小部分圖像作為畫筆去stroke。這開啟了很多可能性。
根據(jù)圖像,我們可以實現(xiàn)不同的畫筆樣式。在這種情況下,它類似于厚刷。
毛皮(旋轉(zhuǎn)畫布)
跟前面一樣用繪制圖片方式填充路徑,但每次繪制時生成隨機的角度值旋轉(zhuǎn)一下畫布。
如果我們這樣做,我們可以得到類似毛皮(或花環(huán))一樣的東西。
筆(變化寬度)
當談到模擬鋼筆寫字時,一個很好的解決方案是簡單地隨機變化路徑的線寬!我們?nèi)匀皇墙Y(jié)合moveTo+ lineTo的方式,但每次去更改“l(fā)ineWidth”繪制出來的線是什么樣子呢?這是它的外觀:
要記住的一件事是,為了使繪圖看起來真實,隨機值差別不應(yīng)該太大。
筆2(多線條)
另一模擬鋼筆的實現(xiàn)是通過多筆觸完成的。我們在點與點之間連線stroke多次,而不是僅僅一次。
但是我們并不是在同一個坐標位置重復畫線,因為這不會改變?nèi)魏问虑椤?
相反,我們在原始(圖片上的綠點)位置旁邊取幾個隨機點(圖片上的藍點),然后從那里開始。因此,不是一行,而是在原始線旁邊增加了兩天“凌亂”的線條。完美的鋼筆模擬!
厚刷
你可以用這種“多筆觸”技術(shù)做很多事情。我鼓勵大家嘗試自己的想法。
這里有一個例子,如果我們增加線條寬度并稍加修正第條線的偏移量,我們可以模擬一個厚刷子。邊緣上的那些空白點使其看起來很逼真。
“切片”筆觸
多筆觸的方法中如果使用均勻的小點的偏移量,我們可以得到類似于切片的東西。
這一次,不使用圖像。
簡單的方法讓路徑呈現(xiàn)偏斜效果。
帶有透明度的“切片”筆觸
如果我們使用與前面示例中相同的畫筆,并給每次筆觸依次變小的透明度,我們會得到一個像這樣的有趣效果。
多行線條
筆直線條用夠了。我們是否可以將相同的技術(shù)應(yīng)用于基于貝塞爾曲線的路徑呢?當然。我們只需要在偏離原始點一定距離的位置繪制每條曲線。這就是它的樣子:
帶有透明度的多行線條
我們也可以使用相同的“褪色”技術(shù),讓其中每條線的不透明度依次降低。這使得這些線條看起來更加簡潔優(yōu)雅。
與直筆觸一樣,使用貝塞爾曲線的可能性是無盡的。
印記樣
基本概念
在我們學會了如何劃線和曲線后,實現(xiàn)印章刷不是更簡單!我們所需要的只是在每次鼠標移動時,在鼠標的位置繪制某種形狀。而已。這是一個用紅色圓圈標記的例子。
追蹤效果
您可以看到中間點的相同問題,我們可以使用相同的預(yù)填充技術(shù)解決這些問題。郵票的預(yù)填充往往會產(chǎn)生非常有趣的線索或管狀效果。您可以通過在最后一個點和當前點之間預(yù)先填充每個點的間隔來控制管的密度。
隨機半徑,不透明度
當然,我們總是可以調(diào)情,以某種方式改變每個郵票。例如,第一個例子中隨機變化的半徑和不透明度就是這個。
形狀
當涉及到那種郵票時,你可以盡可能地去 - 從我們剛看到的基本形狀(例如圓圈)到由數(shù)百或數(shù)千條曲線組成的更復雜的路徑。這里唯一的限制因素是性能。這是一個用簡單的五角星沖壓的例子。
旋轉(zhuǎn)形狀
這是同一顆星,但每次移動都會隨機旋轉(zhuǎn),以獲得更自然的感覺。
隨機化一切!
哎呀,讓我們放大一點 - 尺寸,角度,不透明度,顏色,厚度!現(xiàn)在不是那么有趣。
彩色像素
我們也不僅限于形狀。一種選擇是直接操縱鼠標點周圍的像素。一個簡單的例子就是隨機化它們的顏色和位置。
基于圖案的畫筆
現(xiàn)在我們已經(jīng)過了撫摸和沖壓,讓我們來看看一個完全不同的野獸模式。我們可以使用canvas’隨時createPattern填充路徑。這會產(chǎn)生一些非常有趣的效果。我們來看一個簡單的點圖案。
圓點圖案
注意這里是如何創(chuàng)建模式的。我們將實例化迷你畫布,在其上繪制圓圈,然后將該畫布用作主畫布上的圖案!我們可能也習慣使用普通圖像,但使用canvas的美妙之處在于我們可以通過編程方式訪問它,并且無論如何我都可以更改它。這意味著我們可以創(chuàng)建動態(tài)模式,例如改變模式中圓的顏色,半徑等。這也意味著我們可以更快更容易地嘗試模式。
線條圖案
根據(jù)前面的示例,您應(yīng)該能夠創(chuàng)建類似的東西。讓我們說一個水平線條圖案。
雙色線條圖案
…或垂直線條,具有互換的顏色。
彩虹
…甚至是多條不同顏色的線條。一切皆有可能。想想一些模式,并嘗試在迷你畫布上創(chuàng)建它。剩下的就是照顧createPattern和填充路徑。
圖片
最后,這是一個使用基于圖像的模式與貝塞爾曲線路徑一起使用的示例。這里改變的是我們將圖像對象傳遞給createPattern(然后將結(jié)果模式分配給strokeStyle)。
噴霧
現(xiàn)在goold-old噴刷怎么樣?我們可以通過幾種方式實現(xiàn)它。其中之一是用顏色簡單地填充鼠標點周圍的區(qū)域(像素)。面積(半徑)越大,噴霧越厚。我們填充的像素越多,它就越密集。
基于時間的噴霧
您可能會注意到,之前的方法并不像真正的噴霧那樣。一個真正的噴漆面積不斷,不只是當我們移動鼠標/刷。為了實現(xiàn)這一點,我們需要在按下鼠標時以恒定間隔繪制區(qū)域。這樣,某些區(qū)域可以通過更長時間“保持噴霧”而變得更暗。
基于時間的噴霧,圓形分布
前面的例子更現(xiàn)實,但并不完全如此。真正的噴涂油漆在圓形區(qū)域上,而不是矩形。所以讓我們嘗試在圓形區(qū)域上分布像素。
好多了。
隨機點
最后,我們還能做些什么來使噴霧更逼真嗎?當然,除了使用圖像作為印章。我們當然可以使涂料偶爾散布,就像在現(xiàn)實生活中一樣。如果我們改變每個繪制像素的不透明度,我們會得到非常相似的效果。
鄰居點連接
連接鄰居點的概念由zefrank的Scribbler和doob先生的Harmony推廣。如果你還記得Harmony畫筆,如粗略,陰影,鉻色 - 這就是我所說的效果。
這個想法是:在已經(jīng)繪制的路徑的附近點之間添加額外的筆劃。這通常會產(chǎn)生草圖,網(wǎng)頁或某種陰影的效果; 額外的筆劃在小的“彎曲”區(qū)域增加了較暗點的錯覺。
全點連接
一個天真的方法是采用我們的第一個簡單的基于點的畫筆的例子,并添加額外的撫摸。對于沿路徑的每個點,我們將朝著路徑上的前一個點沖程:
你可以開始看到類似Harmony畫筆的東西,但它并不完全相同。通過降低額外筆畫的不透明度(即對比度)可以使其更加逼真和陰影。但要完全重建效果,我們需要遵循不同的算法。
鄰居點
負責“附近”撫摸的部分是這樣的:
復制代碼
1 var lastPoint = points[points.length-1];
2
3 for (var i = 0, len = points.length; i < len; i++) {
4 dx = points[i].x - lastPoint.x;
5 dy = points[i].y - lastPoint.y;
6 d = dx * dx + dy * dy;
7
8 if (d < 1000) {
9 ctx.beginPath();
10 ctx.strokeStyle = ‘rgba(0,0,0,0.3)’;
11 ctx.moveTo(lastPoint.x + (dx * 0.2), lastPoint.y + (dy * 0.2));
12 ctx.lineTo(points[i].x - (dx * 0.2), points[i].y - (dy * 0.2));
13 ctx.stroke();
14 }
15 }
復制代碼
這里發(fā)生了什么?看起來很瘋狂 我花了一些時間來理解,但這個概念非常簡單!
繪制線時,我們檢查已經(jīng)繪制的路徑的整個距離,將所有點與當前(最后一個)點進行比較。如果該點在d < 1000最后一個點的某個接近度()中,我們移動指向它的指針并從那里劃線到當前點。dx * 0.2并dy * 0.2給那些額外的筆畫稍微偏移。
而已。簡單的想法,強大的效果。
毛皮通過鄰居點
這種技術(shù)的一個有趣的轉(zhuǎn)折 - 在和諧中看到 - 是創(chuàng)造皮毛效果。不是向附近點(從最后一點)劃動,而是向相反方向行程。稍微偏移,它會在某些(近距離)區(qū)域產(chǎn)生毛茸茸的筆觸。
調(diào)查Harmony畫筆后不久,我偶然發(fā)現(xiàn)了Luká?Tvrdy的精彩博客文章,很好地解釋了鄰居點技術(shù)的一些變化。他描述了不同參數(shù)如何影響筆畫及其產(chǎn)生的效果類型。絕對值得一試。
所以你有它 - 一些基本的以及更有趣的繪圖技術(shù)。我們這里只是劃了一個表面。有無限的可能性來定制任何一個畫筆,創(chuàng)造更多令人興奮的效果。改變不透明度或顏色,寬度或偏移,引入隨機因素,并產(chǎn)生全新的效果。