Canvas学习笔记

 

慕课网视频《 炫丽的倒计时效果Canvas绘图与动画基础 》&《 Canvas绘图详解 》笔记

↑老师讲的很好,不过语速很慢,二倍速刚好。

基础知识

通过 <canvas></canvas> 即可创建一个canvas。

<canvas id="canvas" style="..."></canvas>
<canvas id="canvas" width="1024" height="768"></canvas>

不建议直接使用css的方式指定大小。css指定的是显示的大小,通过height和width指定的是显示的大小以及分辨率的大小。

JavaScript中指定canvas宽高。

var canvas = document.getElementById('canvas');

canvas.width = 1024;
canvas.height = 768;

canvas 绘图主要通过 canvas.getContext 的获得的上下文的 api 实现。

var context = canvas.getContext('2d'); // 获得绘图上下文环境

canvas坐标轴 :左上角为原点,向右为x轴,向下为y轴。

canvas 是基于状态的绘图。

context.beginPath();
context.moveTo(100, 100);
context.lineTo(700, 700);
context.lineTo(100, 700);
context.lineTo(100, 100);
context.closePath();

context.fillStyle = 'rgb(233, 233, 233)';
context.fill();

context.lineWidth = 5;
context.strokeStyle = '#123456';

context.stroke();

context.beginPath();
context.moveTo(200, 100);
context.lineTo(700, 600);
context.strokeStyle = 'black';
context.stroke();

 

moveTo(x,y) 画笔移到(x,y)。

lineTo(x,y) 从当前点到(x,y)画一条。

stroke() 把当前的路径绘制出来,但并不会清空当前状态(也就是说下一次调用stroke之前绘制的会再次被绘制)。

fill() 如果路径不是封闭的,会把路径首尾相连。

beginPath() 开始一段新的路径,也就是说,此后再次调用stroke()的时候,之前的线条不会被重新绘制。同时清空当前坐标。(紧接着的lineTo()相当于moveTo())

closePath() 结束一段路径。如果路径没有封闭,就将路径首尾连接起来。

context.lineWidth, context.strokeStyle 设置绘制线条宽度和样式。

context.fillStyle 设置填充样式。

绘制圆和弧

context.arc(
    centerX, centerY, radius,   // 圆心、半径
    startAngle, endAngle,       // 起始角度、结束角度(水平向右为 0°
    anticlockwise = false       // 是都逆时针 默认为false
)
//
context.arc(300, 300, 200, 0, 1.5 * Math.PI, true);

通过 canvas 制作动画

通过定时器不断更新状态重新绘制

setInterval(
    function() {
        render();
        update();
    },
    50
)

清空指定区域  clearRect(x1, y1, x2, y2); 

 

画一个七巧板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <canvas id="canvas" width="1024" height="768" style="border:1px solid #aaa; display: block;">
    </canvas>

    <script>
        var tangram = [
            {p:[{x:0,y:0},{x:800,y:0},{x:400,y:400}],color:"green"},
            {p:[{x:0,y:0},{x:400,y:400},{x:0,y:800}],color:"red"},
            {p:[{x:800,y:0},{x:800,y:400},{x:600,y:600},{x:600,y:200}],color:"yellow"},
            {p:[{x:600,y:200},{x:600,y:600},{x:400,y:400}],color:"blue"},
            {p:[{x:400,y:400},{x:600,y:600},{x:400,y:800},{x:200,y:600}],color:"pink"},
            {p:[{x:200,y:600},{x:400,y:800},{x:0,y:800}],color:"black"},
            {p:[{x:800,y:400},{x:800,y:800},{x:400,y:800}],color:"gray"}
        ];

        window.onload = function() {
            var canvas = document.getElementById('canvas');
            var context = canvas.getContext('2d');
            for (var i = 0; i < tangram.length; i++) {
                draw(tangram[i], context);
            }
        }

        function draw(piece, ctx) {
            ctx.beginPath();
            ctx.moveTo(piece.p[0].x, piece.p[0].y);
            for (var i = 1; i < piece.p.length; i++) {
                ctx.lineTo(piece.p[i].x, piece.p[i].y);
            }
            ctx.closePath();
            ctx.fillStyle = piece.color;
            ctx.fill();
        }
    </script>
</body>
</html>
View Code

绚丽的倒计时效果

(坐标推导过程)

index.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        html, body{ margin:0; height:100%; }
    </style>
</head>
<body>
    <canvas id="canvas" style="width:100%; height:100%;">
        当前浏览器不支持Canvas,请更换浏览器后再试
    </canvas>

    <script src="digit.js"></script>
    <script src="countdown.js"></script>
