<canvas>
是HTML5
新增的一种标签,一个可以使用脚本(通常为JavaScript
)在其中绘制图的HTML
元素。它可以用来制作照片集或者制作简单(也不是那么简单)的动画,甚至可以进行实时视频处理和渲染。
<canvas> 目前只支持JS,不支持其他如 jQuery 等等; 需要设置宽高,单位默认是px,如果不设置宽高,默认为 300*150;
<canvas>在低版本浏览器不兼容时,可以在标签内,设置不兼容时显示的内容
一、canvas 获取画布和画笔 画线条
注意:canvas 标签是行内块元素,如果需要设置他的布局,可以设置为块级元素
1.设置一个canvas 画布画直线,需要固定执行以下步骤:
1.1.获取canvas 标签(通过标签选择器来选择)
1.2.拿到canvas 的上下文对象(通过ctx.getContext('2d')来获取)
1.3.开启一条路径 beginPath();
1.4.确定起始点 moveTo(x,y)
1.5.确定终点 lineTo(x,y) ,这里可以连写lineTo(x,y) 终点位置,实现连笔画,及以上一个结束坐标作为下一个点的开始坐标;
1.6.进行上色
1.7.关闭路劲
/* 1.获取canvas 标签 */
let canvas = document.querySelector('canvas')
/* 2.获取上下文对象,使用getContext('2d')方法 */
let ctx = canvas.getContext('2d');
/* 3.开启一条路径 */
ctx.beginPath();
/* 4.确定起点坐标 坐标是相对于 canvas 的左上角 */
ctx.moveTo(100, 100)
/* 5.确实终点坐标 */
ctx.lineTo(400, 400)
/* 设置颜色和线宽,必须要在上色之前设置颜色和线宽属性 */
ctx.strokeStyle = 'pink'
ctx.lineWidth = 5
/* 6.上色 */
ctx.stroke()
/* 7.关闭路径 */
ctx.closePath();
2.画虚线(通常可以把重复的代码封装为一个函数来调用)
2.1 可以使用循环方式连续画出不同的直线,实现虚线效果;原理是,不断的变化每条小的直线的坐标,来实现;
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
drawLine(100, 100, 105, 100, 'pink', 5)
// 画虚线
for (let i = 0; i < 20; i++) {
drawLine(100 + 10 * i, 100, 105 + 10 * i, 100, 'pink', 5)
}
// 斜角度画虚线
for (let i = 0; i < 20; i++) {
drawLine(100 + 10 * i, 100 + 10 * i, 105 + 10 * i, 105 + 10 * i, 'pink', 5)
}
// 画直线函数
function drawLine(x1, y1, x2, y2, color, width) {
ctx.beginPath();
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.strokeStyle = color
ctx.lineWidth = width
ctx.stroke()
ctx.closePath();
}
2.2 通过 ctx.setLineDash([num1,num2]) 方法和 ctx.lineDashOffset = -5; 来定制虚线样式
(1)setLineDash([num1,num2]) 方法接受一个数组作为参数,num1为 线条长度,num2 为间隔距离
(2)lineDashOffset = num, 方法设置起始点偏移量;
function draw() {
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
ctx.setLineDash([10, 10]); // [实线长度, 间隙长度]
ctx.lineDashOffset = -5;
// ctx.strokeRect(50, 50, 210, 210);
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(100, 100)
ctx.stroke()
ctx.closePath()
}
draw();
3.线条连画(多次调用lineTo(x,y))
4.画矩形
4.1 画有填充矩形 方法:fillRect(x,y,width,height)
x,y表示矩形左上角起始坐标;
width,height 表示 矩形的长宽;
ctx.fillRect(200, 200, 100, 100)
ctx.fillStyle = 'red'
4.2 画无填充矩形 方法:ctx.strokeRect(x,y,width,height)
ctx.strokeStyle = '#ccc'
ctx.lineWidth = 3
ctx.strokeRect(300, 300, 100, 100)
4.3 画有边框的 填充矩形
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
ctx.beginPath()
// 创建矩形,rect(x,y,width,height)
ctx.rect(100, 100, 100, 100)
// 填充色
ctx.fillStyle = 'pink'
// 填充
ctx.fill()
// 描边
ctx.strokeStyle = 'green'
// 描边的宽度
ctx.lineWidth = 5
// 着色
ctx.stroke()
简单实例如下:
5.清除画布 方法:ctx.clearRect(x,y,w,h)
清除画布在画图过程中很常用,比如实现动态图,就是不断清除画布和重绘画布;
/* 画矩形 */
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
ctx.beginPath()
// 创建矩形,rect(x,y,width,height)
ctx.rect(100, 100, 300, 200)
// 填充色
ctx.fillStyle = 'pink'
// 填充
ctx.fill()
// 描边
ctx.strokeStyle = 'green'
// 描边的宽度
ctx.lineWidth = 5
// 着色
ctx.stroke()
// 清除画布 clearRect(x,y,width,height) 坐标x,y依然是矩形的左上角;
ctx.clearRect(120, 120, 150, 100)
6.画圆
方法:arc(x,y,radius,startAngle,endAngle,counterclockwise)
(1)x,y 表示圆心坐标
(2)radius 表示圆弧半径
(3)startAngle 开始角度
(4)endAngle 结束角度
(5)counterclockwise 表示画圆弧的顺逆时针方向,false 表示顺时针,true表示逆时针
ctx.beginPath()
ctx.arc(400, 400, 250, 0, Math.PI * 2, false)
ctx.strokeStyle = 'red'
ctx.lineWidth = 3
// 内部填充
ctx.fillStyle = 'gold'
ctx.fill()
// 着色,不可缺,没有着色是不能看到的
ctx.stroke()
// 图像不是封闭的时候,需要设置开始路径和结束路径的,要不然会连笔
ctx.beginPath()
ctx.arc(400, 400, 100, 0, Math.PI, false)
ctx.strokeStyle = 'red'
ctx.lineWidth = 3
// 着色,不可缺,没有着色是不能看到的
ctx.stroke()
7.画文字
7.1 绘制文字
(1).实心字体 方法:fillText('文字内容',x,y,maxWidth)
(2) 空心字体 方法:ctx.strokeText('文字内容', x, y)
- x,y 表示文字的开始位置坐标,maxWidth表示文字内容最大宽度,超出会压缩
(3)线性渐变
方法:let grd = ctx.createLinearGradient(x1, y, x2, y2);
grd.addColorStop('0','颜色')
grd.addColorStop('1','颜色')
- x1,y1,x2,y2表示渐变开始和结束的坐标
- addColorStop() 表示,颜色的添加
// 字体的样式需要放在 前面
ctx.font = '50px 微软雅黑'
ctx.fillStyle = 'gold'
// 实心字体
ctx.fillText('hello canvas', 100, 100)
ctx.font = '50px 宋体'
// 空心字体
ctx.strokeStyle = 'pink'
ctx.strokeText('你好', 100, 200)
// 颜色线性渐变
let grd = ctx.createLinearGradient(0, 0, canvas.width, 0);
// 增加渐变颜色以及范围
grd.addColorStop('0', 'yellow')
grd.addColorStop('0.5', 'red')
grd.addColorStop('1.0', 'skyBlue')
// 应用渐变,把渐变的变量 赋值给strokeStyle 属性
ctx.strokeStyle = grd;
ctx.font = '40px 楷体'
ctx.strokeText('今天很冷,要多穿衣服哦!!!', 0, 300, 400)
7.2 文字位置设置
文字位置属性:
文字水平方向:start,end, left,right,center
文字垂直方向:top,bottom,middle
// 画田字格
ctx.beginPath();
ctx.moveTo(250, 0)
ctx.lineTo(250, 500)
ctx.stroke()
ctx.closePath()
ctx.beginPath();
ctx.moveTo(0, 250)
ctx.lineTo(500, 250)
ctx.stroke()
ctx.closePath()
ctx.font = '30px 宋体'
ctx.fillStyle = 'red'
// 水平居中 (参考点都是坐标点)
ctx.textAlign = 'center'
// 垂直居中 (参考点都是坐标点)
ctx.textBaseline = 'middle'
ctx.fillText('风和日丽', 250, 250)
8. 绘制图片
全图显示方法:ctx.drawImage(img,x,y)
- img 为图片对象,采用new Image()方式
- x,y 为图片左上角的坐标;
裁剪显示方法:ctx.drawImage(img,xs,ys,sWidth,sHeight,x,y,width,heigth)
- xs,ys,表示开始裁剪的位置坐标
- sWidth,sHeight 表示裁剪的宽高
- x,y 表示图片在画布上显示的位置坐标
- width,height 表示要使用的图像的宽高(sheng)
// 创建图片
let img = new Image()
img.src = './imgs/fengjing.jpeg'
img.onload = function () {
/**
* 获取当前图片的属性,宽高
*/
console.log(img.width, img.height);
// ctx.drawImage(img, 100, 100)
ctx.drawImage(img, 0, 0, 100, 100, 100, 100, 50, 50)
}
9. 像素操作和像素数据
9.1 获取图片像素对象 getImageData(x,y,width,height)
- x,y 表获取起始点坐标
- width,height 表示宽高
ctx.getImageData(x,y,w,h)返回一个对象,包含了所获取的像素块的像素内容,包含了 该块图像的每一个像素,每一个像素点信息 = rgba(0-255,0-255,0-255,0-255),前三个数据表示红绿蓝,第四个数据表示 透明度(0-255)
9.2 重新绘制获取的图片对象 putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight)
- imgData 表示要放回画布的 ImageData 对象
- x,y 表示 ImageData 对象左上角的x,y坐标,以像素点计算
- dirtyX,dirtyY 可选。表示在画布上放置图像的位置
- dirtyWidth,dirtyHeight 可选。表示在画布上绘制图像所使用的宽高
// 1.创建图片
let img = new Image();
img.src = './imgs/fengjing.jpeg';
img.onload = function () {
// 绘制图片
ctx.drawImage(img, 0, 0);
let copy = ctx.getImageData(0, 0, 100, 100)
console.log(copy);
/*
putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight)
- imgData 表示要放回画布的 ImageData 对象
- x,y 表示 ImageData 对象左上角的x,y坐标,以像素点计算
- dirtyX,dirtyY 可选。表示在画布上放置图像的位置
- dirtyWidth,dirtyHeight 可选。表示在画布上绘制图像所使用的宽高
*/
ctx.putImageData(copy, 0, 650)// 可以在原图片上扣取一部分图片重新绘制到指定地方;
}
二、动图案例
1. 小球的碰撞检测
重要点为:小球碰到边缘如何反弹,本质是小球的速度(也就是坐标的变化距离),当小球的坐标+半径和 到了边缘时,设置小球往方向跑
<!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 {
margin: 50px auto;
display: block;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<canvas width="600" height="600">默认参数</canvas>
<script>
let c = document.querySelector('canvas')
let ctx = c.getContext('2d')
/*
小球运动的本质就是定时器重新绘制小球
*/
let w = h = 600
let x = 100;
let y = 100
let r = 20
let xSpeed = 3;
let ySpeed = 4
setInterval(function () {
ctx.clearRect(0, 0, w, h)
// 小球坐标的变化
drawCircle(x, y, r, 'red')
if (x + r >= w || x - r <= 0) {
xSpeed = -xSpeed;
}
x += xSpeed
if (y + r >= h || y - r <= 0) {
ySpeed = -ySpeed;
}
y += ySpeed
}, 10)
function drawCircle(x, y, r, color = '#000') {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fillStyle = color;
// ctx.fill()方法是实心着色
ctx.fill()
// 着色,不可缺,没有着色是不能看到的
// ctx.stroke() 是空心着色
// ctx.stroke();
ctx.closePath();
}
</script>
</body>
</html>
2.面向对象的小球碰撞
面向对象的本质是对小球对象的封装,小球移动和绘制方法的封装;
<body>
<canvas width="600" height="600">默认参数</canvas>
<script>
let c = document.querySelector('canvas')
let ctx = c.getContext('2d')
/*
小球运动的本质就是定时器重新绘制小球
*/
let w = h = 600
// 创建随机数
function ranD(num) {
return Math.random() * num
}
// 创建一个小球对象
class Ball {
constructor() {
this.r = ranD(30) + 20;
this.x = ranD(500) + 60;
this.y = ranD(500) + 60;
this.xSpeed = ranD(3) + 2;
this.ySpeed = ranD(3) + 1;
this.color = "#" + parseInt(ranD(1) * 0xffffff).toString(16)
}
// 小球显示的方法
show() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill()
ctx.closePath();
}
// 小球运动的方法
run() {
if (this.x + this.r >= w || this.x - this.r <= 0) {
this.xSpeed = -this.xSpeed;
}
this.x += this.xSpeed
if (this.y + this.r >= h || this.y - this.r <= 0) {
this.ySpeed = -this.ySpeed;
}
this.y += this.ySpeed
}
}
// 创建小球
let ballArr = []
for (let i = 0; i < 20; i++) {
var ball = new Ball()
ballArr.push(ball)
ball.show()
}
console.log(ballArr);
// 小球运动
setInterval(function () {
ctx.clearRect(0, 0, w, h)
for (let i = 0; i < ballArr.length; i++) {
ballArr[i].show()
ballArr[i].run()
}
}, 10)
</script>
</body>
</html>
3.小球连线
小球之间连线的本质是 选取小球之间的圆心,不断动态重新绘制直线
<body>
<canvas width="600" height="600">默认参数</canvas>
<script>
let c = document.querySelector('canvas')
let ctx = c.getContext('2d')
/*
小球运动的本质就是定时器重新绘制小球
*/
let w = h = 600
// 创建随机数
function ranD(num) {
return Math.random() * num
}
// 创建一个小球对象
class Ball {
constructor(text) {
this.r = ranD(30) + 20;
this.x = ranD(500) + 50;
this.y = ranD(500) + 50;
this.xSpeed = ranD(3) + 2;
this.ySpeed = ranD(3) + 1;
this.color = "#" + parseInt(ranD(1) * 0xffffff).toString(16);
this.text = text;
}
// 画圆;小球显示的方法
show() {
drawCircle(this.x, this.y, this.r, this.color);// 画圆
drawText(this.r, this.text, this.x + this.r, this.y, this.color);// 画文字
}
// 小球运动的方法
run() {
if (this.x + this.r >= w - 60 || this.x - this.r <= 0) {
this.xSpeed = -this.xSpeed;
}
this.x += this.xSpeed
if (this.y + this.r >= h || this.y - this.r <= 0) {
this.ySpeed = -this.ySpeed;
}
this.y += this.ySpeed
}
}
// 画直线
function drawLine(x1, y1, x2, y2, color = "#000") {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = color;
ctx.stroke();
ctx.closePath();
}
// 画字体
function drawText(font, text, x, y, color = '#000') {
ctx.font = `${font}px 宋体`;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillStyle = color;
ctx.fillText(text, x, y, 60)
}
// 画圆
function drawCircle(x, y, r, color) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill()
ctx.closePath();
}
// 文字数据
let titleText = 'Java PHP JS GO Vue Angular jQuery CSS HTML Bootstrap webGL ThreeJs ElementUI UI AXIOS ajax'.split(' ')
// 创建小球
let ballArr = []
for (let i = 0; i < 6; i++) {
var ball = new Ball(titleText[i])
ballArr.push(ball)
ball.show()
}
console.log(ballArr);
// 小球运动
setInterval(function () {
ctx.clearRect(0, 0, w, h)
for (let i = 0; i < ballArr.length; i++) {
ballArr[i].show()
ballArr[i].run()
/*
小球连线
*/
for (let j = 0; j < i; j++) {
drawLine(ballArr[i].x, ballArr[i].y, ballArr[j].x, ballArr[j].y, ballArr[i].color)
}
}
}, 10)
</script>
</body>
</html>
4.跟随鼠标的炫彩炫彩小球
<body>
<canvas>默认参数</canvas>
<script>
let c = document.querySelector('canvas')
let ctx = c.getContext('2d')
/*
小球运动的本质就是定时器重新绘制小球
*/
let w = document.documentElement.clientWidth - 10
let h = document.documentElement.clientHeight - 10
c.width = w;
c.height = h
// 创建随机数
function ranD(num) {
return Math.random() * num
}
// 创建一个小球对象
class Ball {
constructor(x, y) {
this.r = 60;
this.x = x;
this.y = y;
this.color = "#" + parseInt(ranD(1) * 0xffffff).toString(16);
}
// 画圆;小球显示的方法
show() {
this.r--;// 小球直径减小
if (this.r <= 0) return
drawCircle(this.x, this.y, this.r, this.color);// 画圆
}
}
// 画圆
function drawCircle(x, y, r, color) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill()
ctx.closePath();
}
// 创建小球
let ballArr = []
// 小球的坐标跟着鼠标的位置而移动
window.onmousemove = function (event) {
console.log(event.x, event.y);
var ball = new Ball(event.x, event.y)
ballArr.push(ball)
ball.show()
}
// 拿到小球对象集合
console.log(ballArr);
// 小球运动
setInterval(function () {
ctx.clearRect(0, 0, w, h)
for (let i = 0; i < ballArr.length; i++) {
ballArr[i].show()
}
}, 8)
</script>
</body>
</html>
最后附上写的比较好的大佬的博客,感谢参考!!