WFX-Maniac

0%

何謂路徑?

要介紹路徑繪圖相關的api之前,必須要先理解什麼叫做『路徑』。
有學過電腦繪圖軟體,例如Adobe Photoshop, Adobe Illustrator的同學可能對『路徑』這個詞相當的熟悉,同時也可能可以更快速掌握2D渲染環境路徑繪圖的概念,但是考量到大多數人都沒有美術學經歷背景,所以這邊還是簡單做點說明~

路徑是使用繪圖工具建立的任意形狀的曲線,用它可勾勒出物體的輪廓,所以也稱之為輪廓線。 為了滿足繪圖的需要,路徑又分為開放路徑和封閉路徑。 –維基百科

如果要白話一點的解釋『路徑(Path)』這個概念,可以想像成他是由一條透明的曲線所圈出來的一塊(非)開放區域,在canvas中我們可以利用(接下來會提到的)上色填充相關api為已經成形的路徑設定填充色(fill)/邊框色(stroke)。

有學過SVG相關知識的同學應該馬上就會發現這其實就跟SVG的Path 屬性是一樣的東西~沒錯,路徑(Path)其實是計算機繪圖領域的概念,並不是Canvas獨有的。

(圖片說明:在上圖我們可以看到我們必須要先有一個葉子形狀的Path,然後接著才可以對這個Path施加Fill和Stroke)

接下來我們要藉由實作的方式來加速學習api的使用方式,藉由實際操作API來畫一條線/一個圓/一個不規則形狀來加深對API的認識。

在開始之前,有一個特別需要注意的地方,那就是『繪製路徑』這個行為過程其實有點類似於用筆尖在紙面上作畫。
這個『筆尖』會是一個實際存在的座標(但是你看不到),打個比方:假設我們現在畫了一段由A點畫向B點的路徑,那麼『筆尖』最後也會停在B上面。
這時候要注意,如果沒有利用API去移動筆尖,而是直接在別的地方畫了一個新的形狀,那麼先被畫出來的形狀和後被畫出來的形狀就會產生多餘的連線。

來畫一條線看看吧!

See the Pen canvas畫線 by mizok_contest (@mizok_contest) on CodePen.

起手式!

任何的路徑在開始畫之前,最好都要先使用ctx.beginPath()來宣告『嘿,我要開始畫路徑囉』;
然後在結束路徑繪製時,也最好使用ctx.closePath() 來宣告結束路徑的繪製;

一般來說如果不宣告結束,那麼路徑就會一直存續下去,這樣就沒有辦法畫出個別獨立的圖形(例如個別獨立顏色不同的方塊)

另外一提,ctx.fill() (填充顏色的api) 本身自帶closePath的效果,所以如果先執行了fill(),則可以不用額外宣告結束路徑繪製。

接著來畫一個圓!

1
2
3
4
5
6
7
8
// API 用法
void ctx.arc(x, y, radius, startAngle, endAngle [, counterclockwise]);
// x: 圓心X座標
// y: 圓心Y座標
// radius : 半徑
// startAngle: 起始角度=> 記得是順時針為正喔(而且必須要是徑度量)
// endEngle: 結束角度=> 記得是順時針為正喔(而且必須要是徑度量)
// counterClockwise : 是否以逆時針方向作畫

這邊我提出了一個錯誤的範例,和一個正確的範例,讓大家可以參考一下錯誤的原因和正確的寫法。

See the Pen canvas畫圓 by mizok_contest (@mizok_contest) on CodePen.

畫一個不規則形狀!

這邊我們利用畫二次曲線的API來畫一個由三條曲線構成的形狀,接著填充並且賦予框線。
這邊可以稍微理解一下Canvas 的API ~ ctx.curveTo是怎麼定義二次曲線的參數需求。
簡單來說這個api把一段二次曲線看作是一個由三個點所構成的曲線,三個點分別是:

  • 開始畫線時筆尖的座標(第一端點)
  • 曲線結束的端點座標(第二端點)
  • 兩個端點沿著曲線拉出的切線所形成的交點,是為『控制點 cp』
1
2
3
4
5
void ctx.quadraticCurveTo(cpx, cpy, x, y);
// cpx: 曲線控制點X座標
// cpy: 曲線控制點Y座標
// x: 曲線結束點X座標
// y: 曲線結束點Y座標

