前言
canvas 在很多时候能带给我们意想不到的结果
例如: 粒子效果、树状图、饼图、文字渐变…
所以打算花一些时间去学习,这里将是记录学习的地方
很多人再编写canvas的时候没有智能的提示,也就是 . 的时候没有东西出来
再最上面写上这段注释即可
/** @type {HTMLCanvasElement} */
let canvas = document.querySelector('#canvas')
let ctx = c1.getContext('2d');
ctx.fillRect(100, 100, 100, 100)
使用body里面的标签的方式,创建一个canvas对象
canvas 和别的元素不太一样,需要使用属性的方式设置宽和高,不然会有问题
填充图形 Api:fillReact(x,y,width,height)
X , Y
都是对应的浏览器的左上角
<canvas width="600" height="600" id="canvas">canvas>
<script>
/** @type {HTMLCanvasElement} */
// 1. 获取canvas(画布)标签
const canvas = document.getElementById('canvas')
// 2. 获取context(画笔)对象
const ctx = canvas.getContext('2d')
// 3. 画出自己想要的图形
// 画一个正方形 ,而且是一个填充的正方形,api为 fillReact(x,y,width,height)
ctx.fillRect(50,50,100,200)
script>
除了使用标签创建canvas,我们还可以是使用 script 来创建
const canvas = document.createElement('canvas')
canvas.width = 600
canvas.height = 600
document.body.appendChild(canvas)
// 获取context(画笔)对象
const ctx = canvas.getContext('2d')
ctx.fillRect(50,50,100,200)
起始位置:moveTo(x,y)
终点:lineTo(X,Y)
记住(该方法并不会创建线条,必须使用画线的方法)
画线的方法 stroke()
到这里估计你已经熟悉了 canvas的创建了,下面我将只贴出核心代码,canvas的创建代码将省略
const canvas = document.createElement('canvas')
canvas.width = 600
canvas.height = 600
document.body.appendChild(canvas)
// 获取context(画笔)对象
const ctx = canvas.getContext('2d')
// 画线段
ctx.moveTo(100,100)
ctx.lineTo(300,100)
ctx.stroke()
lineTo 又叫转折点,它并不会画线。
当你使用 画线的方式时,canvas会自动连接开始与转折点(各个终点的位置)
// 画折线
// 起始
ctx.moveTo(100,100)
// 移动到位置(200,50)
ctx.lineTo(200,50)
// 移动到位置(200,100)
ctx.lineTo(300,100)
// 移动到位置(400,50)
ctx.lineTo(400,50)
ctx.stroke()
lineWidth: 数字
// 画线段
ctx.moveTo(100,100)
ctx.lineTo(300,100)
// 增加线条宽度
ctx.lineWidth = 10
ctx.stroke()
ctx.lineTo(200,200)
// 如果有新的路径,也要调用 stoke 才会有图像
ctx.stroke()
strokeStyle = 后面带颜色值 支持 十六进制 渐变 '#0c0'
// 修改线条颜色
ctx.strokeStyle = '#0c0'
// 画线段
ctx.moveTo(100,100)
ctx.lineTo(300,100)
// 增加线条宽度
ctx.lineWidth = 10
ctx.stroke()
ctx.lineTo(200,200)
// 如果有新的路径,也要调用 stoke 才会有图像
ctx.stroke()
MDN文档: https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasGradient
// 创建 线性渐变
const gradient = ctx.createLinearGradient(0,0,600,600)
// 从什么颜色开始
gradient.addColorStop(0,'red')
// 从什么颜色结束
gradient.addColorStop(1,'blue')
// 画线段
ctx.moveTo(0,100)
ctx.lineTo(500,100)
// 增加线条宽度
ctx.lineWidth = 20
// 修改线条颜色
ctx.strokeStyle = gradient
ctx.stroke()
只能在 addColorStop 0- 1之间加
// 创建 线性渐变
const gradient = ctx.createLinearGradient(0,0,600,600)
// 从什么颜色开始
gradient.addColorStop(0,'red')
// 0.5
gradient.addColorStop(0.5,'pink')
// 从什么颜色结束
gradient.addColorStop(1,'blue')
createRadialGradient
MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createRadialGradient
// 创建 线性渐变
const gradient = ctx.createRadialGradient(110, 90, 30, 100, 100, 70)
// 从什么颜色开始
gradient.addColorStop(0,'red')
// 0.5
gradient.addColorStop(0.5,'pink')
// 从什么颜色结束
gradient.addColorStop(1,'blue')
// 修改填充颜色
ctx.fillStyle = gradient
ctx.fillRect(20, 20, 160, 160);
用得不多,可以自己去 MDN里面了解
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createConicGradient
用得不多,可以自己去 MDN里面了解
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createPattern
// 修改粗细
ctx.lineWidth = 20
// 创建线性渐变
const gradient = ctx.createLinearGradient(0,0,600,600)
// 开始颜色
gradient.addColorStop(0, 'red')
// 0.3
gradient.addColorStop(0.4, 'pink')
// 0.7
gradient.addColorStop(0.7, 'yellow')
// 结束颜色
gradient.addColorStop(1, 'blue')
// 画线
ctx.moveTo(50,300)
ctx.lineTo(200,100)
ctx.lineTo(400,300)
ctx.lineTo(600,100)
// 使用线性渐变颜色
ctx.strokeStyle = gradient
ctx.stroke()
lineJoin: "round", "bevel", "miter"
三个可选值
lineCap:"butt","round","square"
三个可选值
可以让线条更加自然
// 修改粗细
ctx.lineWidth = 20
// 创建线性渐变
const gradient = ctx.createLinearGradient(0,0,400,500)
// 开始颜色
gradient.addColorStop(0, 'red')
// 0.3
gradient.addColorStop(0.4, 'pink')
// 0.7
gradient.addColorStop(0.7, 'yellow')
// 结束颜色
gradient.addColorStop(1, 'blue')
// 修改线段的转角样式
ctx.lineCap = "round"
ctx.lineJoin = 'round'
// 画线
ctx.moveTo(50,300)
ctx.lineTo(200,100)
ctx.lineTo(400,300)
ctx.lineTo(550,100)
// 使用线性渐变颜色
ctx.strokeStyle = gradient
ctx.stroke()
arc(x, y, radius, startAngle, endAngle)
arc(x, y, radius, startAngle, endAngle, counterclockwise)
radius: 圆心
startAngle从哪个角度开始画
endAngle 到那个角度结束
counterclockwise 顺时针(false)还是逆时针(true)
ctx.arc(300,300,50,0,1)
ctx.stroke()
ctx.arc(300,300,50,0,1,true)
ctx.stroke()
重新生成一个新的路径:beginPath()
结束新的路径:closePath()
必须要是用 beginPath 、 closePath不然就会像下面这样
// 画笑脸
// 画大圆
ctx.arc(300,300,100,0, 2 * Math.PI)
ctx.stroke()
// 画左边的小圆
// 需要先开始一个新的路径,不然会连起来
ctx.arc(250,250,20,0 , 2 * Math.PI)
ctx.stroke()
// 画笑脸
// 画大圆
ctx.arc(300,300,100,0, 2 * Math.PI)
ctx.strokeStyle = '#DC143C'
ctx.fillStyle = '#E9967A'
ctx.fill()
// 画左边的小圆
// 需要先开始一个新的路径,不然会连起来
ctx.beginPath()
ctx.arc(250,250,20,0 , 2 * Math.PI)
ctx.stroke()
ctx.closePath()
// 画右边的小圆
ctx.beginPath()
ctx.arc(350,250,20,0 , 2 * Math.PI)
ctx.stroke()
ctx.closePath()
// 画鼻子
ctx.beginPath()
ctx.arc(300,300,20,0 , 2 * Math.PI)
ctx.stroke()
ctx.closePath()
// 画嘴巴
ctx.beginPath()
ctx.arc(300,300, 80 , 0 , Math.PI)
ctx.stroke()
ctx.closePath()
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle)
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise)
radiusX: X轴半径
radiusY: Y轴半径
rotation: 旋转角度
startAngle: 从那个角度开始画
endAngle: 到那个角度结束
ctx.beginPath();
ctx.ellipse(100, 100, 50, 75, Math.PI / 4, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.ellipse(100, 100, 50, 75, 0, 0, 2 * Math.PI);
ctx.stroke();
上面画笑脸的时候其实已经用过了
就是fill()
填充会自动闭合起始点
ctx.moveTo(100,100)
ctx.lineTo(100,200)
ctx.lineTo(200,200)
ctx.fill()
ctx.moveTo(100,100)
ctx.lineTo(300,100)
ctx.lineTo(200,200)
ctx.fill()
ctx.arc(200,200,100,0 ,2 * Math.PI)
ctx.fill()
使用径向渐变让圆更有立体感
ctx.arc(200,200,100,0 ,2 * Math.PI)
let g = ctx.createRadialGradient(150,150,0,150,150,150)
g.addColorStop(0, '#ccc')
g.addColorStop(1, '#000')
ctx.fillStyle = g
ctx.fill()
<style>
canvas{
background-color: aquamarine;
display: block;
margin: 0 auto;
}
</style>
// 画棋盘
// 1. 每个格子宽高为 50 ,那么 800 - 左右间隔100 / 50 = 14
for(let i = 1;i<= 15;i++){
// 横向
ctx.beginPath()
ctx.moveTo(50, 50 * i)
ctx.lineTo(750, 50 * i)
ctx.stroke()
ctx.closePath()
// 纵向
ctx.beginPath()
ctx.moveTo(50 * i, 50)
ctx.lineTo(50 * i, 750)
ctx.stroke()
ctx.closePath()
}
// 判断白子、黑子
let isBlack = true
canvas.addEventListener('click',(e)=>{
const {offsetX,offsetY} = e
// 判断点击的边界
if(offsetX < 25 || offsetY < 25 || offsetX > 775 || offsetY > 775)return
const X = Math.floor(((offsetX + 25) / 50))* 50
const Y = Math.floor(((offsetY + 25) / 50))* 50
let tx = isBlack?X-10:X + 10
let ty = isBlack?Y-10:Y + 10
let g = ctx.createRadialGradient(tx,ty,0,tx,ty,30)
g.addColorStop(0, isBlack?'#ccc':'#666')
g.addColorStop(1, isBlack?'#000':'#fff')
ctx.beginPath()
ctx.arc(X,Y,20, 0 ,2 * Math.PI)
ctx.fillStyle = g
ctx.fill()
ctx.closePath()
isBlack = !isBlack
// 创建一个二维数组用来判断重复落子跟输赢情况
const circles = []
for(let i = 1;i<= 15;i++){
circles[i] = []
}
// 格子所在的位置
const i = Math.floor(((offsetX + 25) / 50))
const j = Math.floor(((offsetY + 25) / 50))
// 棋子落子的位置
const X = i * 50
const Y = j * 50
// 判断当前是否可以落子
if(circles[i][j]) return
circles[i][j] = isBlack?'black':'white'
纵向, 横向, 左斜,右斜都类似四个方向
endGame = checkVertical(i,j)
// 纵向查找是否有5个连续的棋子
function checkVertical(row,col){
// 定义一个向上的次数
let up = 0
// 定义一个向下的次数
let down = 0
let time = 0
// 初始值为1,代表它本省已经是查找的第一课棋子
let count = 1
while(time < 100){
time++
if(count>=5){
break;
}
let target = isBlack?'black':'white'
// 以row,col为起点,在二维数组上,向上查找
up++
if(circles[row][col - up] && circles[row][col - up] == target){
count++
}
// 以row,col为起点,在二维数组上,向下查找
down++
if(circles[row][col + down] && circles[row][col + down] == target){
count++
}
}
return count>=5;
}
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>Documenttitle>
<style>
canvas{
background-color: aquamarine;
display: block;
margin: 0 auto;
}
h3{
text-align: center;
}
style>
head>
<body>
<h3 id='tip'>h3>
body>
html>
<script>
const tip = document.getElementById('tip')
const canvas = document.createElement('canvas')
canvas.width = 800
canvas.height = 800
document.body.appendChild(canvas)
// 获取context(画笔)对象
const ctx = canvas.getContext('2d')
// 画棋盘
// 1. 每个格子宽高为 50 ,那么 800 - 左右间隔100 / 50 = 14
for(let i = 1;i<= 15;i++){
// 横向
ctx.beginPath()
ctx.moveTo(50, 50 * i)
ctx.lineTo(750, 50 * i)
ctx.stroke()
ctx.closePath()
// 纵向
ctx.beginPath()
ctx.moveTo(50 * i, 50)
ctx.lineTo(50 * i, 750)
ctx.stroke()
ctx.closePath()
}
// 判断白子、黑子
let isBlack = true
// 判断游戏结束
let endGame = null
tip.innerHTML = isBlack?'请黑棋落子!!':'请白棋落子!!'
// 创建一个二维数组用来判断重复落子跟输赢情况
const circles = []
for(let i = 1;i<= 15;i++){
circles[i] = []
}
canvas.addEventListener('click',(e)=>{
const {offsetX,offsetY} = e
if(endGame)return
// 判断点击的边界
if(offsetX < 25 || offsetY < 25 || offsetX > 775 || offsetY > 775)return
// 格子所在的位置
const i = Math.floor(((offsetX + 25) / 50))
const j = Math.floor(((offsetY + 25) / 50))
// 棋子落子的位置
const X = i * 50
const Y = j * 50
// 判断当前是否可以落子
if(circles[i][j]) return
let tx = isBlack?X-10:X + 10
let ty = isBlack?Y-10:Y + 10
let g = ctx.createRadialGradient(tx,ty,0,tx,ty,30)
g.addColorStop(0, isBlack?'#ccc':'#666')
g.addColorStop(1, isBlack?'#000':'#fff')
ctx.beginPath()
ctx.arc(X,Y,20, 0 ,2 * Math.PI)
ctx.fillStyle = g
// 设置阴影
ctx.shadowColor = '#000'
ctx.shadowOffsetX = 4
ctx.shadowOffsetY = 4
ctx.shadowBlur = 4
ctx.fill()
ctx.closePath()
circles[i][j] = isBlack?'black':'white'
// 判断当前是否有五个棋子
endGame = checkVertical(i,j) || checkHorizontal(i,j) || checkTopToBottom(i,j) || checkRTToLB(i,j)
if(endGame) {
tip.innerHTML = `${isBlack ?'黑':'白'}子获胜,请刷新重新开始游戏`
return
}
isBlack = !isBlack
tip.innerHTML = isBlack?'请黑棋落子!!':'请白棋落子!!'
})
// 纵向查找是否有5个连续的棋子
function checkVertical(row,col){
// 定义一个向上的次数
let up = 0
// 定义一个向下的次数
let down = 0
let time = 0
// 初始值为1,代表它本身已经是查找的第一颗棋子
let count = 1
while(time < 100){
time++
let target = isBlack?'black':'white'
// 以row,col为起点,在二维数组上,向上查找
up++
if(circles[row][col - up] && circles[row][col - up] == target){
count++
}
// 以row,col为起点,在二维数组上,向下查找
down++
if(circles[row][col + down] && circles[row][col + down] == target){
count++
}
// 如果棋子已经大于 5 个,并且,不是连续的黑白棋,那么也不能算赢
if(count>=5 || (circles[row][col - up] !== target && circles[row][col + down] !== target)){
break;
}
}
return count>=5;
}
// 横向查找是否有5个连续的棋子
function checkHorizontal(row,col){
// 定义一个向右的次数
let right = 0
// 定义一个向左的次数
let left = 0
let time = 0
// 初始值为1,代表它本身已经是查找的第一颗棋子
let count = 1
while(time < 100){
time++
let target = isBlack?'black':'white'
// 以row,col为起点,在二维数组上,向上查找
left++
if(circles[row - left][col] && circles[row - left][col] == target){
count++
}
// 以row,col为起点,在二维数组上,向下查找
right++
if(circles[row + right][col] && circles[row + right][col] == target){
count++
}
// 如果棋子已经大于 5 个,并且,不是连续的黑白棋,那么也不能算赢
if(count>=5 || (circles[row - left][col] !== target && circles[row + right][col] !== target)){
break;
}
}
return count>=5;
}
// 左上到右下查找是否有5个连续的棋子
function checkTopToBottom(row,col){
// 定义一个向右下的次数
let rb = 0
// 定义一个向左上的次数
let lt = 0
let time = 0
// 初始值为1,代表它本身已经是查找的第一颗棋子
let count = 1
while(time < 100){
time++
let target = isBlack?'black':'white'
// 以row,col为起点,在二维数组上,向上查找
lt++
if(circles[row - lt][col - lt] && circles[row - lt][col - lt] == target){
count++
}
// 以row,col为起点,在二维数组上,向下查找
rb++
if(circles[row + rb][col + rb] && circles[row + rb][col + rb] == target){
count++
}
// 如果棋子已经大于 5 个,并且,不是连续的黑白棋,那么也不能算赢
if(count>=5 || (circles[row - lt][col - lt] !== target && circles[row + rb][col + rb] !== target)){
break;
}
}
return count>=5;
}
// 右上到左下查找是否有5个连续的棋子
function checkRTToLB(row,col){
// 定义一个向右下的次数
let rt = 0
// 定义一个向左上的次数
let lb = 0
let time = 0
// 初始值为1,代表它本身已经是查找的第一颗棋子
let count = 1
while(time < 100){
time++
let target = isBlack?'black':'white'
// 以row,col为起点,在二维数组上,向上查找
rt++
if(circles[row + rt][col - rt] && circles[row + rt][col - rt] == target){
count++
}
// 以row,col为起点,在二维数组上,向下查找
lb++
if(circles[row - lb][col + lb] && circles[row - lb][col + lb] == target){
count++
}
// 如果棋子已经大于 5 个,并且,不是连续的黑白棋,那么也不能算赢
if(count>=5 || (circles[row + rt][col - rt] !== target && circles[row - lb][col + lb] !== target)){
break;
}
}
return count>=5;
}
script>
shadowColor = '#ccc'
阴影颜色
shadowOffsetX = 10
X 偏移量
shadowOffsetY = 10
Y 偏移量
shadowBlur = 10
模糊程度
学会了阴影,还可以把上面的棋子改一改
ctx.arc(200,200,100,0 ,2 * Math.PI)
let g = ctx.createRadialGradient(150,150,0,150,150,150)
g.addColorStop(0, '#ccc')
g.addColorStop(1, '#000')
ctx.fillStyle = g
// 设置阴影
ctx.shadowColor = '#ccc'
ctx.shadowOffsetX = 10
ctx.shadowOffsetY = 10
ctx.shadowBlur = 10
ctx.fill()
drawImage(image, dx, dy)
drawImage(image, dx, dy, dWidth, dHeight)
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage
// 首先得准备一张图片
let img = new Image()
img.src = './1.jpg'
img.onload = ()=>{
// 这样子渲染出来的图片是不完整的
ctx.drawImage(img,100,100)
}
这样子就能完整的将图片缩小并完整的展示出来
// 首先得准备一张图片
let img = new Image()
img.src = './1.jpg'
img.onload = ()=>{
// 可以先计算出 img 的宽高,在动态的 修改后面的值
ctx.drawImage(img,100,100, 200,200)
}
裁切一部分图片展示
// 首先得准备一张图片
let img = new Image()
img.src = './1.jpg'
img.onload = ()=>{
ctx.drawImage(img,500,500,200,200,100,100,200,200)
}
fillText(text, x, y, [maxWidth])
strokeText(text, x, y, [maxWidth])
ctx.font = "50px serif";
设置字体的大小以及字体:符合 CSS font 语法的DOMString 字符串。默认字体是 10px sans-serif
// 注意,文字是以左下角为初始坐标
ctx.font = "700 50px serif"
ctx.fillText('holle world',100,100)
ctx.strokeText('holle world',100,150)
//渐变
let g = ctx.createLinearGradient(0,0,600,0)
g.addColorStop(0,'red')
g.addColorStop(0.25,'yellow')
g.addColorStop(0.5,'orange')
g.addColorStop(.75,'hotpink')
g.addColorStop(1, 'purple')
ctx.fillStyle = g
// 注意,文字是以左下角为初始坐标
ctx.font = "italic 700 50px cursive"
ctx.fillText('holle world',200,100)
ctx.strokeStyle = g
ctx.strokeText('holle world',200,250)
direction = "ltr" || "rtl" || "inherit"
textAlign = "left" || "right" || "center" || "start" || "end"
//渐变
let g = ctx.createLinearGradient(0,0,600,0)
g.addColorStop(0,'red')
g.addColorStop(0.25,'yellow')
g.addColorStop(0.5,'orange')
g.addColorStop(.75,'hotpink')
g.addColorStop(1, 'purple')
ctx.fillStyle = g
// 注意,文字是以左下角为初始坐标
ctx.font = "italic 700 50px cursive"
ctx.fillText('holle world',200,100)
ctx.strokeStyle = g
// 文本方向从右向左
ctx.direction = 'rtl'
ctx.strokeText('holle world',200,250)
const x = canvas.width / 2;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
//渐变
let g = ctx.createLinearGradient(0,0,600,0)
g.addColorStop(0,'red')
g.addColorStop(0.25,'yellow')
g.addColorStop(0.5,'orange')
g.addColorStop(.75,'hotpink')
g.addColorStop(1, 'purple')
ctx.fillStyle = g
// 注意,文字是以左下角为初始坐标
ctx.font = "italic 700 50px cursive"
ctx.textAlign = 'left';
ctx.fillText('holle world',x,100)
ctx.textAlign = 'center';
ctx.fillText('holle world',x,150)
ctx.textAlign = 'right';
ctx.fillText('holle world',x,200)
ctx.textAlign = 'start';
ctx.fillText('holle world',x,350)
ctx.textAlign = 'end';
ctx.fillText('holle world',x,400)
textBaseline = "top" || "hanging" || "middle" || "alphabetic" || "ideographic" || "bottom"
https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/textBaseline
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const baselines = ['top', 'hanging', 'middle', 'alphabetic', 'ideographic', 'bottom'];
ctx.font = '36px serif';
ctx.strokeStyle = 'red';
baselines.forEach((baseline, index) => {
ctx.textBaseline = baseline;
const y = 75 + index * 75;
ctx.beginPath();
ctx.moveTo(0, y + 0.5);
ctx.lineTo(550, y + 0.5);
ctx.stroke();
ctx.fillText(`Abcdefghijklmnop (${baseline})`, 0, y);
});
filter
https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/filter
图片为网图
// 滤镜
// ctx.filter = 'none' // 默认
ctx.filter = 'blur(4px)' // 给绘图提供一个高斯模糊
let img = new Image()
img.src = './2.jpg'
img.onload = function(){
ctx.drawImage(img, 350,150,700,600,100,100,200,200)
}
// 百分比。给绘图提供一个线性乘法,调节亮度的高低
ctx.filter = 'brightness(50%)'
// 百分比。给绘图提供一个线性乘法,调节亮度的高低
ctx.filter = 'brightness(100%)'
// 百分比。调节图像的对比度。当数值为 0% 时,图像会完全变黑。当数值为 100% 时,图像没有任何变化
ctx.filter = 'contrast(150%)'
// 百分比。将图像转换成灰色的图片。当值为 100% 时,图像会完全变成灰色。当值为 0% 时,图像没有任何变化
ctx.filter = 'grayscale(80%)'
// 度数。对图像进行色彩旋转的处理。当值为 0 度时,图像没有任何变化
ctx.filter = 'hue-rotate(45deg)'
// 百分比。反色图像(呈现出照片底片的效果)。当值为 100% 时,图像会完全反色处理。当值为 0% 时,图像没有任何变化
ctx.filter = 'invert(100%)'
// 百分比。对图像进行透明度的处理。当值为 0% 时,图像完全透明。当值为 100% 时,图像没有任何变化
ctx.filter = 'opacity(50%)'
// 对图像进行饱和度的处理。当值为 0% 时,图像完全不饱和。当值为 100% 时,图像没有任何变化。
ctx.filter = 'saturate(30%)'
//对图像进行深褐色处理(怀旧风格)。当值为 100% 时,图像完全变成深褐色。当值为 0% 时,图像没有任何变化
ctx.filter = 'sepia(100%)'
位置的变化都是基于原点的变换, context 默认的原点都是在 0,0 位置
translate(x, y)
rotate(deg)
scale(x, y);
transform(a, b, c, d, e, f) 你可以缩放、旋转、移动和倾斜上下文
**注意: 当你使用变换改变图形位置时,默认的开始位置也会改变。所以在这后面绘制的图形,全都会偏移 x,y **
位移
ctx.translate(100,100)
// 在坐标0,0位置绘制一个方形
ctx.fillRect(0,0,100,100)
如果你还想作画,并且从 0 ,0开始,那么你就需要使用
save() 跟restore()
ctx.save()
ctx.translate(100,100)
// 在坐标0,0位置绘制一个方形
ctx.fillRect(0,0,100,100)
ctx.restore()
ctx.strokeRect(0,0,200,200)
旋转
ctx.translate(100,100)
ctx.rotate('45')
// 在坐标0,0位置绘制一个方形
ctx.fillRect(0,0,100,100)
缩放
正值放大,0.0 - 1 为缩小
ctx.translate(100,100)
ctx.scale(1.5,2)
// 在坐标0,0位置绘制一个方形
ctx.fillRect(0,0,100,100)
前置知识
save() 是 Canvas 2D API 通过将当前状态放入栈中,保存 canvas 全部状态的方法
restore() 是 Canvas 2D API 通过在绘图状态栈中弹出顶端的状态,将 canvas 恢复到最近的保存状态的方法
clearRect(x, y, width, height) 这个方法通过把像素设置为透明以达到擦除一个矩形区域的目的
const canvas = document.createElement('canvas')
canvas.width = 800
canvas.height = 600
document.body.appendChild(canvas)
// 获取context(画笔)对象
const ctx = canvas.getContext('2d')
let earthAngle = 0
let moonAngle = 0
function loop(){
// 保存 canvas 全部状态的方法
ctx.save()
// 清除画布
ctx.clearRect(0,0,canvas.width,canvas.height)
// 画太阳
ctx.beginPath()
ctx.arc(400,300, 50 ,0,2 * Math.PI)
ctx.fillStyle = '#e3fa14'
ctx.shadowColor = '#e3fa14'
ctx.shadowBlur = 10
ctx.fill()
ctx.closePath()
// 画地球
ctx.beginPath()
// 先把canvas 原点移动正中心
ctx.translate(400,300)
// 让画笔旋转一个角度
ctx.rotate((earthAngle += 0.1) * Math.PI / 180)
// 为了后面画月亮的动画,要把原点编程和地球中心完全一致
ctx.translate(200,0)
ctx.arc(0,0,20,0,2 * Math.PI)
ctx.fillStyle = 'blue'
ctx.shadowBlur = 0
ctx.fill()
ctx.closePath()
// 画月亮
ctx.beginPath()
ctx.fillStyle = '#fff'
ctx.fillStyle = '#fff'
ctx.shadowBlur = 8
ctx.rotate((moonAngle += 0.5) * Math.PI / 180)
ctx.arc(40,0,6,0,2 * Math.PI)
ctx.fill()
ctx.closePath()
// 恢复
ctx.restore()
requestAnimationFrame(loop)
}
requestAnimationFrame(loop)
<!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{
display: block;
}
</style>
</head>
<body>
<canvas id="mycanvas"></canvas>
</body>
</html>
<script>
var canvas = document.getElementById('mycanvas');
var ctx = canvas.getContext('2d');
//画布的尺寸
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
function rn(mix,max){
return parseInt(Math.random()*(max-mix)+mix)
}
function rc(min,max){
var r = rn(min,max);
var g = rn(min,max);
var b = rn(min,max);
return `rgb(${r},${g},${b})`
}
function Ball(){
//小球生成的随机位置
this.x = parseInt(Math.random() * canvas.width + 20);
this.y = parseInt(Math.random() * canvas.height) + 20;
this.r = 10;
this.color = rc(150,230);
//小球的运行轨迹
this.dx = parseInt(Math.random()*10) - 5;
this.dy = parseInt(Math.random()*10) - 5;
console.log(this.x,this.y)
ballArr.push(this);
}
//小球更新
Ball.prototype.update = function(){
this.x +=this.dx;
this.y +=this.dy;
if(this.x < this.r || this.x > canvas.width - this.r){
this.dx = -this.dx;
}
if(this.y < this.r || this.y > canvas.height - this.r){
this.dy = -this.dy;
}
}
//小球的渲染
Ball.prototype.render = function(){
ctx.beginPath();
//透明度
ctx.globaAlpha =1;
//话小球
ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
ctx.fillStyle = this.color;
ctx.fill();
}
//创建20个小球;
let ballArr = [];
for(var i = 0;i<30;i++){
new Ball();
}
//定时器
setInterval(function(){
//清除画布
ctx.clearRect(0,0,canvas.width,canvas.height)
for(var i=0;i< ballArr.length;i++){
ballArr[i].render();
ballArr[i].update();
}
},20)
</script>