1、canvas
- canvas是h5添加的新标签,所以在低版本的ie中可能不兼容。
- canvas是画布,可以自由绘制图形。
- canvas是原生js对象,不建议使用jq
2、canvas的基础使用和介绍
1.基础使用
使用canvas标签添加宽高等属性
canvas标签是一个行内标签
<html>
<canvas height='400px' width='400px'></canvas>
</html>
<style>
canvas{
//可设置宽高,边框外边距等属性
}
</style>
低版本不兼容问题
添加提示语
<canvas height='400px' width='400px'>该版本浏览器不支持canvas标签,请更新浏览器或者使用火绒</canvas>
3、canvas绘制直线
一、直线的基础绘制方法
坐标点从左上角的点为原点__
1.获取canvas节点
2.建立canvas路径上下文
3.开启一条路径
4.路径开始点
5.路径结束点
6.路径添加颜色
7.结束路径
//固定步骤
var canvas=document.getElementById('canvas') //获取canvas节点
var c=canvas.getContext('2d') //建立canvas路径上下文
c.beginPath() //开启一条路径 beginpath()自带关闭上一个线的功能
c.moveTo(100,100) //路径开始点
c.lineTo(200,200) //路径结束点
//在着色之前设置颜色
c.strokeStyle='green' //通用颜色属性
//设置线宽
c.lineWidth=5;
c.stroke() //路径添加颜色
c.closePath() //结束路径
//平移中心点
can.translate(width / 2, height / 2)
二、画多彩直线和虚线
如果需要绘制多条颜色不同的直线,每绘制一条线就设置一次路径、坐标点、颜色
//固定步骤
var canvas=document.getElementById('canvas') //获取canvas节点
var c=canvas.getContext('2d') //建立canvas路径上下文
//第一条直线
c.beginPath() //开启一条路径
c.moveTo(100,200) //路径开始点
c.lineTo(200,200) //路径结束点
//在着色之前设置颜色
c.strokeStyle='green' //通用颜色属性
//设置线宽
c.lineWidth=5;
c.stroke() //路径添加颜色
c.closePath() //结束路径
//第二条直线
c.beginPath() //开启一条路径
c.moveTo(200,200) //路径开始点
c.lineTo(200,100) //路径结束点
//在着色之前设置颜色
c.strokeStyle='green' //通用颜色属性
//设置线宽
c.lineWidth=5;
c.stroke() //路径添加颜色
c.closePath() //结束路径
//第三条直线
c.beginPath() //开启一条路径
c.moveTo(200,100) //路径开始点
c.lineTo(100,100) //路径结束点
//在着色之前设置颜色
c.strokeStyle='green' //通用颜色属性
//设置线宽
c.lineWidth=5;
c.stroke() //路径添加颜色
c.closePath() //结束路径
//第四条直线
c.beginPath() //开启一条路径
c.moveTo(100,100) //路径开始点
c.lineTo(100,200) //路径结束点
//在着色之前设置颜色
c.strokeStyle='green' //通用颜色属性
//设置线宽
c.lineWidth=5;
c.stroke() //路径添加颜色
c.closePath() //结束路径
这样代码的重复性过高,所以我们可以将一条直线的代码封装起来,得到如下代码
//c为绘制的canvas
function drawLine(x1, y1, x2, y2, color, width) { //x1:开始横坐标,y1:开始纵坐标,x2:结束横坐标,y2:结束纵坐标,color:线的颜色,width:线宽
c.beginPath() //开启一条路径
c.moveTo(x1, y1) //路径开始点
c.lineTo(x2, y2) //路径结束点
//在着色之前设置颜色
c.strokeStyle = color //通用颜色属性
//设置线宽
c.lineWidth = width;
c.stroke() //路径添加颜色
c.closePath() //结束路径
}
三、直线连用
当我们需要连续使用统一颜色直线,我们可以使用 lineto()方法连用绘制
//固定步骤
var canvas=document.getElementById('canvas') //获取canvas节点
var c=canvas.getContext('2d') //建立canvas路径上下文
c.beginPath() //开启一条路径
c.moveTo(100,200) //路径开始点
c.lineTo(200,200)
c.lineTo(100,200)
c.lineTo(100,100)
c.lineTo(100,200) //路径结束点
//在着色之前设置颜色
c.strokeStyle='green' //通用颜色属性
//设置线宽
c.lineWidth=5;
c.stroke() //路径添加颜色
c.closePath() //结束路径
四、虚线
虚线就是多条直线分割而来,那么我们就可以使用多条直线来构成这个虚线,我们可以利用前面封装的直线函数
//构造一条虚线
drawLine(100,100,105,100,red,2)
drawLine(110,100,115,100,red,2)
drawLine(120,100,125,100,red,2)
drawLine(130,100,135,100,red,2)
//或者直接利用方法
can.setLineDash(5,15) //参数1为:虚线的每段长度,参数2间隔
drawLine(130,100,135,100,red,2)
//可利用for循环构建虚线
for (let i = 0; i < 20; i++) {
drawLine(100 + i * 10, 100, 105 + i * 10, 100, 'red', 2)
}
//如果需要构造斜线,就可以同时改变横纵坐标
for (let i = 0; i < 20; i++) {
drawLine(100 + i * 10, 100 + i * 10, 105 + i * 10, 105 + i * 10, 'red', 2)
}
4、矩形
我们可以绘制一个矩形
can.rect(x,y,width,height) //绘制一个矩形
/***
x :矩形左上角的x坐标
y : 矩形左上角的y坐标
width:矩形的宽度
height:矩形的高度
***/
can.rect(x,y,width,height)
//填充矩形
can.fillstyle='red'
can.fill()
//描述边框
can.strokeStyle='red' //设置矩形边框颜色
can.lineWidth=5 //设置矩形边框宽度
can.stroke() //设置空心矩形
//会存在一个问题,矩形的边框被盖住,所以我们需要先填充在进行描边,切勿搞反
//更多样化的方法
//绘制实心矩形
can.fillStyle = 'red' //矩形填充颜色
can.fillRect(x,y,width,height)
/***
x :矩形左上角的x坐标
y : 矩形左上角的y坐标
width:矩形的宽度
height:矩形的高度
***/
//绘制空心矩形
can.strokeStyle = 'red' //边框颜色
can.lineWidth=5 //设置矩形边框宽度
can.strokeRect(x,y,width,height)
/***
x :矩形左上角的x坐标
y : 矩形左上角的y坐标
width:矩形的宽度
height:矩形的高度
***/
由此我们可以封装好矩形的函数
function drawFill(x, y, width, height, color) { //参数1:矩形的纵坐标,参数2:矩形的横坐标,参数3:矩形的宽度,参数4:矩形的高度,参数5:矩形的颜色
can.fillStyle = color
can.fillRect(x, y, width, height)
}
5、利用直线和矩形画矩形统计图
//固定步骤
var canvas = document.getElementById('canvas') //获取canvas节点
var can = canvas.getContext('2d') //建立canvas路径上下文
//绘制坐标轴
can.beginPath() //开启一条路径
can.moveTo(100, 100) //路径开始点
can.lineTo(100, 400)
can.lineTo(400, 400) //路径结束点
//在着色之前设置颜色
can.strokeStyle = 'green' //通用颜色属性
//设置线宽
can.lineWidth = 1;
can.stroke() //路径添加颜色
can.closePath() //结束路径
function drawFill(x, y, width, height, color) {
can.fillStyle = color
can.fillRect(x, y, width, height)
}
//添加矩形
drawFill(110, 200, 30, 200, 'red')
drawFill(160, 240, 30, 160, 'yellow')
drawFill(210, 390, 30, 10, 'green')
//这样写的效率很低,所以我们可以进一步升级这个函数
function drawFillARR(data, width = 30, mr = 20, color = 'red') {
let x = 100
data.forEach(item => {
can.fillStyle = color
x += width + mr
can.fillRect(x, 400 - item, width, item)
});
}
drawFillARR([100, 20, 310, 20,100])
//这样就只需要传入一个数组中的数组就可以画出一个简单的柱形统计表
//随机生成图表
function drawFillRandom(width = 30, mr = 20, color = 'red') {
let x = 100
let data = []
for (let i = 0; i < 6; i++) {
let height = Math.random() * 280 + 10
data.push(height)
}
data.forEach((item, index) => {
can.fillStyle = color
if (index == 0) {
x += mr
} else {
x += width + mr
}
can.fillRect(x, 400 - item, width, item)
});
}
drawFillRandom()
6、拓展知识
- 生成16进制随机颜色函数
- 生成rgb随机数
//随机生成16进制颜色
let color16='#' + parseInt(Math.random() * 0Xffffff).toString(16)
//随机生成rgb颜色
let rgbaColor = `rgb(${parseInt(Math.random() * 256)},${parseInt(Math.random() * 256)},${parseInt(Math.random() * 256)})`
console.log(rgbaColor);
7、清除画布(重要)
方法:can.clearRect(x,y,width,height)
can.clearRect(x,y,width,height)
/***
x :清除左上角的x坐标
y : 清除左上角的y坐标
width:清除的宽度
height:清除的高度
***/
8、画圆
方法:can.arc(x,y,radius,startAngle,endAngle,counterclockwise)
can.arc(x,y,radius,startAngle,endAngle,counterclockwise)
/***
x:圆的中心点x轴坐标
y: 圆的中心点y轴坐标
radius:圆的半径
startAngle:起始角度
endAngle:结束角度 Math.PI就是180度 改变这个值便可以画出圆弧
counterclockwise:画圆的方向 true为逆时针画,false为顺时针
***/
can.arc(250,250,200,0,Math.PI*2,false) //画圆
can.fillStyle = 'green' //填充设置颜色
can.fill() //填充颜色
can.lineWidth=5 //圆的线宽
can.strokeStyle='red' //线设置颜色
can.stroke() //线添加颜色
计算圆周坐标
x=x+r*cos(角度) 横坐标
y=y+r*sin(角度) 纵坐标
简单练习
画出一个茶杯
<body>
<canvas id="canvas" height="600" width="600"></canvas>
</body>
<script>
//固定步骤
var canvas = document.getElementById('canvas') //获取canvas节点
var can = canvas.getContext('2d') //建立canvas路径上下文
//茶杯
can.rect(200, 300, 200, 200)
can.lineWidth = 4
can.stroke()
can.beginPath()
can.arc(400, 400, 50, Math.PI / 2, Math.PI * 2 * 3 / 4, true)
can.lineWidth = 10
can.stroke()
//气息
can.beginPath()
can.arc(400, 400, 50, Math.PI / 2, Math.PI * 2 * 3 / 4, true)
can.lineWidth = 10
can.stroke()
for (let i = 0; i < 4; i++) {
can.beginPath()
can.arc(230 + i * 40, 200, 20, Math.PI / 2, Math.PI * 2 * 3 / 4, false)
can.lineWidth = 2
can.stroke()
can.beginPath()
can.arc(230 + i * 40, 240, 20, Math.PI / 2, Math.PI * 2 * 3 / 4, true)
can.lineWidth = 2
can.stroke()
}
</script>
9、拓展知识
利用定时器画出统计计时器
<body>
<canvas id="canvas" height="500" width="500"></canvas>
</body>
<script>
//固定步骤
var canvas = document.getElementById('canvas') //获取canvas节点
var can = canvas.getContext('2d') //建立canvas路径上下文
let deg = Math.PI / 180
let count = 0
let timer = setInterval(
function () {
if (count > 360) {
clearInterval(timer)
}
count++
can.beginPath()
can.arc(250, 250, 200, 0, count * deg, false) //画圆
// can.fillStyle = 'green' //填充设置颜色
// can.fill() //填充颜色
// can.lineWidth = 5 //圆的线宽
can.strokeStyle = 'red' //线设置颜色
can.stroke() //线添加颜色
}, 1
)
</script>
10、碰撞检测
逻辑为:碰撞到指定位置,便是加速度相反
<body>
<canvas id="canvas" height="500" width="500"></canvas>
</body>
<script>
//固定步骤
var canvas = document.getElementById('canvas') //获取canvas节点
var can = canvas.getContext('2d') //建立canvas路径上下文
let arc1 = {
x: 20,
y: 20,
r: 20,
xv: 1,
yv: 1
}
let width = 500
let height = 500
setInterval(function () {
can.clearRect(0, 0, width, height) //每次运动完成后清空画布
can.beginPath() //运动完成后清除画笔
if ((arc1.x - arc1.r) < 0 || (arc1.x + arc1.r) >= width) { //制作x轴的弹性碰撞
arc1.xv = -arc1.xv
}
arc1.x += arc1.xv
if ((arc1.y - arc1.r) < 0 || (arc1.y + arc1.r) >= height) { //制作y轴的弹性碰撞
arc1.yv = -arc1.yv
}
arc1.y += arc1.yv
can.arc(arc1.x, arc1.y, arc1.r, 0, Math.PI * 2, false) //画出小球
can.fillStyle = 'green' //填充设置颜色
can.fill() //填充颜色
}, 5)
</script>
面向对象的小球,随机生成多个小球
随机生成多个小球碰撞
<body>
<canvas id="canvas" height="500" width="500"></canvas>
</body>
<script>
//固定步骤
var canvas = document.getElementById('canvas') //获取canvas节点
var can = canvas.getContext('2d') //建立canvas路径上下文
let width = 500
let height = 500
function r(num) {
return parseInt(Math.random() * num)
}
function Ball() { //随机生成小球
this.r = r(40) + 10 //小球半径在[10,50)
//随机生成位置
this.x = r(400) + 50
this.y = r(400) + 50
this.color = '#' + parseInt(Math.random() * 0Xffffff).toString(16) //随机生成颜色
this.xv = r(3) > 1 ? ~(r(2) + 2) : r(2) + 2//[2,5)
this.yv = r(3) > 1 ? ~(r(2) + 2) : r(2) + 2//[2,5)
}
Ball.prototype.show = function () {
this.run()
can.beginPath() //运动完成后清除画笔
can.arc(this.x, this.y, this.r, 0, Math.PI * 2, false) //画出小球
can.fillStyle = this.color//填充设置颜色
can.fill() //填充颜色
}
Ball.prototype.run = function () {
// can.clearRect(0, 0, width, height) //每次运动完成后清空画布
if ((this.x - this.r) < 0 || (this.x + this.r) >= width) { //制作x轴的弹性碰撞
this.xv = -this.xv
}
this.x += this.xv
if ((this.y - this.r) < 0 || (this.y + this.r) >= height) { //制作y轴的弹性碰撞
this.yv = -this.yv
}
this.y += this.yv
}
let timer = null
function addBall(num) {
can.clearRect(0, 0, width, height)
if (timer) {
clearInterval(timer)
}
let ballArr = []
for (let i = 0; i < num; i++) { // 添加小球
let ball = new Ball()
ballArr.push(ball)
ball.show()
}
timer = setInterval(function () { //开始运动
can.clearRect(0, 0, width, height) //每次运动完成后清空画布
for (let i = 0; i < ballArr.length; i++) {
ballArr[i].show()
}
}, 5)
}
addBall(10)
</script>
11、画文字
方法:can.fillText(text,x,y,maxWidth)
//画基础的文字
can.fillText(text,x,y,maxWidth)
/***
text:文字内容
x:文字左下角位置x坐标
y: 文字左下角位置y坐标
maxWidth:最大的宽度 可选参数 如果规定了最大宽度,文字溢出就会挤压文字
***/
//设置文字样式
can.font='30px 微软雅黑' //大小和样式
can.fillstyle='red' //设置文字颜色
can.fillText('你好',300,300)
//水平文字的属性
textAlign的值:start end left right center 默认值为left
can.textAlign='left'
//基线的对其方式(垂直位置)
textBaseline: Top Botton Middle 默认值为top
can.textBaseline='top'
//绘制空心文字
can.strokeStyle='green'
can.strokeText('你好',200,200)
//不重要,所有自行百度学习
//画渐变文字
can.createLinearGradient(0,0,canvas,width,0)
12、绘制图片
语法1:
can.drawImage(img,x,y)
can.drawImage(img,x,y)
/***
img:图片
x:图片起始位置x轴坐标
y:图片起始位置y轴坐标
***/
语法2:
can.drawImage(img,x,y,width,height)
can.drawImage(img,x,y,width,height)
/***
img:图片
x:图片起始位置x轴坐标
y:图片起始位置y轴坐标
width:图片宽度 (伸缩或缩小)
height:被剪切的图片高度 (伸缩或缩小)
***/
语法3
can.drawImage(img,sx,sy,swidth,sheight,x,y,width,height)
can.drawImage(img,sx,sy,swidth,sheight,x,y,width,height)
/***
img:图片
sx:开始剪切的x轴坐标
sy:开始剪切的y轴坐标
swidth:开始剪切的图片宽度
sheight:开始剪切的图片高度
x:图片剪切后位置x轴坐标
y:图片剪切后位置y轴坐标
width:图片剪切后宽度
height:图片剪切后高度
***/
图片的知识点补充
let img = new Image() //创建图片的实例
img.src = 'fangzi.jpg' //设置路径
/***
图片加载时会触发函数
成功触发onload:
失败触发onerror:
***/
var canvas = document.getElementById('canvas') //获取canvas节点
var can = canvas.getContext('2d') //建立canvas路径上下文
let img = new Image()
img.src = 'fangzi.jpg'
img.onload = function () {
console.log(img.width, img.height);
can.drawImage(img, 450, 400, 460, 500, 0, 0, 250, 250) //切图并移动到某个位置
}
getImageData(x,y,width,height)
//获取指定位置矩形的像素数据
can.getImageData(x,y,width,height)
/***
x:位置x轴坐标
y:位置y轴坐标
width:图片宽度
height:图片高度
***/
//获取后以一个数组的形式存储
如果使用本地图片会出现跨域问题,暂无解决方案
添加img.crossOrigin = '';
使用网络图片
putImageData(imaData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight)
//将图片放置到某一个位置
putImageData(imaData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight)
/***
imgData //规定放回画布的imageData对象
x ImageData对象左上角x轴坐标,以像素计算
y ImageData对象左上角y轴坐标,以像素计算
dirtyX 可选参数,水平x,以像素计算,在画布上放置图像的位置
dirtyy 可选参数,水平值,以像素计算,在画布上放置图像的位置
dirtyWidth 可选,绘制图像的宽度
dirtyHeight 可选,在画布上绘制图像的高度
***/
//可以先使用getImageData获取在使用putImageData放置
前面总结加强练习线性小球
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
/* canvas {
border: 1px solid;
display: block;
margin: 0 auto;
} */
</style>
</head>
<body style="overflow:-Scroll;overflow-y:hidden">
<canvas id="canvas"></canvas>
</body>
<script>
let canvas = document.querySelector('#canvas')
let can = canvas.getContext('2d')
let height = document.documentElement.clientHeight
let width = document.documentElement.clientWidth
canvas.height = height
canvas.width = width
// let width = 800
// let height = 800
//画直线
function drawLine(x1, y1, x2, y2, color) { //x1:开始横坐标,y1:开始纵坐标,x2:结束横坐标,y2:结束纵坐标,color:线的颜色,width:线宽
can.beginPath() //开启一条路径
can.moveTo(x1, y1) //路径开始点
can.lineTo(x2, y2) //路径结束点
//在着色之前设置颜色
can.strokeStyle = color || '#000' //通用颜色属性
//设置线宽
can.lineWidth = 2;
can.stroke() //路径添加颜色
can.closePath() //结束路径
}
//写文字
function drawText(text, x, y) {
can.font = '15px 微软雅黑' //大小和样式
can.textAlign = 'center'
can.textBaseline = 'top'
can.fillText(text, x, y)
}
let strArr = 'python java javasprit HTML5 CSS3 Nodejs PHP VUE React'.split(' ')
//生成小球
function r(num) {
return parseInt(Math.random() * num)
}
function Ball() { //随机生成小球
this.r = r(40) + 10 //小球半径在[10,50)
//随机生成位置
this.x = r(width - 100) + 50
this.y = r(height - 100) + 50
this.color = `rgb(${parseInt(Math.random() * 256)},${parseInt(Math.random() * 256)},${parseInt(Math.random() * 256)})` //随机生成颜色
this.xv = r(3) > 1 ? ~(r(2) + 2) : r(2) + 2//[2,5)
this.yv = r(3) > 1 ? ~(r(2) + 2) : r(2) + 2//[2,5)
}
Ball.prototype.show = function () {
this.run()
can.beginPath() //运动完成后清除画笔
can.arc(this.x, this.y, this.r, 0, Math.PI * 2, false) //画出小球
can.fillStyle = this.color//填充设置颜色
can.fill() //填充颜色
}
Ball.prototype.run = function () {
// can.clearRect(0, 0, width, height) //每次运动完成后清空画布
if ((this.x - this.r) < 0 || (this.x + this.r) >= width) { //制作x轴的弹性碰撞
this.xv = -this.xv
}
this.x += this.xv
if ((this.y - this.r) < 0 || (this.y + this.r) >= height) { //制作y轴的弹性碰撞
this.yv = -this.yv
}
this.y += this.yv
}
let timer = null
function addBall(num) {
can.clearRect(0, 0, width, height)
if (timer) {
clearInterval(timer)
}
let ballArr = []
for (let i = 0; i < num; i++) { // 添加小球
let ball = new Ball()
ballArr.push(ball)
ball.show()
}
timer = setInterval(function () { //开始运动
can.clearRect(0, 0, width, height) //每次运动完成后清空画布
for (let i = 0; i < ballArr.length; i++) {
ballArr[i].show()
drawText(strArr[i], ballArr[i].x, ballArr[i].y + ballArr[i].r + 5) //添加文字跟踪小球
for (let j = 0; j < i; j++) {
drawLine(ballArr[i].x, ballArr[i].y, ballArr[j].x, ballArr[j].y, ballArr[i].color) //从大到小划线
}
}
}, 10)
}
addBall(strArr.length)
</script>
</html>
加强线性练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
/* canvas {
border: 1px solid;
display: block;
margin: 0 auto;
} */
</style>
</head>
<body style="overflow:-Scroll;overflow-y:hidden">
<canvas id="canvas"></canvas>
</body>
<script>
let canvas = document.querySelector('#canvas')
let body = document.querySelector('body')
let can = canvas.getContext('2d')
let height = document.documentElement.clientHeight
let width = document.documentElement.clientWidth
canvas.height = height
canvas.width = width
//生成小球
function r(num) {
return parseInt(Math.random() * num)
}
function Ball(x, y) { //随机生成小球
this.r = 60 //小球半径在[10,50)
//随机生成位置
this.x = x
this.y = y
this.color = `rgb(${parseInt(Math.random() * 256)},${parseInt(Math.random() * 256)},${parseInt(Math.random() * 256)})` //随机生成颜色
}
Ball.prototype.show = function () {
can.beginPath()
this.r -= 5 //运动完成后清除画笔
can.arc(this.x, this.y, this.r, 0, Math.PI * 2, false) //画出小球
can.fillStyle = this.color//填充设置颜色
can.fill() //填充颜色
}
let ballArr = []
setInterval(function () { //移除数组中的小球
can.clearRect(0, 0, width, height)
for (let i = 0; i < ballArr.length; i++) {
let ball = ballArr[i]
if (ball.r <= 0) { //当半径为0时,从数组中移除这个元素
ballArr.splice(i, 1)
} else {
ballArr[i].show() //否则持续渲染
}
}
}, 30)
window.addEventListener('mousemove', function (e) { //获取移动的时候的x,y
let ball = new Ball(e.x, e.y)
ballArr.push(ball)
ball.show()
})
</script>
</html>
画时钟
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
div {
background-color: #ccc;
border-radius: 20px;
width: 400px;
height: 400px;
margin: 0 auto;
margin-top: 100px;
}
canvas {
/* border: 1px solid; */
/* background-color: #fff; */
display: block;
margin: 0 auto;
}
</style>
</head>
<body style="overflow:-Scroll;overflow-y:hidden">
<div>
<canvas id="canvas"></canvas>
</div>
</body>
<script>
let canvas = document.querySelector('#canvas')
let can = canvas.getContext('2d')
// let height = document.documentElement.clientHeight
// let width = document.documentElement.clientWidth
// canvas.height = height
// canvas.width = width
// let width = 800
// let height = 800
canvas.height = 400
canvas.width = 400
let height = width = 400 //时钟宽高
let x = y = 200 //时钟中心点
let r = 180 //时钟半径
let r_houe = 60 //时针长度
let r_min = 120 //分针长度
let r_s = 140 //秒针长度
let r_text = 140 //文字半径
let r_square = 165 //刻度半径
let r_circle = 10 //表盘小圆点
let deg = 2 * Math.PI //定义基本圆大小
//画直线
function drawLine(x1, y1, x2, y2, color, width) { //x1:开始横坐标,y1:开始纵坐标,x2:结束横坐标,y2:结束纵坐标,color:线的颜色,width:线宽
can.beginPath() //开启一条路径
can.moveTo(x1, y1) //路径开始点
can.lineTo(x2, y2) //路径结束点
//在着色之前设置颜色
can.strokeStyle = color || '#000' //通用颜色属性
//设置线宽
can.lineWidth = width || 2;
can.stroke() //路径添加颜色
can.closePath() //结束路径
}
//画文字
function drawText(text, x, y) {
can.font = '700 20px 微软雅黑' //大小和样式
can.textAlign = 'center'
can.textBaseline = 'middle'
can.fillText(text, x, y)
}
//画圆
function drawArc(x, y, r, color) {
can.beginPath()
can.arc(x, y, r, 0, Math.PI * 2, false)
// can.lineWidth = 1
// can.stroke()
can.fillStyle = color || '#fff'
can.fill()
}
// 1、重置中心点
can.translate(width / 2, height / 2)
function startM() { //不变值
//2、画表盘
drawArc(0, 0, r) //时钟表盘
drawArc(0, 0, r_circle, '#000') //中心点
//3、画数字
for (let i = 1; i <= 12; i++) {
let flagDeg = (2 * Math.PI) / 12 * i + (2 * Math.PI) / 12 * (~2)
x = r_text * Math.cos(flagDeg)
y = r_text * Math.sin(flagDeg)
drawText(i, x, y)
}
//4、画刻度
for (let i = 1; i <= 60; i++) {
let flagDeg = (2 * Math.PI) / 60 * i
if (i % 5 == 0) {
drawLine(r * Math.cos(flagDeg), r * Math.sin(flagDeg), (r - 10) * Math.cos(flagDeg), (r - 10) * Math.sin(flagDeg), '#aaa', 2)
} else {
drawLine(r * Math.cos(flagDeg), r * Math.sin(flagDeg), (r - 6) * Math.cos(flagDeg), (r - 6) * Math.sin(flagDeg), '#ccc', 1)
}
}
}
startM()
drawLine(0, 0, 0, ~r_houe, 'red', 5) //时钟
drawLine(0, 0, 0, ~r_min, 'yellow', 3) //分针
drawLine(0, 0, 0, ~r_s, 'blue', 2) //秒针
function houe() {
this.x = 0
this.y = r_houe
this.r = r_houe
this.color = 'red'
this.width = 5
this.total = 0
}
function min() {
this.x = 0
this.y = r_min
this.r = r_min
this.color = 'blue'
this.width = 3
this.total = 0
}
function mis() {
this.x = 0
this.y = r_s
this.r = r_s
this.color = 'yellow'
this.width = 2
this.total = 0
}
houe.prototype.run = function (m) {
let flagDeg = (2 * Math.PI) / 12 * this.total - Math.PI / 2 + m
drawLine(0, 0, this.r * Math.cos(flagDeg), this.r * Math.sin(flagDeg), this.color, this.width) //时钟
}
min.prototype.run = function (s) {
let flagDeg = (2 * Math.PI) / 60 * this.total - Math.PI / 2 + s
drawLine(0, 0, this.r * Math.cos(flagDeg), this.r * Math.sin(flagDeg), this.color, this.width) //分针
}
mis.prototype.run = function () {
let flagDeg = (2 * Math.PI) / 60 * this.total - Math.PI / 2
drawLine(0, 0, this.r * Math.cos(flagDeg), this.r * Math.sin(flagDeg), this.color, this.width) //秒针
}
let h = new houe()
let m = new min()
let s = new mis()
setInterval(function () {
can.clearRect(-200, -200, 400, 400)
startM()
if (s.total == 60) {
s.total = 0
s.total += 1
if (m.total == 60) {
m.total = 0
m.total += 1
if (h.total == 12) {
h.total = 0
m.total += 1
} else {
h.total += 1
}
} else {
m.total += 1
}
} else {
s.total += 1
}
h.run((2 * Math.PI / 60 * m.total) / 360 * 30)
m.run((2 * Math.PI / 60 * s.total) / 360 * 6)
s.run()
}, 1000)
</script>
</html>