See the Pen canvas畫不規則形狀 by mizok_contest (@mizok_contest) on CodePen.

Canvas Property的紀錄(save)與復原(restore)

我們在前面的三個範例都有去調整過渲染環境當前的fillStyle和 strokeStyle 來改變填充色和邊框的顏色。
(有電腦繪圖經驗的同學可能很快的就注意到了–這兩個東西其實就是Illustrator的前景色和背景色吧!)
要知道,Canvas的Property在同一時間底下是只有一個唯一值的,也就是說填充色在同一瞬間只能被指定一個hex作為類似全域變數的概念,
所以如果今天有一個需求,要先畫出一條紅色的線,接著再畫出一條藍色的線,流程便會是:

  • 把fillstyle 設定成紅色
  • 畫第一條線
  • 把fillstyle 設定成藍色
  • 畫第二條線

雖然這樣的場景很單純看起來沒什麼,但是如果今天到了很複雜的狀況,例如初始顏色是透過random函數隨機決定的,而繪製過程中突然有需求回歸原本random到的那個顏色,那就會需要有能夠復原Property的需求。

上述的場景雖然可以透過把字串值存取道臨時變數來達成,但是別忘了,Canvas 的Property 遠遠不止strokeStyle 一個,如果任何一個Property都要存一個變數,想必程式碼會變得很亂。

這時我們就可以透過Canvas 的原生API,也就是ctx.save()與 ctx.restore() (存檔與讀檔) 來達成上述需求。

這邊我提出了一個範例,範例中我先random 了一個hex色碼來作為初始顏色,畫一條線,接著把顏色改為藍色再畫一條線,最後我則是回歸原本ramdom到的顏色畫第三條線。

See the Pen canvas畫不規則形狀 by mizok_contest (@mizok_contest) on CodePen.

路徑繪製/上色 - 小結

實際上,canvas 關於繪製路徑的api還遠不止上述提到的這幾種。
例如曲線還有 ctx.bezierCurveTo()(貝茲曲線), 設定邊框粗細可以用 ctx.lineWidth, 設定端點類型可以用ctx.lineJoin…,etc

這些api/property 如果要在文章中一一介紹其實多少會變得有點流水帳,所以我比較傾向讓大家自己去搜尋自己需要的api

推薦在查詢api 的時候可以多使用MDN~ MDN 會是學習Canvas基礎的好幫手。

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D

進入2D渲染的世界

我們在前面的章節有提到,任何Canvas的相關程序,起手式必定是先取得渲染環境,所以2D渲染程序的第一步當然也就是先取得2D得渲染環境

1
2
let cvs = document.querySelector('canvas');
let ctx = cvs.getContext('2d'); //這個ctx就是2d模式的渲染環境

2D渲染環境下的座標系

在國高中上數學課的提到座標系時,我們會很習慣的去畫一個X軸向右為正,Y軸向上為正的座標軸,像這樣的座標系也就是所謂的『卡氏座標』,他是一種遵循右手定則的坐標系。

我猜大家應該都已經把過去學過的數學/理化都還給老師了,所以這邊我們稍微複習一下『右手定則』這個概念:

所謂的『右手定則』指的是:

把右手伸出來,掌心朝自己,四指的方向會跟上圖Y軸同向,而大拇指方向則跟X軸同向,同時,你不管怎麼旋轉這張圖,你都可以用你的右手(手心必須朝自己)四指去比出Y軸方向/用大拇指比出X軸方向,這就是所謂的『右手定則座標系』

但是在Canvas 2D渲染環境的世界中,它所採取的座標系並不是『右手定則座標系』,而是『左手定則座標系』。

『左手定則座標系』其實就是『右手定則座標系』的鏡像版~也就是說,我們其實可以直接把上面這張圖水平翻轉,它就直接成為了『左手定則座標系』,這時候你會發現,像剛剛同樣的操作,會變成只有左手才做得到。

接著是重點,『左手定則座標系』和 『右手定則座標系』雖然看上去好像很相像,但最重要的差別就在於:

右手定則的座標系,逆時針是正的旋轉方向,但是左手定則的座標系,順時針才是正的旋轉方向

這點必須要熟記。

2D渲染環境下常見的API與Property

實際上,比起我們之後會提到的webgl環境,2D渲染環境的API在使用上都相當的直觀而且單純(當然,這也同時意味著2D渲染的難點不在於對API的理解,而是在於運算的過程)

