目录
0x01 Canvas的绘图流程
- 编写canvas标签,注意指定宽高,如果不指定宽高,默认是300px宽,150px高。不建议使用css的方式,例如style 属性来设置canvas标签的宽高,因为css设置的只是canvas显示的大小。不能设置canvas画布分辨率的大小。采用width 和 height属性设置大小,相当于同时将两者设置了出来,是复合W3C的标准的。注意:数字之后是没有单位的。
- 获取 canvas DOM对象
- 获取 canvas 对象,即绘图的上下文环境
- 设置绘图属性
- 调用绘图API 例如 stroke fill 这些函数
案例:
准备工作:
<canvas id="canvas" width='800' height='800'><strong>尊敬的用户,您的浏览器并不支持canvas,请更换浏览器!</strong></canvas>
<script>
const canvas = document.getElementById("canvas");
//获取到canvas对象
const ctx = canvas.getContext('2d');
//如果标签中没有设置大小,也可以通过js的方式来指定canvas的大小
//canvas.width = 800;
//canvas.height = 800;
在canvas标签之间可以写上当用户的浏览器不支持canvas时,你希望用户看到什么。
当用户的浏览器支持canvas时,canvas标签之间的html代码将会完全被浏览器忽略掉。
也可以在js中实现这一点,如果canvas.getContext('2d')为空,那么就说明浏览器不支持canvas
<script>
var canvas = document.getElementById("canvas");
if(canvas.getContext('2d')){
var context = canvas.getContext('2d');
//使用context绘图
}else{
alert("当前浏览器不支持canvas,请更换浏览器后再试");
}
</script>
canvas中的绘图是一种基于状态的绘图,即先设置绘图的状态、绘图的属性,然后再调用api进行具体的绘制
0x02 绘制线段:
多个lineTo连用,即可连续绘制线段
这里就可以体现出 canvas是一种基于状态的绘图,而不是一种基于对象的绘图
如果是基于对象的绘图,那么我们就应该创建出一个line对象,然后对line对象进行设置
而canvas对线条的种种设置都是对ctx对象的属性的设置。
//开始绘制线段
ctx.beginPath();
//设置线条的宽度,单位px
ctx.lineWidth = 1;
//设置线的颜色,可以使用16进制,但是需要放在一个字符串中
ctx.strokeStyle = 'blue';
//线的起点
ctx.moveTo(100,100);
//中间点坐标
ctx.lineTo(250,250);
//终点坐标
ctx.lineTo(300,100);
//绘制线段
ctx.stroke();
0x03 绘制多边形:
起点坐标 = 终点坐标 ,即可绘制多边形。
ctx.fillStyle = ’rgb(0,0,0)‘ 设置多边形的填充色
ctx.fill() 进行填充
context.moveTo(512,200);
context.lineTo(612,300);
context.lineTo(500,500);
context.lineTo(512,200);
//填充颜色
context.fillStyle = 'rgb(2,100,30)';
context.fill();
//绘制边框
//也可以不调用一下方法,这样多边形就没有边框了
context.lineWidth = 5;
context.strokeStyle = '#005588';
context.stroke();
0x04 绘制多个图形:
因为canvas是基于状态的,所以为了不让给上一个图形设置的状态 影响到 给下一个图形设置的状态,我们可以用
context.beginPath() 重新规划一个路径
context.closePath() 结束当前的路径(在api函数之前调用)
将上个图形的状态设置包裹起来,表明其中的状态只对该图形起作用。
注意:
当我们绘制的路径是一条不封闭的路径时,如果我们调用了closePath(),那么closePath()会自动将我们绘制的路径的首尾用线段连接起来。
所以可以只使用beginPath() 而不使用closePath();
var context = canvas.getContext('2d');
//使用context绘图
context.beginPath();
context.moveTo(512,200);
context.lineTo(612,300);
context.lineTo(500,500);
context.lineTo(512,200);
context.fillStyle = 'rgb(2,100,30)';
context.fill();
context.lineWidth = 5;
context.strokeStyle = '#005588';
context.closePath();
context.stroke();
context.beginPath();
context.moveTo(200,100);
context.lineTo(700,600);
context.strokeStyle='black';
context.closePath();
context.stroke();
0x05 绘制点:
//绘制点
ctx.beginPath();
ctx.lineWidth = 1;
//线的颜色
ctx.strokeStyle = 'blue';
//线的起点
ctx.moveTo(500,100);
//终点坐标
ctx.lineTo(501,101);
//绘制线段
ctx.stroke();
0x06 绘制弧线:
/**
* 绘制弧线
* @ centerx 圆心x坐标
* @ centery 圆心y坐标
* @ radius 半径
* @ startingAngle 起始弧度,0弧度为x轴正半轴的位置
* @ endingAngle 终点弧度
* @ false 顺时针绘制 true 逆时针绘制
*/
context.arc(
centerx,centery,radius,
startingAngle,endingAngle,
anticlockwise = false
)
因为canvas是以向下为y轴的正半轴,所以90度位于正下方。
绘制弧形:调用closePath() 和不调用closePath() 的区别
效果:
代码:
<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
for(var i=1;i<10;i++){
ctx.beginPath();
ctx.arc(50+100*i,100,30,0,2*Math.PI*i/10);
ctx.lineWidth= 1;
ctx.strokeStyle = '#003388';
ctx.stroke();
}
for(var i=1;i<10;i++){
ctx.beginPath();
ctx.arc(50+100*i,200,30,0,2*Math.PI*i/10);
ctx.lineWidth= 1;
ctx.strokeStyle = '#003388';
ctx.closePath();
ctx.stroke();
}
</script>
0x07 绘制圆形:
//绘制圆形
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = 'green';
ctx.fillStyle = 'red';
/**
*@ x轴坐标
*@ y轴坐标
*@ 半径
*@ 起始角度
*@ 结束角度
*/
ctx.arc(400,400,50,0,2 * Math.PI);
// 绘制圆形的边框
ctx.stroke();
// 绘制圆形的填充色
ctx.fill();
0x08 Demo:绘制七巧板
效果:
思路:
先准备好七巧板每一片的各个点的坐标和颜色
然后写一个draw函数,作用是画出一片。
然后只要循环调用draw函数即可
代码:
<canvas id="canvas" width='1024' height='600'></canvas>
<script>
function draw(piece,cxt){
cxt.beginPath();
cxt.moveTo(piece.p[0].x,piece.p[0].y);
for(var i =1;i<piece.p.length;i++){
cxt.lineTo(piece.p[i].x,piece.p[i].y);
}
cxt.fillStyle = piece.color;
cxt.fill();
cxt.closePath();
}
var tangram =[
{
p:[
{x:0,y:0},
{x:800,y:0},
{x:400,y:400}
],
color:'#caff67'
},
{
p:[
{x:0,y:0},
{x:400,y:400},
{x:0,y:800}
],
color:'#67becf'
},
{
p:[
{x:800,y:0},
{x:800,y:400},
{x:600,y:600},
{x:600,y:200}
],
color:'#ef3d61'
},
{
p:[
{x:600,y:200},
{x:600,y:600},
{x:400,y:400}
],
color:'#f9f51a'
},
{
p:[
{x:400,y:400},
{x:600,y:600},
{x:400,y:800},
{x:200,y:600}
],
color:'#a594c0'
},
{
p:[
{x:200,y:600},
{x:400,y:800},
{x:0,y:800}
],
color:'#fa8ecc'
},
{
p:[
{x:800,y:400},
{x:800,y:800},
{x:400,y:800}
],
color:'#f6ca29'
}
]
var canvas = document.getElementById("canvas");
if(canvas.getContext('2d')){
var context = canvas.getContext('2d');
for(var i =0;i<tangram.length;i++){
draw(tangram[i],context);
}
}else{
alert("当前浏览器不支持canvas,请更换浏览器后再试");
}
</script>
0x09 实现电子时钟:
效果:
思路:
格子系统
代码:
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]
]//:
];
时钟.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="js/digit.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<style>
/* #canvas{
border:1px solid black;
position:absolute;
left:50%;
top:50%;
transform: translate(-50%,-50%);
} */
html,body{
height:100%;
}
</style>
</head>
<body>
<canvas id='canvas' style='height:100%' ></canvas>
</body>
<script>
var WINDOW_WIDTH = 1024;
var WINDOW_HEIGHT = 768;
//小球半径
var RADIUS = 8;
//每个数字距离画布上边距的距离
var MARGIN_TOP = 60;
//第一个数字距离画布左边距的距离
var MARGIN_LEFT = 30;
//倒计时
//js中月份比较特别,0表示1月,所以6月就是5
// const endTime = new Date(2020,5,18,12,0,0);
//1小时学习计时器
//当前时间
var endTime = new Date();
//当前时间之后的一个小时
//setTime接收的参数为时间戳,getTime获取当前时间的时间戳
endTime.setTime(endTime.getTime() + 3600000)
var curShowTimeSeconds = 0;
var balls = [];
const colors = [
'#33B5E5','#0099CC','#AA66CC','#9933CC','#99CC00',
'#669900','#FFBB33','#FF8800','#FF4444','#C000C'
]
//返回当前应该显示时间,即倒计时时间,单位为秒
function getCurrentShowTimeSeconds(){
var curTime = new Date();
var ret = curTime.getHours() * 3600 + curTime.getMinutes() *60 +curTime.getSeconds();
// 保证倒计时结束时屏幕显示为0
return ret >= 0? ret:0;
}
//负责数据的改变
function update(){
var nextShowTimeSeconds = getCurrentShowTimeSeconds();
var next_hours = parseInt(nextShowTimeSeconds/3600);
var next_minutes = parseInt((nextShowTimeSeconds - next_hours*3600)/60);
var next_seconds = parseInt(nextShowTimeSeconds % 60);
var cur_hours = parseInt(curShowTimeSeconds/3600);
var cur_minutes = parseInt((curShowTimeSeconds - cur_hours*3600)/60);
var cur_seconds = parseInt(curShowTimeSeconds % 60);
if(next_seconds != cur_seconds){
//如果小时的十位数发生了改变,就在十位数所在的位置添加小球
if(parseInt(cur_hours/10)!=parseInt(next_hours/10)){
addBalls(MARGIN_LEFT+0,MARGIN_TOP,parseInt(cur_hours/10));
}
//如果小时的个位数发生了变化,就在个位数所在的位置添加小球
if(parseInt(cur_hours%10)!=parseInt(next_hours%10)){
addBalls(MARGIN_LEFT+15*(RADIUS+1),MARGIN_TOP,parseInt(cur_hours%10));
}
//对分钟
if(parseInt(cur_minutes/10)!=parseInt(next_minutes/10)){
addBalls(MARGIN_LEFT+39*(RADIUS+1),MARGIN_TOP,parseInt(cur_minutes/10));
}
if(parseInt(cur_minutes%10)!=parseInt(next_minutes%10)){
addBalls(MARGIN_LEFT+54*(RADIUS+1),MARGIN_TOP,parseInt(cur_minutes%10));
}
//对秒钟
if(parseInt(cur_seconds/10)!=parseInt(next_seconds/10)){
addBalls(MARGIN_LEFT+78*(RADIUS+1),MARGIN_TOP,parseInt(cur_seconds/10));
}
if(parseInt(cur_seconds%10)!=parseInt(next_seconds%10)){
addBalls(MARGIN_LEFT+93*(RADIUS+1),MARGIN_TOP,parseInt(cur_seconds%10));
}
curShowTimeSeconds = nextShowTimeSeconds;
}
//对已经存在彩色小球进行更新
updateBalls();
}
//更新小球数组中 小球信息
function updateBalls(){
for(var i=0;i<balls.length;i++){
balls[i].centerX += balls[i].vx;
balls[i].centerY += balls[i].vy;
balls[i].vy += balls[i].g;
//地板的碰撞检测
if(balls[i].centerY >= WINDOW_HEIGHT-RADIUS){
balls[i].centerY = WINDOW_HEIGHT - RADIUS;
balls[i].vy = - balls[i].vy * 0.75;
}
}
//记录画布中的小球的数量
var cnt =0;
for(var i=0;i<balls.length;i++){
if(balls[i].centerX + RADIUS >0 && balls[i].centerX + RADIUS <WINDOW_WIDTH){
balls[cnt++] = balls[i];
}
}
//将画布外的小球从balls中删除
while(balls.length > Math.min(300,cnt)){
balls.pop();
}
}
//添加彩色的小球
function addBalls(x,y,num){
for(var i =0;i<digit[num].length;i++){
for(var j=0;j<digit[num][j].length;j++){
if(1 == digit[num][i][j]){
var aBall ={
centerX:x + j*2*(RADIUS+1)+(RADIUS+1),
centerY:y + i*2*(RADIUS+1)+(RADIUS+1),
g:1.5+Math.random(),//1.5+ 0到1之间的一个随机数
vx:Math.pow(-1,Math.ceil(Math.random()*1000))*4,//-1的多少次方 * 4 多少次方呢 在0到1000之前取一个随机数然后取整,最终结果为正负四
vy:-5,
color:colors[Math.floor(Math.random()*colors.length)] //随机索引 0-9 来 获取随机颜色
}
balls.push(aBall)
}
}
}
}
/*
绘制数字
x 数字盒子 左上角 的x坐标
y 数字盒子 左上角 的y坐标
num 绘制什么数字
ctx canvas对象
*/
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][j].length;j++){
if(1 == digit[num][i][j]){
//绘制小球
ctx.beginPath();
//算出小球球心的位置
var centerX = x + j*2*(RADIUS+1)+(RADIUS+1);
var centerY = y + i*2*(RADIUS+1)+(RADIUS+1);
ctx.arc(centerX,centerY,RADIUS,0,2*Math.PI);
ctx.closePath();
ctx.fill();
}
}
}
}
//绘制函数(绘制数字,绘制彩色小球)
function render(ctx){
// 防止之前的图像 和 新的图像叠加在一起
//对一个矩形空间的图像进行刷新
ctx.clearRect(0,0,WINDOW_WIDTH,WINDOW_HEIGHT);
var date = new Date();
var hours = parseInt(curShowTimeSeconds/3600);
var minutes = parseInt((curShowTimeSeconds - hours*3600)/60);
var seconds = parseInt(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);
//遍历小球数组,绘制每个小球
for(var i =0;i<balls.length;i++){
ctx.fillStyle = balls[i].color;
ctx.beginPath();
ctx.arc(balls[i].centerX,balls[i].centerY,RADIUS,0,2*Math.PI,true);
ctx.closePath();
ctx.fill();
}
}
window.onload = function(){
//必须先设置body的高度为100%,才能获得正确的高度
WINDOW_WIDTH =document.body.clientWidth;
WINDOW_HEIGHT =document.body.clientHeight;
// console.log(document.body.clientHeight);
MARGIN_LEFT = Math.round(WINDOW_WIDTH / 10);
MARGIN_TOP = Math.round(WINDOW_HEIGHT /5);
//因为最后一个数字距离左边距的位置是93倍的(RADIUS+1)一个数字占据的空间是15个(RADIUS+1)
RADIUS = Math.round(WINDOW_WIDTH * 8/10 /108 )-1
var canvas = document.getElementById('canvas');
canvas.width = WINDOW_WIDTH;
canvas.height = WINDOW_HEIGHT;
ctx = canvas.getContext('2d');
curShowTimeSeconds = getCurrentShowTimeSeconds();
/**
* 每帧要执行的函数
* 每帧多少毫秒,帧率 = 1s / 每帧的时间
*/
setInterval(
function(){
render(ctx);
update();
},20
)
}
</script>
</html>
0x0A 总结:
context.clearRect(x,y,width,height) 清除一个矩形区域的内容
context.canvas 得到context上下文属于的画布
context.canvas.wdith 获取画布的大小
context.canvas.height