整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

canvas-绘制圆形

制圆形和圆弧都使用arc()方法:

Arc(x, y, r, startAngle, endAngle, antiClockwise)

Arc()方法是从(x,y)位置画出半径为r的圆形;

startAngle及endAngle决定圆弧的起点角度与终点角度; startAngle: 起点是X轴方向, 即3点钟方向。

antiClockwise参数决定是否已逆时针方向绘图,逆时针为true,顺时针为false。


在canvas中所有涉及角度的坐标系有两点注意的:

0弧度的方向是正右方向。

弧度的顺时针和逆时针:


角度与弧度转公式

角度转弧度 π/180×角度

弧度变角度 180/π×弧度


将角度转换为弧度,计算公式可以参考下面的公式:

radians = degrees * Math.PI/180

degrees 角度

radians 弧度

圆的弧度(360):360 * Math.PI/180 = Math.PI * 2 = 6.28

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>绘制圆</title>
<script type="text/javascript">
function drawCircle(){
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
//开始绘图--圆
ctx.strokeStyle="#336600"; //设定边框颜色
ctx.lineWidth=10; //设定线条宽度 10px
ctx.fillStyle="#FF0000"; //设定填充颜色
ctx.beginPath(); //路径开始
ctx.arc(100,100,50,0,Math.PI*2,true); //起始点(100,100)半径50的圆
ctx.closePath(); //关闭路径
ctx.fill(); //绘出填满图形
ctx.stroke(); //绘出边框
}
</script>
<!--style标记内是CSS语法-->
<style type="text/css">
Canvas { border: 1px solid black; } //将框线设为1px
</style>
</head>
<body>
<input type="button" value=" 画 圆 " onclick="drawCircle();"><br />
<canvas id="myCanvas" width="400" height="200"></canvas>
</body>
</html>

绘制圆弧

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>绘制圆弧</title>
<style type="text/css">
canvas{
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="600" height="400"></canvas>
</body>
<script type="text/javascript">
var canvas = document.querySelector("canvas")
var ctx = canvas.getContext("2d");
ctx.beginPath(); //开始绘制路径
// ctx.arc(100, 100, 60, 0, 6.28, false);
// ctx.arc(100, 100, 60, 0, -6.28, true);
ctx.arc(100, 100, 60, 0, Math.PI * 2, false);
ctx.stroke(); //显示路径线
ctx.fillStyle = "pink";   //设置填充颜色
ctx.fill(); //填充颜色
</script>
</html>


绘制笑脸

么是Canvas

<canvas> 是HTML中的一个元素,它可被用来通过 JavaScript(Canvas API 或 WebGL API)绘制图形及图形动画。

Canvas API 提供了一个通过 JavaScriptHTML<canvas> 元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。

<canvas>标签本身没有绘图能力,它仅仅是图形的容器。在HTML,一般通过Javascript语言来完成实际的操作。

创建HTML页面并添加绘图容器

本文通过Javascript操作Canvas制作一个简单的显示当前时间的动画时钟,了解和学习简单的canvas用法,仅以抛砖引玉。

首先创建一个HTML文件,为了方便管理,使用一个div标签包裹两个canvas标签,并加上一些简单的css样式。

<!doctype html>
<html lang="zh-cn">
<head><title>Canvas绘制动画时钟</title>
<style>
html,body{margin:0;padding:0}
#clockWrap {
	position: relative;
}
canvas {
	position: absolute;
}
#clock-ui {
	z-index: 2;
}
#clock-plate {
	z-index: 1;
}
</style>
</head>
<body>
  <div id="clockWrap">
  <canvas id="clock-plate"></canvas>
  <canvas id="clock-ui"></canvas>
</div>
<script></script>
</body></html>

本示例中使用了两个canvas标签(为什么使用两个,一个不是更简单吗?),一个用于绘制钟面,一个根据当前时间实时显示和更新时针、分针和秒针的动态指向。好了,话不多说,开干。

