Canvas描绘多彩的星星 | HTML5图形开发 从最基础开始学Canvas(二):基本图形变换、状态的保存恢复及代码实现五角星

978 阅读6分钟

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

基本代码如下,所有示例在此canvas实现

<style>
    canvas{
        border: 1px solid #000;
    }
</style>

<canvas width="600" height="500" id="c1"></canvas>
    <script>
        let canvas=document.getElementById("c1");
        let ctx=canvas.getContext("2d");

        // 示例。。。
    </script>

基本的图形变换

基本的图形变换包括位移、旋转、缩放。在canvas中,这些操作都是基于坐标系原点进行操作的。在复杂多图形处理中,不同的图形应用的变换多种多种,这样就有可能产生混乱,因此Canvas中提供save和restore方法,实现坐标系、颜色设置等上下文的隔离和还原。

位移(translate)

  • translate(x, y): 移动 canvas 的原点到指定的位置。即移动的是坐标原点
ctx.beginPath();
// 将canvas坐标系原点移动到 x,y
ctx.translate(origin.x,origin.y);
ctx.arc(0,0,20,0,2*Math.PI);
ctx.fillStyle="green";
ctx.fill();

ctx.beginPath();
ctx.rect(-canvas.width/2 +100,-canvas.height/2+50,200,100);
ctx.strokeStyle="red";
ctx.stroke();

旋转(rotate)

  • rotate(angle):顺时针方向的,以弧度为单位旋转坐标轴,旋转的中心是坐标原点

    旋转(坐标原点)

let rotateTest=function(){
    ctx.beginPath();
    ctx.rotate(Math.PI/180*30);
    ctx.rect(0,0,320,180);
    ctx.fillStyle="green";
    ctx.fill();
}
rotateTest();

  1. 移动并旋转
let rotateTest2=function(){
    // 位移原点,旋转时以原点旋转
    ctx.translate(100, 100);

    ctx.beginPath();           
    ctx.rect(0, 0, 320, 180);
    ctx.fillStyle = "red";
    ctx.fill();

    ctx.beginPath();
    ctx.rotate(Math.PI/180*32);
    ctx.rect(0,0,320,180);
    ctx.fillStyle="green";
    ctx.fill();
}
rotateTest2();

缩放(scale)

  • scale(x, y) 通过增减图形在 canvas 中的像素数目,对形状(位图)进行缩小或者放大。

​scale方法。x,y分别表示横轴和纵轴的缩放,正值。小于1表示缩小,大于1表示放大,1为原始比例。

默认情况下,canvas 的 1 单位就是 1 个像素。举例说,如果我们设置缩放因子是 0.5,1 个单位就变成对应 0.5 个像素,这样绘制出来的形状就会是原先的一半。同理,设置为 2.0 时,1 个单位就对应变成了 2 像素,绘制的结果就是图形放大了 2 倍。

可以看作缩放是整个画布坐标系的缩放

let scaleTest=function(){
    ctx.beginPath();
    ctx.strokeStyle="green";
    ctx.lineWidth=3;
    ctx.strokeRect(0,0,200,100);

    // 缩放x或y轴
    ctx.scale(2,1);
    ctx.scale(1,2);
    ctx.scale(2, 2);
    ctx.beginPath();    
    ctx.strokeStyle = "red";
    ctx.lineWidth = 3;
    ctx.strokeRect(0, 0, 200, 100);
}
scaleTest();

两点间的距离

如下,利用勾股定理求距离

let getDistance=function(x1,y1,x2,y2){
    return Math.sqrt(Math.abs((x1 - x2) * (x1 - x2))*Math.abs((y1-y2)*(y1-y2)))
}

sin\cos\tan\atan2函数