</body>
</html>
View Code

countdown.js 和老师写的不完全一样,因为我懒得看了,所以小部分偷懒自己瞎写的

var WINDOW_WIDTH = 1024;
var WINDOW_HEIGHT = 768;
var RADIUS = 8;
var MARGIN_TOP = 60;
var MARGIN_LEFT = 30;

var curShowTimeSeconds = 0;

var balls = [];
const colors = ['#ff5b5b', '#a2ff95', '#95ffc8', '#95b2ff', '#c195ff', '#ff95f4', '#ff95c3', '#ffe295'];

window.onload = function() {

    WINDOW_HEIGHT = document.body.clientHeight-1;
    WINDOW_WIDTH = document.body.clientWidth-1;

    MARGIN_LEFT = Math.round(WINDOW_WIDTH / 10);
    RADIUS = Math.round(WINDOW_WIDTH * 4 / 5 / 108) - 1;

    MARGIN_TOP = Math.round(WINDOW_HEIGHT / 5);

    var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');

    canvas.width = WINDOW_WIDTH;
    canvas.height = WINDOW_HEIGHT;

    console.log(canvas);

    curShowTimeSeconds = 3 * 3600; // 倒计时三小时

    var timer = setInterval(
        function() {
            render(context);
            renderBalls(context);
            curShowTimeSeconds -= 1/50;
            update();
            if (curShowTimeSeconds <= 0) {
                curShowTimeSeconds = 0;
                clearInterval(timer);
            }
        },
        20
    )
}

function update() {
    var tempBalls = [];
    for (var ball of balls) {
        ball.x += ball.vx;
        ball.y += ball.vy;
        ball.vy += ball.g;
        if (ball.y >= 768 - ball.r) {
            ball.y = 768 - ball.r;
            ball.vy = -ball.vy * 0.75;
        }
        if (ball.x + RADIUS > 0 && ball.x - RADIUS < WINDOW_WIDTH) {
            tempBalls.push(ball);
        }
    }
    balls = tempBalls;

    var seconds = curShowTimeSeconds % 60;
    if (Math.floor(curShowTimeSeconds) !== Math.floor(curShowTimeSeconds + 1/50)) {
        addBalls(MARGIN_LEFT + 78*(RADIUS+1), MARGIN_TOP, parseInt(seconds/10));
        addBalls(MARGIN_LEFT + 93*(RADIUS+1), MARGIN_TOP, parseInt(seconds%10));
    }
}

function addBalls(x, y, num) {
    for (var i = 0; i < digit[num].length; i++) {
        for (var j = 0; j < digit[num][i].length; j++) {
            if (digit[num][i][j] === 1) { // 点阵中1表示绘制
                balls.push({ 
                    x: x+j*2*(RADIUS+1)+(RADIUS+1),
                    y: y+i*2*(RADIUS+1)+(RADIUS+1),
                    r: RADIUS, 
                    g: 1.5 + Math.random(), 
                    vx: Math.pow(-1, Math.ceil(Math.random() * 1000)) * 4,
                    vy: -10, 
                    color: colors[Math.floor(Math.random() * colors.length)]
                });
            }
        }
    }
}

function render(ctx) {
    ctx.clearRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

    var hours = parseInt(curShowTimeSeconds / 3600);
    var minutes = parseInt((curShowTimeSeconds - hours * 3600) / 60);
    var seconds = curShowTimeSeconds % 60;

    renderDigit(MARGIN_LEFT, MARGIN_TOP, parseInt(hours/10), ctx);
    renderDigit(MARGIN_LEFT + 15*(RADIUS+1), MARGIN_TOP, parseInt(hours%10), ctx)
    renderDigit(MARGIN_LEFT + 30*(RADIUS + 1), MARGIN_TOP, 10 , ctx)
    renderDigit(MARGIN_LEFT + 39*(RADIUS+1), MARGIN_TOP, parseInt(minutes/10), ctx);
    renderDigit(MARGIN_LEFT + 54*(RADIUS+1), MARGIN_TOP, parseInt(minutes%10), ctx);
    renderDigit(MARGIN_LEFT + 69*(RADIUS+1), MARGIN_TOP, 10, ctx);
    renderDigit(MARGIN_LEFT + 78*(RADIUS+1), MARGIN_TOP, parseInt(seconds/10), ctx);
    renderDigit(MARGIN_LEFT + 93*(RADIUS+1), MARGIN_TOP, parseInt(seconds%10), ctx);
}

function renderBalls(ctx) {
    balls.forEach((ball) => {
        ctx.fillStyle = ball.color;
        ctx.beginPath();
        ctx.arc(ball.x, ball.y, ball.r, 0, 2 * Math.PI);
        ctx.closePath();

        ctx.fill();
    })
}