绘制钟面刻度

一个简单的时钟,可以分为钟面上的刻度和指针。其中刻度和12个数字是固定的,我们可以将它们绘制在当作背景的canvas上(示例中id为clock-plate的canvas)。

(1)要使用canvas,首先必须通过容器获取渲染上下文:

var $=function(id){return document.querySelector(id);}//这个函数只是为了方便获取dom元素
const canvasbg=$("#clock-plate"),
      canvas=$("#clock-ui"),
      ctx = canvasbg.getContext("2d"),//背景容器上下文
      ctxUI = canvas.getContext("2d");//指针容器上下文,后面代码要用
//定义画布宽度和高度,时钟圆直径,并设置画布大小
const oW=1000,oH=800,cW=400,r=cW/2,oX=oW/2,oY=oH/2;
canvas.width=oW;
canvas.height=oH;
canvasbg.width=oW;
canvasbg.height=oH;

(2)画钟的边框,为了好看,这里画两个圈:

 //画出时钟外圆框
  ctx.lineWidth = 12;
	ctx.beginPath();
	ctx.arc(oX, oY, r+14, 0, 2 * Math.PI);
	ctx.stroke();
	ctx.closePath();
	ctx.lineWidth = 8;
	//画出时钟内圆框(刻度圈)
	ctx.beginPath();
	ctx.arc(oX, oY, r, 0, 2 * Math.PI);
	ctx.stroke();
	ctx.closePath();
	ctx.beginPath();

边框效果图

(3)绘制刻度线和数字,可以利用三角函数计算出每个刻度的坐标:

利用三角函数计算刻度线的坐标位置

钟面上有12个大格,从正上方12开始,它们的度数分别是270,300,330,0,30,60,90,120,150,180,210,240。然后利用JS的Math.sin和Math.cos分别计算出各大格的坐标。注意:js中Math.sin()和Math.cos()的参数不是角度数弧度。可以使用Math.PI/180*角度来转化,比如将30度转换成弧度=Math.PI/180*30

//绘制钟表中心点
	ctx.beginPath();
	ctx.arc(oX, oY, 8, 0, 2 * Math.PI);//圆心
	ctx.fill();
	ctx.closePath();
	//设置刻度线粗细度
	ctx.lineWidth = 3;
	//设置钟面12个数字的字体、大小和对齐方式
	ctx.font = "30px serif";
	ctx.textAlign="center";
	ctx.textBaseline="middle";
	var kdx,kdy;
	//绘制12个大刻度和12个数字
	//为方便计算,先定义了0-11这12个刻度对应的度数,也可以直接定义对应的弧度。
	const hd=Math.PI/180,degr=[270,300,330,0,30,60,90,120,150,180,210,240];
	for(var i=0;i<12;i++){
		kdx=oX+Math.cos(hd*degr[i])*(r-3);
		kdy=oY+Math.sin(hd*degr[i])*(r-3);
		ctx.beginPath();
		ctx.arc(kdx, kdy, 6, 0, 2 * Math.PI);//画圆形大刻度
		ctx.fill();
    //绘制刻度对应的数字
		ctx.strokeText(i==0? 12 : i,oX+Math.cos(hd*degr[i])*(r-24),oY+Math.sin(hd*degr[i])*(r-24));
		ctx.closePath();
	}
	
	//绘制小刻度
	ctx.lineWidth = 2;
	for(var i=0;i<60;i++){
		if(i % 5 == 0) continue;//跳过与刻度重叠的刻度
		x0=Math.cos(hd*i*6);
		y0=Math.sin(hd*i*6);
		ctx.beginPath();
		ctx.moveTo(oX+x0*(r-10), oY+y0*(r-10)); 
		ctx.lineTo(oX+x0*r, oY+y0*r); //画短刻度线
		ctx.stroke(); 
		ctx.closePath();
	}