数学中sin-正弦、cos-余弦、tan-正切的关系如下图:

  • Math.sin(x) 返回一个 -1 到 1 之间的数值,表示给定角度(单位:弧度)的正弦值。
  • Math.cos(x) 返回一个 -1 到 1 之间的数值,表示角度(单位:弧度)的余弦值。
  • Math.atan2(y,x) 返回从原点(0,0)到(x,y)点的线段与x轴正方向之间的平面角度(弧度值),返回值-pi 到 pi 之间,点x,y的偏移角度。Math.atan(y/x)返回的弧度在一三或二四象限是相同的,无法区分。
  • Math.tan(x) 返回一个角(弧度)的正切值。`

// sin cos atan2
let sincosatan2Test=function(){
    let p={
        x:100,
        y:100,
    }

    let alpha=Math.atan2(p.x,p.y);
    console.log(`坐标${p.x},${p.y}的弧度值:${alpha}`);
    console.log(`坐标${p.x},${p.y}的角度:${alpha*180/Math.PI}度`);
    let anglePI_sin=Math.sin(Math.PI);
    let anglehalfPI_sin=Math.sin(Math.PI/2);
    let angle2PI_sin = Math.sin(Math.PI*2);
    let angle1000_sin = Math.sin(1000);
    console.log("PI的sin:"+anglePI_sin);
    console.log("1/2PI的sin:"+anglehalfPI_sin);
    console.log("2*PI的sin:"+angle2PI_sin);
    console.log("1000的sin:"+angle1000_sin);

    let anglePI_cos = Math.cos(Math.PI);
    let anglehalfPI_cos = Math.cos(Math.PI / 2);
    let angle2PI_cos = Math.cos(Math.PI * 2);
    let angle1000_cos = Math.cos(1000);
    console.log("PI的sin:" + anglePI_sin);
    console.log("1/2PI的sin:" + anglehalfPI_sin);
    console.log("2*PI的sin:" + angle2PI_sin);
    console.log("1000的sin:" + angle1000_sin);
}
sincosatan2Test();

Canvas状态的保存与恢复

在Canvas中,Saving and restoring state是绘制复杂图形时必不可少的操作。

save的作用在于隔离出来一个新的canvas上下文环境,保存之前的状态,可以重新设置新的样式并绘制图形;restore的作用在于恢复上一次的保存的状态,使用旧的样式绘制。

尤其是对于使用了位移、旋转、缩放等效果的情况下,restore可以还原坐标系的状态,防止复杂操作下出现混乱

save()restore()方法用来保存和恢复Canvas的状态

Canvas的状态就是当前画面应用到的所有样式和变形的一个快照

save()

Canvas状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。

一个绘画状态包括:

  • 当前应用的变形(即移动,旋转和缩放)

  • strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值

  • 当前的裁切路径(clipping path)

可以调用任意多次save方法。(类似数组的push())

restore()

每一次调用restore方法,上一个保存的状态就从栈中弹出,所有设定都恢复。(类似数组的pop())。

save和resotre对canvas上下文状态的隔离和管理非常方便,通常写canvas效果可以如下所示:

// 保存之前的状态
ctx.save();
// 样式设置
//...
ctx.restore();//恢复状态

save和restore使用示例

如下,使用这个过程:画一个正方形,保存之前的状态并修改颜色绘制,保存之前的状态并修改颜色绘制,恢复状态并绘制,恢复状态并绘制,绘制的一个小例子。

// 状态保存恢复
let statusSaveRestore=function(){
    ctx.fillRect(0, 0, 150, 150);   // 使用默认设置绘制一个矩形
    ctx.save();                  // 保存默认状态

    ctx.fillStyle = 'red'       // 在原有配置基础上对颜色做改变
    ctx.fillRect(15, 15, 120, 120); // 使用新的设置绘制一个矩形

    ctx.save();                  // 保存当前状态
    ctx.fillStyle = '#FFF'       // 再次改变颜色配置
    ctx.fillRect(30, 30, 90, 90);   // 使用新的配置绘制一个矩形

    ctx.restore();               // 重新加载之前的颜色状态
    ctx.fillRect(45, 45, 60, 60);   // 使用上一次的配置绘制一个矩形

    ctx.restore();               // 加载默认颜色配置
    ctx.fillRect(60, 60, 30, 30);   // 使用加载的配置绘制一个矩形
}
statusSaveRestore();

不使用状态保存和恢复的效果如下:

多种多样的五角星示例

如下,五角星的五个尖角和5个钝角可以看作从圆心平均分开的10个点,可以由内外两个圆各自包含5个点,给定不同的内外圆的半径,各个点的坐标就可以通过正余弦求得。

如下,实现五角星的函数代码,还可以修改,防止产生反五角星:

// 画一个五角星
let drawPentagram=function(options){

    let ctx=options.ctx;
    let origin = {
        x: options.x,
        y: options.y
    }
    let lengthScale=options.lengthScale||0.4; // 大于1也是五角星 反五角星

    // 五角星长短边
    let lengthR = options.lengthR||100;
    let shortR = lengthScale* lengthR;
    // 五角星的角度 弧度
    let starRotate=options.starRotate||0;
    let strokeColor=options.strokeColor||"red";

    let deg = Math.PI * 2 / 10;

    
    ctx.save();
    // 移动画布原点到五角星中心
    ctx.translate(origin.x, origin.y);
    // 先假设长边角 位于x轴上
    // 旋转坐标系,x轴向上,即尖角向上,保证摆正五角星
    ctx.rotate(- Math.PI / 2);

    ctx.rotate(starRotate);

    ctx.beginPath();
    //ctx.moveTo(lengthR*Math.cos(0),lengthR*Math.sin(0));
    ctx.moveTo(lengthR, 0);
    for (let i = 1; i < 10; i++) {
        let currDeg = i * deg;
        if (i % 2 === 0) { //lengthR
            ctx.lineTo(lengthR * Math.cos(currDeg), lengthR * Math.sin(currDeg));
        }
        else {
            ctx.lineTo(shortR * Math.cos(currDeg), shortR * Math.sin(currDeg));
        }
    }
    ctx.closePath();
    ctx.strokeStyle = strokeColor;
    ctx.stroke();
    // 将坐标系旋转放正 旋转的是坐标系,以前已经画好的图形保持不变
    // 因为使用了restore,此处的旋转还原可以省略
    // ctx.rotate(Math.PI / 2);
    ctx.restore();

}

画一个标准的五角星:

drawPentagram({
    ctx, 
    x:canvas.width / 2, 
    y:canvas.height / 2,
});

代码效果如下:

随机产生无数星星:

let colorArr=["red","greed","yellow","pink","olive","blue","orange","brown","purple"]

for (let i = 0; i < 10; i++) {
    drawPentagram({
        ctx,
        x: canvas.width*Math.random(),
        y: canvas.height*Math.random(),
        lengthR:100*Math.random(),
        lengthScale: Math.random(),
        strokeColor:colorArr[Math.floor(colorArr.length*Math.random())],
        starRotate: Math.PI*2* Math.random()
    });
}

代码效果如下: