canvas的魅力

前言

canvas 在很多时候能带给我们意想不到的结果
例如: 粒子效果、树状图、饼图、文字渐变…
所以打算花一些时间去学习,这里将是记录学习的地方

小技巧

方法一

很多人再编写canvas的时候没有智能的提示,也就是 . 的时候没有东西出来
再最上面写上这段注释即可

/** @type {HTMLCanvasElement} */
 let canvas = document.querySelector('#canvas')
 let ctx = c1.getContext('2d');
 ctx.fillRect(100, 100, 100, 100)

方法二

直接使用script脚本创建canvas元素,也会有相应的代码提示
在这里插入图片描述

canvas的基本使用

使用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>Document</title>
  <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 = 10X 偏移量
shadowOffsetY = 10Y 偏移量
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>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值