效果如图:

钟面效果图

(4)根据当前时间绘制指针

习惯上,时针粗短,分针略粗而长,秒针细长。为加大区别,示例中秒针细长并且绘制成红色。

function drawHp(i){//绘制时针
	const x0=Math.cos(hd*i*30),y0=Math.sin(hd*i*30);
	drawPointer(oX,oY,oX+x0*(r-90),oY+y0*(r-90),10,"#000000");
}
function drawMp(i){//绘制分针
	const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
	drawPointer(oX,oY,oX+x0*(r-60),oY+y0*(r-60),5,"#000000");
}
function drawSp(i){//绘制秒针
	const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
	drawPointer(oX,oY,oX+x0*(r-20),oY+y0*(r-20),2,"#FF0000");
}
//抽取出绘制三种指针时共同的部分,注意指针绘制在渲染上下文ctxUI中
function drawPointer(ox,oy,tx,ty,width,color){
	ctxUI.strokeStyle = color;
	ctxUI.lineCap = "round";
	ctxUI.lineWidth = width;
	ctxUI.beginPath();
	ctxUI.moveTo(ox, oy);
	ctxUI.lineTo(tx,ty);
	ctxUI.stroke();
	ctxUI.closePath();
}

现在已经有了绘制三种指针的方法,参数是当前时间的时、分和秒,将根据它们的值确定指针的坐标。不过,因为使用的是默认的convas坐标体系,0值实际指向3的位置,需要小小的修正一下。

window.requestAnimationFrame(function fn(){
		var d = new Date();
		ctxUI.clearRect(0,0,oW,oH);
		//度数从0开始,而0在3刻度(15分/秒位置),修正为全值减15,如果小于0则修正回来
    var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;
		hour=hour>11? hour-15 : hour-3;
		drawHp(hour>=0? hour : 12+hour);
		drawMp(minute>=0? minute : 60+minute);
		drawSp(second>=0? second : 60+second);
		window.requestAnimationFrame(fn);
});

接下来,调用window.requestAnimationFrame,在其中绘制并更新指标的位置。看看效果如何:

指针绘制情况与实际时间相符

貌似效果有了,截图时电脑上的时间是10时17分,指针绘制上,时针指向10时,分针指向17。嗯,感觉有点别扭?对了,时针和分针怎么是端端正正地指向它们的整时整分刻度上呢?实际钟表上时针和分针是展示动态进度的,此时时针应该越过10时的位置才对。没关系,我们在绘制时针和分针时别点东西,让它的角度值加上分针和秒针的值试试。

//修改后的绘制三种指针的方法
function drawHp(i,f,m){//绘制时针,参数:时,分,秒
	const x0=Math.cos(hd*(i+(f/60)+(m/600))*30),y0=Math.sin(hd*(i+(f/60)+(m/600))*30);
	drawPointer(oX,oY,oX+x0*(r-90),oY+y0*(r-90),10,"#000000");
}
function drawMp(i,f){//绘制分针,参数,分,秒
	const x0=Math.cos(hd*(i+(f/60))*6),y0=Math.sin(hd*(i+(f/60))*6);
	drawPointer(oX,oY,oX+x0*(r-60),oY+y0*(r-60),5,"#000000");
}
function drawSp(i){//绘制秒针
	const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
	drawPointer(oX,oY,oX+x0*(r-20),oY+y0*(r-20),2,"#FF0000");
}

再来看看效果,嗯,立竿见影呀:

指针指向更合理了

到此为止,canvas绘制一个简易时钟就完成了。下面继续优化一下。刚才使用requestAnimationFrame方法即时更新绘制情况。这个方法与刷新率有关,看看mdn上面怎么说的:

window.requestAnimationFrame() 方法会告诉浏览器你希望执行一个动画。它要求浏览器在下一次重绘之前,调用用户提供的回调函数。