// 在 (x,y) 为起点 画一个数字 num
function renderDigit(x, y, num, ctx) {
    ctx.fillStyle = 'rgb(0, 102, 153)';

    for (var i = 0; i < digit[num].length; i++) {
        for (var j = 0; j < digit[num][i].length; j++) {
            if (digit[num][i][j] === 1) { // 点阵中1表示绘制
                ctx.beginPath();
                ctx.arc(x+j*2*(RADIUS+1)+(RADIUS+1), y+i*2*(RADIUS+1)+(RADIUS+1), RADIUS, 0, 2*Math.PI);
                ctx.closePath();
                ctx.fill();
            }
        }
    }
}
View Code

digit.js 这个是复制老师的

digit =
    [
        [ //
            [0,0,1,1,1,0,0],
            [0,1,1,0,1,1,0],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,0,1,1,0],
            [0,0,1,1,1,0,0]
        ],//0
        [
            [0,0,0,1,1,0,0],
            [0,1,1,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [1,1,1,1,1,1,1]
        ],//1
        [
            [0,1,1,1,1,1,0],
            [1,1,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,0,0],
            [0,0,1,1,0,0,0],
            [0,1,1,0,0,0,0],
            [1,1,0,0,0,0,0],
            [1,1,0,0,0,1,1],
            [1,1,1,1,1,1,1]
        ],//2
        [
            [1,1,1,1,1,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,0,0],
            [0,0,1,1,1,0,0],
            [0,0,0,0,1,1,0],
            [0,0,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,1,1,0]
        ],//3
        [
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,1,0],
            [0,0,1,1,1,1,0],
            [0,1,1,0,1,1,0],
            [1,1,0,0,1,1,0],
            [1,1,1,1,1,1,1],
            [0,0,0,0,1,1,0],
            [0,0,0,0,1,1,0],
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,1,1]
        ],//4
        [
            [1,1,1,1,1,1,1],
            [1,1,0,0,0,0,0],
            [1,1,0,0,0,0,0],
            [1,1,1,1,1,1,0],
            [0,0,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,1,1,0]
        ],//5
        [
            [0,0,0,0,1,1,0],
            [0,0,1,1,0,0,0],
            [0,1,1,0,0,0,0],
            [1,1,0,0,0,0,0],
            [1,1,0,1,1,1,0],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,1,1,0]
        ],//6
        [
            [1,1,1,1,1,1,1],
            [1,1,0,0,0,1,1],
            [0,0,0,0,1,1,0],
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,0,0],
            [0,0,0,1,1,0,0],
            [0,0,1,1,0,0,0],
            [0,0,1,1,0,0,0],
            [0,0,1,1,0,0,0],
            [0,0,1,1,0,0,0]
        ],//7
        [
            [0,1,1,1,1,1,0],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,1,1,0],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,1,1,0]
        ],//8
        [
            [0,1,1,1,1,1,0],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [1,1,0,0,0,1,1],
            [0,1,1,1,0,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,0,1,1],
            [0,0,0,0,1,1,0],
            [0,0,0,1,1,0,0],
            [0,1,1,0,0,0,0]
        ],//9
        [
            [0,0,0,0],
            [0,0,0,0],
            [0,1,1,0],
            [0,1,1,0],
            [0,0,0,0],
            [0,0,0,0],
            [0,1,1,0],
            [0,1,1,0],
            [0,0,0,0],
            [0,0,0,0]
        ]//:
    ];
View Code

 

 

封闭多边形的首尾交点会有缺口,使用 context.closePath() 能解决这个问题。

绘制封闭多边形最好成对使用  beginPath(); 和 closePath(); 

先绘制线条再填充颜色,边框的一半会被填充色覆盖。所以先绘制填充色再描边。

绘制矩形api: 

ctx.beginPath();
ctx.rect(x, y, width, height);
ctx.closePath();
ctx.stroke();

ctx.fillRect(x, y, width, height);

ctx.strokeRect(x, y, width, height);

fillStyle 和 strokeStyle 支持所有CSS支持的颜色表示。

线条的属性

  • lineCap 线条两端的形状: butt (默认,平的)round(圆的) square(方的) 后面两个比较butt会突出来。只用于线段结尾处,不用于连接处。
  • lineJoin 线条连接处形状: miter(默认,尖角)bevel(平的) round(圆角)miter
  • lineJoin 为 miter 时,延长线长度大于miterLimit,会自动变为bevel。miterLimit 默认为10。

 

图形变换