隨然說2D渲染環境的api數量繁多,但是為了避免一條一條介紹有點流水帳,

在這邊我把常見的API/Property劃分成三種類型:

  • 路徑/上色相關

  • 變形相關

  • 像素操作相關

    在接下來的章節中我會根據上述的三種類型,一一介紹三種類型中常用的api和這些api相關的基礎常識

之前我們有提到過,canvas其實本身可以看做一群像素形成的2維陣列,而Canvas的圖像變形,其實就是對canvas自身做的一種陣列運算。

高中讀理組的同學可能還記得數學課學過的『旋轉矩陣』、『平移矩陣』…之類的東西~沒有錯,其實Canvas的變形API背後的原理就是矩陣運算~而第一次聽到這些名詞的人也不用太擔心如何理解這些數理常識,因為在2D渲染環境的case中,其實光靠API就已經可以處理絕大部分的變形問題。

接下來我們就直接看看變形相關的API都有哪些~

Rotate/Translate/scale

之所以把這三個放在一起介紹,是因為這三個API常常會一同使用。

有些人可能會想像canvas的旋轉應該就是旋轉當前畫好的圖像,但是實際上不是的。
Canvas的旋轉是固定以座標軸原點(0,0)為旋轉中心轉動整張canvas。

就像MDN上面的這張圖片一樣

img

到這邊一定會有人問~那如果說我需要以某個座標作為旋轉中心, 要怎麼辦?
這時候就會用到ctx.translate。ctx.translate的用途是把座標軸中心移動到指定新座標,這樣我們就可以透過移動座標軸原點來滿足移動旋轉中心的需求

img

而最後scale其實就跟rotate差不多,比較不同的是scale可以輸入兩個參數,一個參數是for X軸,另一個for Y軸(分別是兩個方向的縮放變形),而當然因為縮放也同樣會有所謂的”變形中心”,變形中心也就跟rotate一樣固定默認為座標軸原點,所以要移動變形中心也是必須要依賴ctx.translate

Transform/setTransform/getTransform

之所以把Transform系列的API 獨立出來講,是因為Transform的參數稍多,一條一條拉出來講解比較清楚些。
而且因為這個API會牽涉到一點點矩陣運算的常識,所以我想也可以稍微提到點這部分背後的運算邏輯(其實還蠻有趣的)~

由於這邊我們不會另外講解一般矩陣運算的做法,高中沒有學過或已經還給老師的人可以看這邊

ctx.transform() 的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void ctx.transform(a, b, c, d, e, f); //一共會有六個參數

//a (m11): 水平scale參數, 最小值為1

//b (m12): Y軸傾斜參數(skewY) , 最小值為0

//c (m21): X軸傾斜參數(skewX) , 最小值為0

//d (m22): 垂直scale參數 , 最小值為1

//e (dx): X軸平移參數 , 最小值為0

//f (dy): Y軸平移參數 , 最小值為0

在這邊我們可以看到這些m11、m12..,etc.這些奇怪的代號,這邊m11,m12,m21,m22指的是當前變形矩陣(Current Transform Matrix, 簡稱CTM)的四個子項名稱(例如m11意思就是CTM的第一行第一列的元素)。

所謂的當前變形矩陣(CTM)是一個3x3矩陣,用途是用來表示canvas元素當前的變形狀態,而當我們每次去使用ctx.scale/ctx.translate/ctx.transform/ctx.rotate 最終都會導致CTM產生變化。

仔細觀察上面a,b,c,d,e,f 這幾個參數,會發現一個有趣的地方,那就是這些參數似乎沒有跟Rotate相關的數值(例如角度),這是因為~ Canvas的旋轉,其實是SkewX/SkewY 合併運用而產生的結果,所以這邊我們可以看到SkewX/SkewY相關的參數,但卻看不到Rotate的Degree值。

ctx.getTransform()/ctx.setTransform() 的用法

首先,這兩個方法其實就是直接去 取得/設定 CTM。
getTransform 本身並沒有參數,他會回傳當前渲染環境的CTM陣列值。

1
let storedTransform = ctx.getTransform();

setTransform 本身的參數則和transform一模一樣,但是他和transform的差別就在於setTransform是直接賦予CTM指定的新陣列值,而transform的陣列值則會透過矩陣乘積的方式累計到當前的CTM上(後面會提到)