对回调函数的调用频率通常与显示器的刷新率相匹配。虽然 75hz、120hz 和 144hz 也被广泛使用,但是最常见的刷新率还是 60hz(每秒 60 个周期/帧)。为了提高性能和电池寿命,大多数浏览器都会暂停在后台选项卡或者隐藏的 <iframe> 中运行的 requestAnimationFrame()。

本示例中,更新指针的位置并不需要很高的刷新频率,可以通过节流进行一下优化:

var fps = 5, fpsInterval = 1000 / fps,lastTime = new Date().getTime(); //记录上次执行的时间
function runStep() {
    requestAnimationFrame(runStep);
    var d=new Date(),now = d.getTime()
    var elapsed = now - lastTime;
    if (elapsed > fpsInterval) {
				ctxUI.clearRect(0,0,oW,oH);
        lastTime = now - (elapsed % fpsInterval); 
			//度数从0开始,而0在3刻度(15分/秒位置),修正为全值-15,如果小于0则用60减回
        var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;//console.log(d.getSeconds(),second);
        hour=hour>11? hour-15 : hour-3;
        drawHp(hour>=0? hour : 12+hour,minute+15,second+15);
        drawMp(minute>=0? minute : 60+minute,second+15);
        drawSp(second>=0? second : 60+second);
    }
}
runStep();

当然,实现时钟的方法是很多,比如可以使用画布的旋转(rotate方法)来实现指针的动态转动等等。

完整HTML+JS源码:

.说明:

推荐指数:★★★★

通俗易懂,小白一看就会,高手请飘过。

学python也是需要懂一点Html、Css、JavaScript的基础知识的。

建议使用vscode编辑器。


2.效果图1


3.代码:保存为html,用浏览器打开即可。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>圆形百分比进度条效果</title>
		<style type="text/css">
		*{
			margin: 0;
			padding: 0;
		}
		.bg{
			width: 200px;
			height: 200px;
			border-radius: 100%;
			background: #ccc;
			position: relative;
			margin: 20px auto;
		}
		.circle-right, .circle-left, .mask-right, .mask-left{
			width: 200px;
			height: 200px;
			border-radius: 100%;
			position: absolute;
			top: 0;
			left: 0;
		}
		.circle-right, .circle-left{
			background: skyblue;
		}
		.mask-right, .mask-left{
			background: #ccc;
		}
		.circle-right, .mask-right{
			clip: rect(0,200px,200px,100px);
		}
		.circle-left, .mask-left{
			clip: rect(0,100px,200px,0);
		}
		.text{
			width: 160px;
			height: 160px;
			line-height: 160px;
			text-align: center;
			font-size: 34px;
			color: deepskyblue;
			border-radius: 100%;
			background: #fff;
			position: absolute;
			top: 20px;
			left: 20px;
		}
		</style>
	</head>
	<body>
		<div class="bg">
			<div class="circle-right"></div>
			<!--100%是显示百分数,动态显示由0~100-->
			<div class="text">100%</div> 

		</div>
		
		<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
		<script type="text/javascript">
			$(function(){				
				//获取百分比值
				var num = parseInt($('.text').html());
				
				//通过计时器来显示过渡的百分比进度
				var temp = 0;
				var timer = setInterval(function(){
					calculate(temp);
					//清除计时器结束该方法调用
					if(temp == num){
						clearInterval(timer);
					}
					temp++;
				},30)

				//改变页面显示百分比
				function calculate(value){
					//改变页面显示的值
					$('.text').html(value + '%');
					
					//清除上次调用该方法残留的效果
					$('.circle-left').remove();
					$('.mask-right').remove();
					
					//当百分比小于等于50
					if(value <= 50){
						var html = '';
						
						html += '<div class="mask-right" style="transform:rotate('+ (value * 3.6) +'deg)"></div>';
						
						//元素里添加子元素
						$('.circle-right').append(html);
					}else{
						value -= 50;
						var html = '';
						
						html += '<div class="circle-left">';
						html += '<div class="mask-left" style="transform:rotate('+ (value * 3.6) +'deg)"></div>';
						html += '</div>';
						
						//元素后添加元素
						$('.circle-right').after(html);
					}
				}
			})
		</script>
		
	</body>
