iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
自我挑戰組

JS30 學習日記系列 第 8

Day 8 - Fun with HTML5 Canvas

前言

JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No FrameworksNo CompilersNo LibrariesNo Boilerplate 在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。

另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。


本日目標

使用 JavaScript 實作出能在 HTML Canvas 上用筆刷畫圖的極簡版小畫家。


解析程式碼

HTML 部分

body 內放入 canvas 標籤作為我們的畫布,這裡雖然指定大小為 800*800,但之後會利用 JS 把它改成跟視窗(window)一樣大。

<canvas id="draw" width="800" height="800"></canvas>

JS 部分

取得 canvas 標籤後,將畫布的寬、高,將其改成跟視窗內部的寬、高一樣的數值。

const canvas = document.querySelector('#draw');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

canvas 只是一個空白的畫布,實際上我們需要"取得畫布的渲染環境(rendering context)"才能在渲染環境上作畫。

我們呼叫 canvasgetContext() 方法並放入 "2d" 作為參數,順利取得 canvas2D 渲染環境,若是要取得 3D 渲染環境只要放入 "3d" 作為參數就好。

緊接著,我們調整 contextstrokeStyle 屬性值,更改作畫時的畫筆顏色。

contextlineJoin 是用來設定兩條長度不為0的線段如何在接合處連接,把值設定為 "round" 則在相連部分以扇形連接。

contextlineCap 是用來設定線段末端的呈現方式,把值設定為 "round" 則在末端部分以扇形呈現。

contextlineWidth 用來設定線條的粗細。

const ctx = canvas.getContext('2d');
ctx.strokeStyle = '#BADASS';
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.lineWidth = 100;

我們希望在視窗下按住滑鼠拖曳時作畫,所以在 canvas 上註冊4個事件監聽器。

觸發'mousedown'事件時,設定 isDrawing = true,表示現在可以作畫;設定 [lastX,lastY] = [e.offsetX,e.offsetY],更新作畫的起始點。

觸發'mousemove'事件時,用 draw() 方法持續的作畫。

觸發'mouseup'、'mouseout'時,皆設定 isDrawing = false,表示在滑鼠沒被按住和滑鼠離開視窗的情況下都停止作畫。

let isDrawing = false;//flag
let lastX = 0;
let lastY = 0;

canvas.addEventListener('mousemove',draw);
canvas.addEventListener('mousedown',(e)=> {
        isDrawing = true;
        [lastX,lastY] = [e.offsetX,e.offsetY];
    }
);
canvas.addEventListener('mouseup',()=> isDrawing = false);
canvas.addEventListener('mouseout',()=> isDrawing = false);

事件處理方法 draw(),在最初就先判斷現在能不能作畫,不能的話(isDrawing = false)就直接返回。

ctx.beginPath() 用來建立一個新的作畫路徑。
ctx.moveTo() 用來移動路徑起始點的座標。
ctx.lineTo() 用來指定這條路徑的終點座標。
ctx.stroke() 將這條路徑描出來。

[lastX,lastY] = [e.offsetX,e.offsetY] 用來不斷更新"此次"按住滑鼠拖曳作畫過程中持續變動的路徑起始點。

function draw(e){
    if(!isDrawing) return;
   
    ctx.beginPath();
    //start from
    ctx.moveTo(lastX,lastY);
    //go to
    ctx.lineTo(e.offsetX,e.offsetY);
    ctx.stroke();
    [lastX,lastY] = [e.offsetX,e.offsetY];
}

HSL 的 H(Hue,色相角度),是由 0~360 為止的色相循環,0是紅色、120是綠色、240是藍色,Hue 本身不加單位,詳細說明可以參考下方補充資料 CSS Coke 大大的文章。

宣告 hue 變數指定畫筆顏色,並持續的將 hue + 1,當 hue >= 360時,代表顏色已經循環過一次,故將其重設為0。

宣告 direction 變數作為目前該加粗畫筆還是讓畫筆變細的判斷,direction 的初始值是 true 也就是讓畫筆變粗。第一個 if 判斷目前的畫筆粗細是不是 >= 100 或 <= 1,如果條件符合就將 direction 變為相反值(true 變 false)。在第二個的 if-else 判斷,如果 direction = true 就加粗畫筆、反之則讓畫筆變得更細。

let hue = 0;
let direction = true;

function draw(e){

    /*.......省略*/
    ctx.strokeStyle = `hsl(${hue},100%,50%)`;
    /*.......省略*/
    hue++;
    if(hue >= 360){
        hue = 0;
    }
    if(ctx.lineWidth >= 100 || ctx.lineWidth <= 1){
        direction = !direction;
    }
    if(direction){
        ctx.lineWidth++;
    }else{
        ctx.lineWidth--;
    }
}

最後來介紹一個特別的東西,ctxglobalCompositeOperation主要用來處理疊圖(線段重疊)的情況,若將其設定為 'multiply' 則將上層像素與相對應的下層像素相乘,整體效果是讓顏色更加趨於黑色。

ctx.globalCompositeOperation = 'multiply';
效果圖(不使用globalCompositeOperation)

補充資料:

Canvas 基本用途
CanvasRenderingContext2D.strokeStyle
CanvasRenderingContext2D.lineJoin
CanvasRenderingContext2D.lineCap
CanvasRenderingContext2D.lineWidth
CanvasRenderingContext2D.beginPath()
CanvasRenderingContext2D.moveTo()
CanvasRenderingContext2D.lineTo()
RGB、HSL、Hex 網頁色彩碼,看完這篇全懂了
JS一秒區分clientX,offsetX,screenX,pageX之間關係
CanvasRenderingContext2D.globalCompositeOperation

範例網頁請按此


上一篇
Day 7 - Array Cardio Day 2
下一篇
Day 9 - 14 Must Know Dev Tools Tricks
系列文
JS30 學習日記30

尚未有邦友留言

立即登入留言