1
void ctx.transform(a, b, c, d, e, f); //一共會有六個參數

當前變形矩陣(CTM)與變形前後向量座標的關係

這邊我們實際用矩陣來表示CTM的組成:

1
2
3
4
5
a(m11) c(m21) e(dx)

b(m12) d(m22) f(dy)

0 0 1

假設今天我們要透過改寫CTM來引導Canvas的變形,則CTM與任意一個像素(Xi,Yi)的關係就如下圖:

舊像素座標陣列 = CTM * 新像素座標陣列

img

這邊我們可以發現一個有趣的點,那就是CTM竟然是放在等號後面的,這意味著若我們已知變形前座標和CTM的陣列值,我們其實就可以透過解二元一次聯立方程式來取得變形後座標。

就像這樣:

1
2
Xin*a+Yin*c+dx = Xip
Xin*b+Yin*d+dy = Yip

當前變形矩陣(CTM)與變形的計算方法

我們前面有提到過Canvas的變形運算其實就是簡單的矩陣乘法,假設我們今天令一個canvas先後做了兩次Transform

CTM的初始值:

1
2
3
1 0 0
0 1 0
0 0 1

第一次Transform使用的a,b,c,d,e,f 值:

1
2
3
1  -0.5  30
0.5 1 10
0 0 1

第二次Transform使用的a,b,c,d,e,f 值:

1
2
3
1  -0.5  30
0.5 1 10
0 0 1

則最終CTM將會變成一二兩次Transform陣列的乘積:

1
2
3
4
5
0.75 -1  55

1 0.75 35

0 0 1

小結

雖然說大部分人可能會覺得變形只是一種稀鬆平常的電腦繪圖操作,但是我覺得它背後的數學運算相當的有趣。

除此之外,雖然這次介紹的部分可能稍微比較需要花時間理解,而且在大部分的2D渲染案例,我們也比較少會需要理解到這麼深,但是未來若是要學習webgl環境的渲染編程,陣列運算會是相當重要的一環。

所以還是老話一句『學了不虧』~

Some Deeper Basics

圖像的概念

我們在前面有說到,canvas本身是透過物件模型來繪製圖像,但是我們沒有提到何謂”圖像”。

實際上圖像的概念很簡單。

大家都知道,光的三原色分別是紅/藍/綠; 一束標準的白光,可以分裂成紅/藍/綠光來看。

而圖像的實作原理,其實就是透過阻擋掉紅/藍/綠光一部分的佔比,來形成不同的顏色。

