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

范例网页请按此


<<:  Day08 - Docker 101 Image 建置篇

>>:  OpenStack Nova 介绍 2

硕士课程一问

想问一下大家觉得要是想从事软件开发或是前端web的工程师,大家会推荐念硕士课程吗?还是大家觉得实战经...

Day 14 实作调色盘App(2/3)

上一篇我们讲到如何用Slider去改变颜色 这篇会介绍如何使用输入框 程序码如下: //控制红色的输...

Day 3 - Vue的专案结构

除了可以用CDN(Content Delivery Network)的方式来引入Vue之外,我们还可...

Day6 React搭配JSX运用

昨天学习到了JSX的几大特色与优点之後,今天就正式来学习用JSX搭配react。载入JSX之前先引用...

How to change Siri's voice on your iPhone

If you want your iPhone to be more original and st...