</html>

4.拆分法:把一个含有css和js(JavaScript)的html,拆掉三个文件,一个叫cirbar1.html,另外的叫:cirbar1.css和cirbar1.js文件,放在同一个文件夹内。这是./的意思,也可以指定文件夹。

4.1 cirbar1.html的代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>圆形百分比进度条效果v2</title>
		<!--如果是style type="text/css"这种直接在html中设置css,注意起始是*,不是点-->
		<!--style type="text/css"-->
		<link href="./cirbar1.css" rel="stylesheet" />
	</head>
	<body>
		<div class="bg">
			<div class="circle-right"></div>
			
			<div class="text">100%</div> 
		</div>
		<!--这个外部js文件=jquery.min.js,不能少,否则不能动了-->
		<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"--></script>
        <!--同样的,把写在html的js,新建一个cirbar1.js,复制下去,保存,这样子简洁很多-->
		<!--script type="text/javascript"-->
		<script src="./cirbar1.js"></script>
	</body>
</html>

4.2 cirbar1.css的代码:

		*{
			margin: 0;
			padding: 0;
		}
		.bg{
			width: 200px;
			height: 200px;
			border-radius: 100%;
			background: #ccc;
			position: relative;
			margin: 20px auto;
		}
		.circle-right, .circle-left, .mask-right, .mask-left{
			width: 200px;
			height: 200px;
			border-radius: 100%;
			position: absolute;
			top: 0;
			left: 0;
		}
		.circle-right, .circle-left{
			background: skyblue;
		}
		.mask-right, .mask-left{
			background: #ccc;
		}
		.circle-right, .mask-right{
			clip: rect(0,200px,200px,100px);
		}
		.circle-left, .mask-left{
			clip: rect(0,100px,200px,0);
		}
		.text{
			width: 160px;
			height: 160px;
			line-height: 160px;
			text-align: center;
			font-size: 34px;
			color: deepskyblue;
			border-radius: 100%;
			background: #fff;
			position: absolute;
			top: 20px;
			left: 20px;
		}

4.3 cirbar1.js的代码:

			$(function(){				
				//获取百分比值
				var num = parseInt($('.text').html());
				
				//通过计时器来显示过渡的百分比进度
				var temp = 0;
				var timer = setInterval(function(){
					calculate(temp);
					//清除计时器结束该方法调用
					if(temp == num){
						clearInterval(timer);
					}
					temp++;
				},30)

				//改变页面显示百分比
				function calculate(value){
					//改变页面显示的值
					$('.text').html(value + '%');
					
					//清除上次调用该方法残留的效果
					$('.circle-left').remove();
					$('.mask-right').remove();
					
					//当百分比小于等于50
					if(value <= 50){
						var html = '';
						
						html += '<div class="mask-right" style="transform:rotate('+ (value * 3.6) +'deg)"></div>';
						
						//元素里添加子元素
						$('.circle-right').append(html);
					}else{
						value -= 50;
						var html = '';
						
						html += '<div class="circle-left">';
						html += '<div class="mask-left" style="transform:rotate('+ (value * 3.6) +'deg)"></div>';
						html += '</div>';
						
						//元素后添加元素
						$('.circle-right').after(html);
					}
				}
			})

==============================

再来一个,不用外部js文件的圆形进度条

顺带回顾一下相关知识。

==============================

5.效果图


6.三个文件,放在同一个文件夹中

6.1 cirbar.html代码:

<!--第1步---声明html5-->
<!DOCTYPE html> 

<!--第2步---html大框架-->
<html lang="en">