這邊我找了兩張圖片,這兩張圖片很好的說明了在計算機領域中圖像的概念(圖片來源:https://dev.to/)

一個圖像,其實可以直接被視為一大群像素的集合體,而每一個像素都具備了能分別對應三種光線(RGB),還有透明值(alpha)的篩子,這些篩子就是所謂的通道(Channel)

透明值的通道,光看字面上可能不太好理解他在數理上的意義,所以這邊我也找到了一個網頁,這個網頁上的範例可以為我們做一個適當的解釋。

https://borderleft.com/toolbox/rgba/

這是一個能夠顯示rgba顏色跟不同顏色底混合之後的樣子的網站,這邊我把底色設定為白色,也就是說合成出來的顏色就是該rgba色放置於白背景時候的樣子。

圖片中我們看到,當alpha設置為0.7時, R和G通道的0(也就是完全阻絕RG光線),他們的阻擋量(也就是255 - 0 = 255)都分別被乘以0.3而得到77這個數值,而B光線因為阻擋量是0, 所以最後在B數值上還是得到255,最終則呈現出(77,77,255)這個顏色。

從這邊我們就可以推論,alpha的數理意義其實就是同步的對三個通道的數值做比例性的消減。

從像素觀點來看Canvas

一張Canvas,其實也就是一個圖像,他也是由非常多個像素所組成,而每一個像素點上又各自都有rgba四個通道。

一張寬度100px, 高度100px的canvas,它實際上就是100*100 = 10000個像素的集合體,而同時在程序上我們則可以把它轉換成一個長度為100*100*4的陣列(也就是一共40000個channel值)

而所謂的『像素操作』,其實就是一種陣列運算,我們可以透過去疊加/轉換/削除陣列中的一部分channel值,來達到產生新圖像的效果。

Photoshop的濾鏡運算(例如彩色轉灰階),或是在社群軟體上有時候可以看到的圖片轉符號(拼字圖畫),就是典型的像素操作技術之運用。

另外,關於Canvas的寬高數值定義其實有一個有趣的點

實際上在定義一張canvas的大小時,並不是使用css來賦值,而是使用width和height這兩個attribute,直接下在canvas元素上面。

只有width和height這兩個attribute數值才能夠直接定義canvas的像素總量,而如果這時硬是要用css去擴大一張100*100的canvas為10000px*10000px,就會出現像素密度過低(也就是圖像模糊的情形)的狀況

關於Canvas的像素操作技術,我們在之後的章節會提到更多實操的範例。

Context? What The Hell Is That?

SO, 你在前面的介紹文一直提到『渲染環境』這一個詞彙,那個到底指的是什麼?

初來乍到的同學對於『渲染環境』這一個詞可能相當的陌生。
但是如果過去曾經有電腦繪圖/3D建模相關知識的人可能就能快速的領會這個詞所代表的意義。

『渲染環境』其實指的是透過canvasElement.getContext(type)這一個api所取得的一個物件。
我們其實可以把『渲染環境』想像成是繪圖軟體(例如小畫家)的概念,環境中會具備大量的工具(就像小畫家會提供畫圓形畫方形的功能),讓使用者可以應用在繪圖程序中。

『渲染環境』實際的提取方式如下:

1
2
let cvs = document.querySelector('canvas');
let ctx = cvs.getContext('2d'); //這個ctx就是2d模式的渲染環境

getContext(type) 這一個方法可以根據輸入的type字串來取得操作者所想要使用的渲染環境,而在目前html5的規制下,一共有四種渲染環境可供選擇,分別是:

  • 2d : 最基礎同時也最適合新手入門的渲染環境,具備多種2d繪圖的api
  • webgl : 是一種速度更快,功能更多,具備原生 3D API,可讓使用者能夠完全訪問渲染管道的渲染環境。(很多人可能會誤會3D圖像只能透過webgl渲染環境所提供的api來達成,但實際上不是的,這點後面的章節會講。)
  • webgl2 : 簡而言之就是webgl環境的上位版,具備更多的功能,但是相對的,對部分舊型瀏覽器/移動裝置瀏覽器的支援度也略低,算是實驗性的功能
  • bitmaprenderer : 這也同樣是一種只有少數瀏覽器有支援的實驗性功能,它的用途很單純,是用來轉移指定圖像的bitmap到指定的canvas上

在這個系列的文章中,我會針對比較常見的兩種環境(2D,webgl)做案例探討。

Start From 2D

雖然說講到網頁前端的Canvas技術,大多數人都會直接地想到像是Three.js/Webgl那樣的渲染環境/套件做出來的各種絢麗特效。
但是我個人認為,初學Canvas應該還是先從2D的基礎學起,雖然說到了webgl渲染環境時,webgl環境整體提供的API和2D環境所提供的API在本質上有很巨大的差異,但是實際上在做渲染運算、模型規劃時的重點還是有其相似之處。

在接下來的章節中,我們會先從2D的基礎講起,然後再開始慢慢地演進到一些更加複雜的案例~

Let’s Start From Scratch

本系列文章的頭幾篇我決定還是帶點基礎的東西,所以首先,我打算介紹一些不常被一般教學提到的資料來當熱身。

The HTMLCanvasElement interface provides properties and methods for manipulating the layout and presentation of <canvas> elements. The HTMLCanvasElement interface also inherits the properties and methods of the HTMLElement interface. — from MDN
The HTML <canvas> element is used to draw graphics on a web page. — from www.w3schools.com

上述是在MDN還有W3school上關於canvas 元素 的解釋。簡單來說canvas就是一種自帶複數api, 然後可以用來在網頁上做動態繪圖的元素。
簡單的介紹就到這邊,接下來是疑問時間。
有些人看到上述的解釋,可能會覺得很疑惑,例如:

那麼我們平常在用的CSS和html基礎元素,那樣子的東西難道不也是一種繪圖嗎? 差別在哪裡?

接下來我們將針對這個問題做一系列的說明。

Canvas 與 一般 Dom元素的差異一:編譯流程

通常在80%以上的web專案內容中,我們都是使用普通的Dom元素來對頁面進行版面規劃。
所謂『普通』的Dom元素指的就是 divspanulli…,etc. 而我們都知道,HTML本身是一種標記語言,
一篇完整的標記語言範本其實就有點像某種願望清單,為什麼說是願望清單呢? 那是因為瀏覽器在接收到一份HTML時,他做的事其實就是根據這份文件,上面提到的: 這份文件有多少元素? 元素個別是哪些? 元素的先後排列順序、元素的巢狀化邏輯…etc. 去生成各個元素的物件實例,然後再透過瀏覽器的渲染引擎去把這些元素渲染在畫面上。

retain mode

像這樣子先確認頁面需求模型(也就是我們提到的願望清單),然後將模型中的細節一一轉譯實現的這種行為模式,我們把它稱為Retained Mode(保留模式)。
接著一定就會有人問:

那麼是不是還有別的模式呢?

Of course.
當瀏覽器需要做這類把文字邏輯轉化成視覺的工作時(我們把這種行為稱為轉譯),會有兩種固定模式,其一是Retained Mode(保留模式), 然後再來就是與Canvas密切相關的Immediate Mode(立即模式)。

img

從上面這張圖片我們可以發現,Canvas 元素和Dom元素最大的區別就是創建模型的這一個環節。
所謂的模型指的其實就是彼此之間具有關聯的物件(Object)的群集。
一個使用Canvas 畫一個藍色方塊,和一個使用div搭配css樣式做出的紅色方塊,本質上最大的區別除了顏色之外,就是這個方塊背後物件的內容差。

舉個簡單的例子,我們在做網頁動畫的時候,常常會需要用到getBoundingClientRect 這個api 來獲取一個區塊的大小、座標等。
而我們在使用這個api的時候,一般就是先去抓取(query)這個元素,然後就可以用這個元素實例底下的api

1
2
3
4
 // ele 本身是透過selector 字串所抓到的元素實例(instance), 這個實例其實是在瀏覽器創建Dom模型的時候new出來的
let ele = document.querySelecor('div');
// getBoundingClientRect 則是ele 這個實例物件的一個方法
let divWidth = ele.getBoundingClientRect().width;

上述對大部分人來說應該都是一個稀鬆平常的操作。
但是當我們用Canvas 繪製一個方塊時,我們所創建的實例並不是透過去new 瀏覽器內建的Element 類(class)所建立出來的,而是透過我們自己定義的一個具有客製化方法的類,例如:

1
2
3
4
5
6
7
8
class block{
constructor(ctx){
this.ctx = ctx;
}
draw(x, y, width, height){
this.ctx.fillRect(x, y, width, height);
}
}

當我們用這樣的一個客製類Block去產生一個方塊的實例時,這個實例底下所有的方法,都是來自於這個客製類Block的定義(所以當然也就沒有getBoundingClientRect這麼方便的api可以用)。
但是相對的,因為產生實例的類可以自己定義,自由度也當然就跟著大幅提高,平常一些不能用基礎Dom元素做到的操作,甚至都變得有可能了(ex:繪製不規則圖形、創作複雜動畫)

Canvas 與 一般 Dom元素的差異二:使用情境

前面我們有提到, Canvas 相較於 基礎Dom元素而言,具有相對高(不只高! 而是非常高!)的自由度。
用一個簡單的例子來描述,就好比訂製手工賽車國產普通轎車 的區別,這邊絕對不是要說國產貨不好,畢竟我也沒有說訂製的手工賽車的來源是哪一家廠商XD

講到這邊應該就可以理解到兩種繪圖方式的使用情境其分野:

1. Dom元素適合的情境

  • 快速/大量生產的需求
  • 不想花費過多時間處理效能問題
  • 有大量的互動操作行為, 例如UI/UX

2. Canvas渲染適合的情境

  • 當有天馬行空的創意
  • 較少的時間壓力,且能夠自行處理圖形重繪/ 效能問題
  • 像素操作(這個後面的章節會提到)

就像我們前面提到的,以現階段來講,前端工程師80%的職業生涯中面對到的多半是第一種情境(當然也有少數人不是),
而我們需要為了這20%的狀況去學習的原因,除了要應對未來的不時之需以外,當然就是有挑戰自我創作的意義存在。

那麼話就說到這邊。
在接下來的章節,我們將會進入下一階段基礎的講解,然後再逐漸的解釋Canvas繪圖的實作,還請各位拭目以待~。

SO Just Who The Hell Am I?

大家好! 首先先介紹一下我自己, 我是Mizok :D
小弟目前人在金融相關產業擔任前端工程師,到今年為止大約有三年左右的資歷。
我在就職的這幾年一直有意識到一件事: 不知道是不是因為個人學經歷的關係(過去並不是資訊本科生畢業), 我對前端感興趣的部分也比較奇怪(?)
比起鑽研那些最新的前端框架功能/API,我發現我自己更喜歡研究瀏覽器渲染與視覺呈現方面的技術(例如物理模擬程序背後的數學運算)。
今年首次挑戰參加鐵人賽,有一部分的目標也是想把自己目前曾經研究過的東西再整理一遍,並且加以深化。
那麼話不多說,接下來就開始介紹我的參賽主題 :D

What Is Ninja?

首先必須老實說, 這個標題其實不是我想的XD, 而是來自於一本外文書
之所以取這個名字只是因為想要看起來潮潮的~然後我在這邊為這次的參賽主題畫了一個LOGO, 我覺得很酷(自己說)

平常在國外的前端社群中提到Ninja這個詞的時候~指的是一些習慣用很自由奔放(而且又讓人看不懂)的語法,來編寫專案源碼的工程師~是一個帶有負面意義的詞
而小弟我對於自己的期許當然也不是成為那樣子的開發者XD。
Ninja這個詞彙對我來說,比較近似於對某種技藝有深度理解的人。
我期許自己不只能做到理解框架/套件怎麼使用,而且還要能深入它們背後的邏輯,並且把這部分的理解運用在自己的專案上。
這對我來說就是成為一個Ninja的必經過程。

Why Canvas?

大家都知道,IE近年已經逐漸退出市場,而這個趨勢也漸漸的對前端圈子造成了不小的影響,除了各大前端框架相繼放棄支援IE以外,過去因為IE實作問題而被限制的技術也漸漸的開始浮出水面
小弟本身因為是在金融相關的專案公司就職,對於這部分真的是很有感。

這三年以來,其實不難發現在台灣的業界中,前端專案視覺部分的複雜程度有一點一點的在逐漸提升,從過去那種用BS隨便套一套就可以交差的網頁, 到現在隨便一個Campaign Site可能就會要你用動畫引擎去實作一堆有的沒的特效。
個人覺得在未來的前端業界中,出現像遊戲公司技術美術(TA)那樣的職位,大概也是極其有可能的。

前端工程師對於技術的鑽研,其實某種程度上也是對於未來的投資。而我之所以選擇Canvas這項技術,大部分的原因也就是想要探求這門技術,它在未來是否有更多的可能性。

About The Road Map

老實說我一開始在定下這個題目的時候也很猶豫,因為一開始是打算把webgl的內容一起包括進來這次的賽程的,但是後來實際評估了一下(有先試著寫了幾篇),才意識到要在30天內把2D + Webgl 全部寫完對我而言還是太困難了,畢竟平常還有工作 :(

主題的規劃上,在賽程的前段會先從基本的2D渲染理論和技術講起,
中期開始則是會找一些實務上常見的特效範例/套件效果來作為研究目標。
目前預計會提到的技術:

  1. web 端粒子系統實作
  2. 力學效果模擬(磁力/張力/引力)
  3. 布料材質模擬
  4. 二維以上的碰撞模擬
  5. 像素操作

敬請期待!

Opening

前陣子講了很久要來學習RxJs, 但是實際上卻是拖到了現在才要認真開始把它學好~
事不宜遲,我們這邊就馬上開始基礎的部分

Preface

首先,我們先來說說為什麼要學rxjs, 平常寫前端UI真的有常常需要用到這東西嗎?

之前偶爾有聽說RxJs可以用來做高頻率事件的監聽與處理(例如mousemovetouchmove), 但是這跟一般的addEventListenerjquery.on 方法又差在哪裡? 有必要為了一個高頻事件處理去特地學這麼一個看上去很複雜的東西嗎?

實際上,如果只是要處理高頻事件監聽,確實是沒有需要特地去學習Rxjs
但是Rxjs 其實並不是一個只能用來做高頻事件監聽控管的library。

Rxjs最重要的意義之一就是Reactive Programming,也就是當變數或資源發生變動時,由變數或資源自動告訴我發生變動了

Rxjs 把很多東西都當成Reactive的對象, 除了前面提到的高頻事件的event object,另外也可以把單純的變數(物件/數值等)作為reavtive對象, 而這個概念也就是所謂的 Observable

git clone專案的方法

說到clone專案(這邊以github為例), 一般狀況下有下面幾種比較常見的狀況

作法一(遠端git倉庫直接clone到本地)

  1. 首先第一步當然是直接在遠端倉庫的網頁(例如github的repo頁面)先開一個repo
  2. 再來打開自己電腦的command line(或是也可以使用Vscode等IDE自帶的終端), 利用bash指令去把當前資料夾位置移到想要存放本地repo資料夾的地方
  3. 然後就直接輸入git clone <遠端repo位址>
  4. 這樣就可以創建出一個和遠端倉庫關聯的本地倉庫

作法二(本地已經有一個裡面有檔案的資料夾, 然後在遠端也有一個裡面有東西的git倉庫,上面有master, 可能也有master以外的分支, 想要把本地的東西推到遠端)

  1. 首先第一步先把本地的資料夾做git的初始化 git init
  2. 再來 git remote add <遠端資料庫的暱稱, 可以自訂> <遠端資料庫的位址>
  3. 上面這個動作是去為本地的這個倉庫”標記”, 他有關聯上哪一個遠端倉庫(所以其實一個本地倉庫可以關聯上很多個遠端倉庫,但是暱稱不能重複)
  4. 所以說如果我想要去關聯一個新的遠端倉庫(我們這邊取名為apple), 那就是git remote add apple <apple的位址>
  5. 接下來因為我們這邊的狀況是本地已經有檔案, 想要推到遠端, 但遠端因為也是有東西的,所以這邊我們其實會有兩種做法,我們接著描述

作法二其1

  1. 直接 git fetch <指定遠端倉庫的暱稱> 這邊以apple為例, 也就是 git fetch apple
  2. 這麼做之後就可以把遠端倉庫的最新改動、所有分支與檔案下載回來, 而我們這邊因為本地倉庫是新開的,這個狀況叫做 “unborn branch”, 是一個只有HEAD在本地的狀況(這部分以後再討論)。
  3. 在這邊講的”unborn branch”的狀況下去做 fetch, 以這邊來講,就會把遠端的master下載回拉本地端,這樣就會在本地端看到一個origin/master分支, 這個就是遠端master在本地的備份, 這時候我們可以直接git checkout master或者直接把本地本來就存在的檔案做git add -A && git commit -m 'initial msg', 這樣就會發現第4步的狀況。
  4. 我們會發現本地出現了本地自己的master(關於 unborn branch的介紹可以看這一篇stackoverflow )。
  5. 如果我們在4.的時候是透過git checkout master產生本地master的話,會發現本地master是會自動跟遠端的備份(origin/master)同步好的, 而且遠端master也會同時被視為上游分支(上游分支的解釋可以看第7步),這樣就可以直接跳過第6步進階到第7步。
  6. 但是如果我們在4的時候是透過git add -A && git commit -m 'initial msg'去走到第5步,則會發現本地master只有原本就存在本地的檔案,這時候如果用的IDE是 vscode, 如過利用左下角的分支切換功能, 去切到apple/master , 就會發現報錯 “Git: fatal: A branch named ‘master’ already exists.”,這種狀況下我們要去執行 git pull apple master,然後就會發現多出了一個新的分支叫做 FETCHED_HEAD,我們接著可以進階到第7步(FETCHED_HEAD只是暫時的, 第七步結束後,FETCHED_HEAD就會消失, IDE也不會再報一樣的錯)。
  7. git push -u <遠端倉庫的暱稱> <想要push過去的遠端倉庫分支> , 以這邊來講就是git push -u apple master
  8. 這邊的 -u 代表的是去把apple master去定為這個分支的上游分支,這樣以後在這個分支上做的git push, 最後都會把改動push到apple master。
  9. 做完最後一步之後就會發現遠端repo的網頁上master的內容已經跟本地同步了

作法二其2

  1. 直接 git pull <指定遠端倉庫的暱稱> <指定想要pull下來的遠端倉庫分支>,這邊以apple上的master分支為例, 也就是 git pull apple master
  2. 其實這種狀況會跟前面其一的git add -A && git commit -m 'initial msg'狀況類似,接著也是直接git push -u <遠端倉庫的暱稱> <想要push過去的遠端倉庫分支> 就完事了(當然,如果有改動要commit的記得要commit)。