位移、旋转、缩放。

 context.translate(x, y); 默认多个translate会叠加。

 context.rotate(deg); 

 context.scale(sx, sy);  不仅会缩放长度、宽度,还会缩放坐标、边框长度等属性。

save(); restore(); 成对出现,中间绘图状态不会对后面造成影响。用于保存和恢复绘图状态,颜色,方向等

/* 
a:水平缩放(默认值1) 
b:水平倾斜(默认值0) 
c:垂直倾斜(默认值0) 
d:垂直缩放(默认值1) 
e:水平位移(默认值0) 
f:垂直位移(默认值0) 
*/ 
context.transform(a, b, c, d, e, f); 

context.transform(); 效果会叠加

如果需要重新初始化矩阵变换的值,用:  context.setTransform(a, b, c, d, e, f);  会使得之前设置的 context.transform() 失效

样式填充

线性渐变

var grd = context.createLinearGradient(xstart, ystart, xend, yend);
grd.addColorStop(stop, color); // 可添加任意个 stop[0,1]之间的数字
// 渐变色结束之后的颜色等于结束时的颜色

var grd = context.createLinearGradient(200, 200, 800, 800);
grd.addColorStop(0.0, '#f00');
grd.addColorStop(1.0, '#000');
context.fillStyle = grd;

径向渐变

createRadialGradient(x1,y1,r1,x2,y2,r2);
addColorStop(stop,color);

图片填充

var bgImg = new Image();
bgImg.src = 'https://img1.mukewang.com/5333a1bc00014e8302000200-140-140.jpg';
bgImg.onload = function () {
    var pattern = context.createPattern(bgImg, 'repeat');
    context.fillStyle = pattern;
    context.fillRect(0, 0, 800, 800);
}

用另外一个canvas填充

var bgCanvas = createBackgroundCanvas();
var pattern = context.createPattern(bgCanvas, 'repeat');
context.fillStyle = pattern;
context.fillRect(0, 0, 800, 800);

function createBackgroundCanvas() {
    var canvas = document.createElement('canvas');
    canvas.width = 100;
    canvas.height = 100;
    var context = canvas.getContext('2d');
    drawStar(context, 50, 50, 50, 0); // 之前的课程中写的的
    return canvas;
}

用 video 填充...

其中 repeat-style 可选值 no-repeat/repeat-x/repeat-y/repeat

 

绘制圆弧。

context.arc( centerX, centerY, radius, startAngle, endAngle, anticlockwise = false )

 context.arcTo(x1,y1,x2,y2,radius);   (x1,y1)是控制点,(x0,y0,x1,y1)作为第一条切线, (x1,y1,x2,y2)作为第二条切线,radius是弧线半径圆弧。起点是当前所在点(x0,y0),到第一个切点作直线,然后画弧,终点是第二个切点。

    

贝塞尔二次曲线。指定起始点,控制点,终点。

context.quadraticCurveTo(control_x, control_y, targetx, targety);

贝塞尔三次曲线。指定起始点,控制点1,控制点2,终点。 

context.quadraticCurveTo(control_x1, control_y1, control_x2, control_y2, targetx, targety);

 

文字渲染

文字样式

ctx.font = fontStyle; // 默认 '20px sans-serif'
ctx.font = font-style font-variant font-weight font-size font-family
ctx.fillText(string, x, y, [maxlen]);
ctx.strokeText(string, x, y, [maxlen]);

ctx.font = 'bold 40px Arial';
ctx.fillStyle = '#058';
ctx.strokeStyle = 'yellow';
ctx.fillText('文字', 100, 100);
ctx.strokeText('文字', 100, 100);

文字对齐

水平对齐方式 left(default) center right

ctx.textAlign = 'left';

垂直对齐方式 top middle bottom alphabetic(default) hanging alphabetic

ctx.textBaseline = 'top';

文本的度量

获取文本宽度,使用前要先设置 font 属性

ctx.measureText(string).width;

 

阴影

context.shadowColor

context.shadowOffsetX

context.shadowOffsetY

context.shadowBlur

 

全局属性

globalAlpha 设置全局透明度 
ctx.globalAlpha=0.2;

globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上。

source-over 默认。在目标图像上显示源图像。
source-atop 在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。
source-in 在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。
source-out 在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。
destination-over 在源图像上方显示目标图像。
destination-atop 在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。
destination-in 在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。
destination-out 在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。
lighter 显示源图像 + 目标图像。
copy 显示源图像。忽略目标图像。
xor 使用异或操作对源图像与目标图像进行组合。
 

、、、、、

 

posted @ 2018-10-11 14:54  我不吃饼干呀  阅读(254)  评论(0编辑  收藏  举报