Canvas 学习笔记(二):canvas的绘图环境

714 阅读4分钟

前面已经了解了canvas元素的一些特性,但是canvas元素在实际使用过程中,仅仅只是充当一个容器,容器内的内容就是canvas的绘图环境了,理解一下就类似于windows的画图里面的“画布”,在前面也知道我们可以在这个绘图环境中“画出”文字“Hello Anker”;

暂时只探讨2D绘图环境,canvas 3D绘图环境被称之为WebGL,是我们下一阶段的目标

Frist of All,既然我们是初学者,还是回忆一下如何获取2D绘图环境。

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

如上代码所示,context即为我们下面要学习的Canvas的2D绘图环境。

那么我们先看下绘图环境有哪些属性 (ps:不需要全部看懂,不理解的属性跳过即可):

CanvasRenderingContext2D对象的属性

属性 简介
canvas 指向该绘图环境所属的canvas对象。该属性最常见的用途就是通过它来获取canvas的宽度与高度,分别调用context.canvas.width与context.canvas.height即可
fillstyle 指定该绘图环境在后续图形填充操作中使用的颜色、渐变色或图案
font 设定在调用绘图环境对象的fillText()或者strokeText()方法,所使用的字形状
globalAlpha 全局透明度(取值0~1.0),浏览器会将每个像素的alpha值与该值相乘
globalCompsiteOperation 该值决定了图形绘制时对于重叠的处理方式
lineCap 该值告诉浏览器如何绘制线段的端点(butt/round/square),默认值butt
lineWidth 该值决定了在canvas之中绘制线段的屏幕像素宽度,取值为非负、非无穷double值,默认值1.0
lineJoin 该值决定了两条线段相交时如何绘制交点(bevel/round/miter)默认值miter
miterLimit 如何绘制miter形式的线段交点
shadowBlur 绘制阴影的延伸
shadowColor 绘制阴影的颜色
shadowOffsetX 阴影的水平偏移量
shadowOffsetY 阴影的垂直偏移量
strokeStyle 对于路径描边的样式
textAlign 文本水平对齐的方式
textBaseline 文本垂直对齐的方式

以上属性,除了canvas,其他属性基本为该绘图环境的全局设置,后续在做图像具体绘制(画线,画曲线,文本)的时候再一一介绍各个属性;这里只需要了解到的一点是,CSS只能影响到Canvas元素,无法影响绘图环境(我曾经一度尝试这么做)。

此外在进行绘图操作时,需要频繁的设置这些值。很多时候只是临时的需要改变某些属性(没什么感觉,比如我临时需要在特定时候使用某种字体,绘图完成后又想恢复成通用字体)。

Canvas为context提供了两个ApI叫save()和restore(),用于保存以及恢复当前canvas绘图环境的所有属性。所以上面的需求可以用类似的方法实现:

function drawGrid(font) {
    context.save(); // 保存所有属性到一个堆栈
    
    context.font = font;
    
    // 绘制需要的图案与文字
    
    context.restore(); // 从堆栈中取出保存的属性,并还原
}

之所以强调save是压栈,restore是出栈,是因为canvas设计时,是支持save/restore嵌套使用的。

说了这么多,我们来实际使用一下,用canvas做个小小的Anker钟;

HTML代码如下

<html>
   <head>
     <title>Clock</title>

      <style> 
         body {
            background: #dddddd;
            display: flex;
            align-items: center;
            justify-content: center;
         }

         #canvas {
            margin: 20px;
            background: #ffffff;
            border: thin solid #aaaaaa;
         }
      </style>
   </head>

  <body>
    <canvas id='canvas' width='400' height='400'>
      Canvas not supported
    </canvas>

    <script src='example.js'></script>
  </body>
</html>
var canvas = document.getElementById('canvas'),
    context = canvas.getContext('2d'),
    FONT_HEIGHT = 15,
    MARGIN = 35,
    HAND_TRUNCATION = canvas.width/25,
    HOUR_HAND_TRUNCATION = canvas.width/10,
    NUMERAL_SPACING = 20,
    RADIUS = canvas.width/2 - MARGIN,
    HAND_RADIUS = RADIUS + NUMERAL_SPACING;

function drawCircle() {
   context.beginPath();
   context.arc(canvas.width/2, canvas.height/2, RADIUS, 0, Math.PI*2, true);
   context.stroke();
}
   
function drawNumerals() {
   var numerals = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ],
        angle = 0,
       numeralWidth = 0;

   numerals.forEach(function(numeral) {
      angle = Math.PI/6 * (numeral-3);
      numeralWidth = context.measureText(numeral).width;
      context.fillText(numeral, 
         canvas.width/2  + Math.cos(angle)*(HAND_RADIUS) - numeralWidth/2,
         canvas.height/2 + Math.sin(angle)*(HAND_RADIUS) + FONT_HEIGHT/3);
   });
}

function drawCenter() {
   context.beginPath();
   context.arc(canvas.width/2, canvas.height/2, 5, 0, Math.PI*2, true);
   context.fill();
}

function drawAnker() {
   context.save();

   context.beginPath();
   context.font = FONT_HEIGHT*3 + 'px Arial';
   context.fillStyle = 'cornflowerblue';
   context.strokeStyle = 'blue';

   context.strokeText("Anker", canvas.width/2 - 55, canvas.height/2 - 50 );

   context.fill();
   context.restore();
}

function drawHand(loc, isHour) {
   var angle = (Math.PI*2) * (loc/60) - Math.PI/2,
       handRadius = isHour ? RADIUS - HAND_TRUNCATION-HOUR_HAND_TRUNCATION 
                           : RADIUS - HAND_TRUNCATION;

   context.moveTo(canvas.width/2, canvas.height/2);
   context.lineTo(canvas.width/2  + Math.cos(angle)*handRadius, 
                  canvas.height/2 + Math.sin(angle)*handRadius);
   context.stroke();
}

function drawHands() {
   var date = new Date,
       hour = date.getHours();
   hour = hour > 12 ? hour - 12 : hour;
   drawHand(hour*5 + (date.getMinutes()/60)*5, true, 0.5);
   drawHand(date.getMinutes(), false, 0.5);
   drawHand(date.getSeconds(), false, 0.2);
}

function drawClock() {
   context.clearRect(0,0,canvas.width,canvas.height);

   drawCircle();
   drawCenter();
   drawAnker();
   drawHands();
   drawNumerals();
}

context.font = FONT_HEIGHT + 'px Arial';
loop = setInterval(drawClock, 1000);

似乎还是挺有意思的不是么? 下一阶段,我们来看下canvas的事件交互是如何做的