<!--第2-1步---head部分-->
<head>
    <!--第2-1-1步---meta部分:声明字符编码格式:utf-8-->
    <!--注意与python的第一行声明一样:# -*- coding: utf-8 -*-->
    <meta charset="utf-8" />
    <!--第2-1-2步---标题名称-->
    <title>圆形进度条v1</title>
    <!--注意:./代表同一个文件夹下,也就是css和js文件与本html文件放在同一个文件夹下-->
    <!--第2-1-3步---导入css文件-->
    <link href="./cirbar.css" rel="stylesheet" />
</head>

<!--第2-2步---body部分-->
<body>
    <!--第2-2-1步---定义画布部分,注意没有逗号隔开,大小设置-->
    <canvas class="canvas" id="canvas" width="400" height="400"></canvas>
    <!--第2-2-2步---导入js文件部分-->
    <script src="./cirbar.js"></script>

<!--注意收尾-->
</body>
<!--注意收尾-->
</html>

6.2 cirbar.css代码:

.canvas {
            /*画布的背景颜色设置为:黑色*/
            background:#303030;
            
        }

6.3 cirbar.js代码:

window.onload = function () {
            var canvas = document.getElementById('canvas'),  //获取canvas元素
                context = canvas.getContext('2d'),  //获取画图环境,指明为2d
                centerX = canvas.width / 2,   //Canvas中心点x轴坐标
                centerY = canvas.height / 2,  //Canvas中心点y轴坐标
                rad = Math.PI * 2 / 100, //将360度分成100份,那么每一份就是rad度
                speed = 0.1; //加载的快慢就靠它了
            //绘制红色外圈
            function blueCircle(n) {
                context.save();
                context.strokeStyle = "	#1E90FF"; //随百分数转动的外圈的颜色为蓝色
                context.lineWidth = 3; //设置线宽
                context.beginPath(); //路径开始
                //注意-为顺时针,+为逆时针
                //用于绘制圆弧context.arc(x坐标,y坐标,半径,起始角度,终止角度,顺时针/逆时针)
                //context.arc(centerX, centerY, 50, Math.PI / 2, Math.PI / 2 - n * rad, false); 
                context.arc(centerX, centerY, 50, -Math.PI / 2, -(Math.PI / 2 - n * rad), false); 
                context.stroke(); //绘制
                context.closePath(); //路径结束
                context.restore();
            }
            //绘制白色外圈,初始的白色外圈
            function whiteCircle() {
                context.save();
                context.beginPath();
                // 下面百分数的字体颜色设置后上面的外圈的颜色竟然保持一样
                //context.strokeStyle = "#F8F8FF";
                context.arc(centerX, centerY, 50, 0, Math.PI * 2, false);
                context.stroke();
                context.closePath();
                context.restore();
            }
            //百分比文字绘制
            function text(n) {
                context.save(); //save和restore可以保证样式属性只运用于该段canvas元素
                context.strokeStyle = "#7FFF00";//设置中间动态百分数的颜色
                context.font = "25px Arial"; //设置字体大小和字体
                context.textAlign = 'center';//字体水平居中
                context.textBaseline = 'middle';//字体垂直居中
                //绘制字体,并且指定位置
                context.strokeText(n.toFixed(0) + "%", centerX, centerY);
                context.stroke(); //执行绘制
                context.restore();
            }
            //循环获取
            (function drawFrame() {
                window.requestAnimationFrame(drawFrame, canvas);
                context.clearRect(0, 0, canvas.width, canvas.height);
                whiteCircle();
                text(speed);
                blueCircle(speed);
                if (speed < 100) {
                    //1可从后台获取值,也是从0~100,step为1,代表速度
                    speed += 1;
                }
            }());
        }

注意到6.3js文件与4.3js文件的区别了么?一个有:window.onload =,一个没有,且只有$和小括号,因为外部的就是文件定义了一部分功能。