前面已经了解了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代码如下
Clockbackground: #dddddd;
display: flex;
align-items: center;
justify-content: center;
}
#canvas {
margin: 20px;
background: #ffffff;
border: thin solid #aaaaaa;
}
Canvas not supported
复制代码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的事件交互是如何做的