Canvas基础知识

275 阅读3分钟

1. Canavs绘制直线

<canvas id="canvas"></canvas>

<script>
  const myCanvas = document.querySelector('#canvas')

  myCanvas.width = 800
  myCanvas.height = 800

  const context = myCanvas.getContext("2d")
  context.moveTo(100, 100)
  context.lineTo(700, 700)
  context.stroke()
</script>
  • ctx.lineWidth = 10, 设置线条的宽度
  • ctx.strokeStyle = "#058", 设置线条的宽度
  • canvas是基于状态的绘制

2. Canavs绘制多条线段

<canvas id="canvas"></canvas>

<script>
  const myCanvas = document.querySelector('#canvas')

  myCanvas.width = 800
  myCanvas.height = 800

  const context = myCanvas.getContext("2d")
	context.lineWidth = 10

	context.beginPath()
  context.moveTo(100, 200)
  context.lineTo(300, 400)
	context.lineTo(100, 600)
	context.strokeStyle = "red"
  context.stroke()

  context.beginPath()
  context.moveTo(300, 200)
  context.lineTo(500, 400)
	context.lineTo(300, 600)
	context.strokeStyle = "green"
  context.stroke()

  context.beginPath()
  context.moveTo(500, 200)
  context.lineTo(700, 400)
	context.lineTo(500, 600)
	context.strokeStyle = "blue"
  context.stroke()
</script>
  • ctx.beginPath(), 清空子路径列表开始一个新路径的方法。

3. Canavs绘制封闭的多边形

<canvas id="canvas"></canvas>

<script>
  const myCanvas = document.querySelector('#canvas')

  myCanvas.width = 800
  myCanvas.height = 800

  const context = myCanvas.getContext("2d")

	context.beginPath()
  context.moveTo(100, 200)
  context.lineTo(300, 400)
	context.lineTo(100, 600)
  context.closePath()

	context.lineWidth = 10
  context.strokeStyle = "green"
  context.fillStyle = 'yellow'

  context.fill()
  context.stroke()
</script>
  • ctx.closePath(), 将笔点返回到当前子路径起始点的方法。
  • ctx.fill(), 填充
  • 填充和描边,下面的会覆盖上面的部分

4. Canavs绘制矩形

<canvas id="canvas"></canvas>

<script>
  const myCanvas = document.querySelector('#canvas')

  myCanvas.width = 800
  myCanvas.height = 800

  const context = myCanvas.getContext("2d")

  drawRect(context, 100, 100, 400, 400, 10, "#058", "red")

  function drawRect(ctx, x, y, width, height, borderWidth, borderColor, fillColor) {
    ctx.beginPath()
    ctx.moveTo(x, y)
    ctx.lineTo(x + width, y)
    ctx.lineTo(x + width, y + height)
    ctx.lineTo(x, y + height)
    ctx.closePath()

    ctx.lineWidth = borderWidth
    ctx.strokeStyle = borderColor
    ctx.fillStyle = fillColor

    ctx.fill()
    ctx.stroke()
  }

  // 方法二
  function drawRect2(ctx, x, y, width, height, borderWidth, borderColor, fillColor) {
    ctx.lineWidth = borderWidth
    ctx.strokeStyle = borderColor
    ctx.fillStyle = fillColor

    ctx.fillRect(x, y, width, height)
    ctx.strokeRect(x, y, width, height)
  }
</script>
  • ctx.closePath(), 将笔点返回到当前子路径起始点的方法。
  • ctx.lineCap,指定如何绘制每一条线段末端的属性。

5. Canavs绘制五角星

<canvas id="canvas"></canvas>

<script>
  const myCanvas = document.querySelector('#canvas')

  myCanvas.width = 800
  myCanvas.height = 800

  const context = myCanvas.getContext("2d")

  drawStar(context, 150, 300, 400, 400, 30)

  function drawStar(ctx, r, R, x, y, rotate) {
    ctx.beginPath()
    for (let i = 0; i < 5; i++) {
      ctx.lineTo(Math.cos((18 + i * 72 - rotate) / 180 * Math.PI) * R + x, -Math.sin((18 + i * 72 - rotate) / 180 * Math.PI) * R + y)
      ctx.lineTo(Math.cos((54 + i * 72 - rotate) / 180 * Math.PI) * r + x, -Math.sin((54 + i * 72 - rotate) / 180 * Math.PI) * r + y)
    }
    ctx.closePath()
    ctx.stroke()
  }
</script>
  • ctx.lineJoin,用来设置 2 个长度不为 0 的相连部分(线段、圆弧、曲线)如何连接在一起的属性

6. Canavs图像变换和状态保存

Canavs使用图像变换,需要使用状态的保存和恢复;因为Canavs图像变换操作是叠加的

(1)图像变换

  • 位移translate(x, y)
  • 旋转rotate(deg)
  • 缩放scale(sx, sy)

(2)状态的保存和恢复

  • save()
  • restore()
const myCanvas = document.querySelector('#canvas')

myCanvas.width = 800
myCanvas.height = 800

const context = myCanvas.getContext('2d')
context.fillStyle = "black"
context.fillRect(0, 0, myCanvas.width, myCanvas.height)

for (let i = 0; i < 200; i++) {
  const r = Math.random() * 10 + 10;
  const x = Math.random() * myCanvas.width;
  const y = Math.random() * myCanvas.height;
  const a = Math.random() * 360
  drawStar(context, r / 2.0, r, x, y, a)
}

function drawStar(ctx, r, R, x, y, rot) {
  ctx.save()
  ctx.translate(x, y)
  ctx.rotate(rot / 180 * Math.PI)

  starPath(ctx, r, R)

  ctx.fillStyle = "#fb3"
  ctx.strokeStyle = "#fd5"
  ctx.lineWidth = 3
  ctx.lineJoin = "round"

  ctx.fill()
  ctx.stroke()

  ctx.restore()
}

function starPath(ctx, r, R) {
  ctx.beginPath()
  for (let i = 0; i < 5; i++) {
    ctx.lineTo(Math.cos((18 + i * 72) / 180 * Math.PI) * R, -Math.sin((18 + i * 72) / 180 * Math.PI) * R)
    ctx.lineTo(Math.cos((54 + i * 72) / 180 * Math.PI) * r, -Math.sin((54 + i * 72) / 180 * Math.PI) * r)
  }
  ctx.closePath()
}
  • ctx.scale(sx, sy),会缩放左上端点的坐标值,会缩放描边宽度,及会缩放图形宽高

7. Canavs矩阵变换transform

// a, d 水平、垂直缩放
// b, c 水平、垂直倾斜
// e, f 水平、垂直位移
ctx.transform(a, b, c, d, e, f)
  • ctx.settransform(),覆盖之前所有的ctx.transform()

8. Canavs渐变色

(1)线性渐变

const myCanvas = document.querySelector('#canvas')

myCanvas.width = 800
myCanvas.height = 800

const context = myCanvas.getContext('2d')

const linearGrad = context.createLinearGradient(0, 0, 800, 800)
linearGrad.addColorStop(0.0, '#fff')
linearGrad.addColorStop(1.0, '#000')
context.fillStyle = linearGrad
context.fillRect(0, 0, 800, 800)

(2)径向渐变

const myCanvas = document.querySelector('#canvas')

myCanvas.width = 800
myCanvas.height = 800

const context = myCanvas.getContext('2d')

const radialGrad = context.createRadialGradient(400, 400, 0, 400, 400, 500)
radialGrad.addColorStop(0.0, 'white')
radialGrad.addColorStop(0.25, 'yellow')
radialGrad.addColorStop(0.5, 'green')
radialGrad.addColorStop(0.75, 'blue')
radialGrad.addColorStop(1.0, 'black')
context.fillStyle = radialGrad
context.fillRect(0, 0, 800, 800)

(3)背景图片、画布或video

  • context.createPattern(image/canvas/video, repeat-style)

// 图片作为背景图
const myCanvas = document.querySelector('#canvas')

myCanvas.width = 800
myCanvas.height = 800

const context = myCanvas.getContext('2d')

const backgroundImage = new Image()
backgroundImage.src = './img/flower.png'
backgroundImage.onload = function() {
  const pattern = context.createPattern(backgroundImage, 'repeat')
  context.fillStyle = pattern
  context.fillRect(0, 0, 800, 800)
}

// canvas作为背景图
const myCanvas = document.querySelector('#canvas')

myCanvas.width = 800
myCanvas.height = 800

const context = myCanvas.getContext('2d')

const pattern = context.createPattern(createBackgroundCanvas(), 'repeat')
context.fillStyle = pattern
context.fillRect(0, 0, 800, 800)

function createBackgroundCanvas() {
  const backgroundCanvas = document.createElement('canvas')
  backgroundCanvas.width = 100
  backgroundCanvas.height = 100
  const bgCtx = backgroundCanvas.getContext('2d')
  drawStar(bgCtx, 25, 50, 50, 50, 0)

  return backgroundCanvas
}

function drawStar(ctx, r, R, x, y, rot) {
  ctx.save()
  ctx.translate(x, y)
  ctx.rotate((rot / 180) * Math.PI)

  starPath(ctx, r, R)

  ctx.fillStyle = '#fb3'
  ctx.strokeStyle = '#fd5'
  ctx.lineWidth = 3
  ctx.lineJoin = 'round'

  ctx.fill()
  ctx.stroke()

  ctx.restore()
}

function starPath(ctx, r, R) {
  ctx.beginPath()
  for (let i = 0; i < 5; i++) {
    ctx.lineTo(
      Math.cos(((18 + i * 72) / 180) * Math.PI) * R,
      -Math.sin(((18 + i * 72) / 180) * Math.PI) * R
    )
    ctx.lineTo(
      Math.cos(((54 + i * 72) / 180) * Math.PI) * r,
      -Math.sin(((54 + i * 72) / 180) * Math.PI) * r
    )
  }
  ctx.closePath()
}

9. Canavs绘制圆角矩形

  • ctx.arc(cx, cy, r, startDeg, endDeg),绘制圆弧路径。

const myCanvas = document.querySelector('#canvas')

myCanvas.width = 800
myCanvas.height = 800

const context = myCanvas.getContext('2d')

// 绘制4x4的网格
fillRoundRect(context, 150, 150, 500, 500, 10, '#bbada0')
for (let i = 0; i < 4; i++) {
  for (let j = 0; j < 4; j++) {
    fillRoundRect(context, 170 + i * 120, 170 + j * 120, 100, 100, 6, '#ccc0b3')
  }
}

// 填充的圆角矩形
function fillRoundRect(ctx, x, y, width, height, radius, fillStyle = 'black') {
  if (2 * radius > width || 2 * radius > height) return
  ctx.save()
  ctx.translate(x, y)
  roundRectPath(ctx, width, height, radius)
  ctx.fillStyle = fillStyle
  ctx.fill()
  ctx.restore()
}

// 描边的圆角矩形
function strokeRoundRect(ctx, x, y, width, height, radius, lineWidth = 1, strokeStyle = 'black') {
  if (2 * radius > width || 2 * radius > height) return
  ctx.save()
  ctx.translate(x, y)
  roundRectPath(ctx, width, height, radius)
  ctx.lineWidth = lineWidth
  ctx.strokeStyle = strokeStyle
  ctx.stroke()
  ctx.restore()
}

function roundRectPath(ctx, width, height, radius) {
  ctx.beginPath()
  ctx.arc(width - radius, height - radius, radius, 0, Math.PI / 2)
  ctx.lineTo(radius, height)
  ctx.arc(radius, height - radius, radius, Math.PI / 2, Math.PI)
  ctx.lineTo(0, radius)
  ctx.arc(radius, radius, radius, Math.PI, Math.PI * 3 / 2)
  ctx.lineTo(width - radius, 0)
  ctx.arc(width - radius, radius, radius, Math.PI * 3 / 2, Math.PI * 2)
  ctx.closePath()
}

10. Canavs绘制弯月

  • ctx.moveTo(x0, y0),开始点

  • ctx.arcTo(x1, y1, x2, y2, radius),控制点和结束点

const myCanvas = document.querySelector('#canvas')

myCanvas.width = 800
myCanvas.height = 800

const context = myCanvas.getContext('2d')

fillMoon(context, 2, 400, 400, 300, 0)

function fillMoon(ctx, d, x, y, R, rotate, fillStyle = '#fb5') {
  ctx.save()
  ctx.translate(x, y)
  ctx.rotate(rotate * Math.PI / 180)
  ctx.scale(R, R)
  moonPath(ctx, d)
  ctx.fillStyle = fillStyle
  ctx.fill()
  ctx.restore()
}

function moonPath(ctx, d) {
  ctx.beginPath()
  ctx.arc(0, 0, 1, 0.5 * Math.PI, 1.5 * Math.PI, true)
  ctx.moveTo(0, -1)
  ctx.arcTo(d, 0, 0, 1, dis(0, -1, d, 0) / d)
  ctx.closePath()
}

function dis(x1, y1, x2, y2) {
  return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
}

11. 贝赛尔曲线

  • ctx.moveTo(x0, y0),开始点

  • ctx.quadraticCurveTo(cpx, cpy, x, y),二次贝塞尔曲线路径。cpx,cpy为控制点的 x, y 轴坐标,x, y为终点的 x, y 轴坐标。

  • ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y),三次贝塞尔曲线路径。cpx,cpy为控制点的 x, y 轴坐标,x, y为终点的 x, y 轴坐标。

// 绘制波浪线
const myCanvas = document.querySelector('#canvas')

myCanvas.width = 800
myCanvas.height = 800

const context = myCanvas.getContext('2d')

drawLand(context)

function drawLand(ctx) {
  ctx.save()

  ctx.beginPath()
  ctx.moveTo(0, 600)
  ctx.bezierCurveTo(540, 400, 660, 800, 1200, 600)
  ctx.lineTo(1200, 800)
  ctx.lineTo(0, 800)
  ctx.closePath()

  const landStyle = ctx.createLinearGradient(0, 800, 0, 0)
  landStyle.addColorStop(0.0, '#030')
  landStyle.addColorStop(1.0, '#580')
  ctx.fillStyle = landStyle

  ctx.fill()

  ctx.restore()
}

12. canvas文字渲染

  • ctx.fillText(text, x, y, [maxWidth])
  • ctx.font = font-style 属性, font-variant 属性, font-weight 属性, font-size 属性(必须), line-height 属性, font-family 属性(必须)。例如: ctx.font = "bold 48px serif"
  • ctx.textAlign = "left" || "right" || "center" || "start" || "end"
  • ctx.textBaseline = "top" || "hanging" || "middle" || "alphabetic" || "ideographic" || "bottom",当前文本基线的属性
  • ctx.measureText(text),返回一个关于被测量文本TextMetrics 对象包含的信息(例如它的宽度)。
const myCanvas = document.querySelector('#canvas')

myCanvas.width = 800
myCanvas.height = 800

const context = myCanvas.getContext('2d')

context.font = 'bold 48px Arial'

context.fillStyle = '#058'
context.fillText('canvas text', 40, 100)

context.strokeStyle = '#058'
context.lineWidth = 3
context.strokeText('canvas text', 40, 200)

13. canvas阴影

  • ctx.shadowColor = "#058"
  • ctx.shadowOffsetX = 10
  • ctx.shadowOffsetY = 10
  • ctx.shadowBlur = 5

14. canvas全局变量

  • ctx.globalAlpha = 1.0,设置图形和图片透明度的属性。
  • ctx.globalCompositeOperation = "source-over",设置要在绘制新形状时应用的合成操作的类型。

15. canvas路径方向(非零环绕原则)

// 绘制圆环
const myCanvas = document.querySelector('#canvas')

myCanvas.width = 800
myCanvas.height = 800

const context = myCanvas.getContext('2d')

context.beginPath()
context.arc(400, 400, 300, 0, Math.PI * 2, false)
context.arc(400, 400, 150, 0, Math.PI * 2, true)
context.closePath()

context.fillStyle = "#058"
context.shadowColor = "gray"
context.shadowOffsetX = 10
context.shadowOffsetY = 10
context.shadowBlur = 10
context.fill()

16. canvas检测是否在图形路径里面

<!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>canvas</title>
    <style>
      .canvas-box {
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="canvas-box">
      <canvas id="cvs" width="400" height="400">不支持canvas</canvas>
    </div>

    <script>
      let cvs = document.getElementById('cvs')
      let ctx = cvs.getContext('2d')
      let balls = []

      cvs.width = 400
      cvs.height = 400

      for (let i = 0; i < 10; i++) {
        balls[i] = {
          x: Math.random() * cvs.width,
          y: Math.random() * cvs.height,
          r: Math.random() * 50 + 20,
        }
        circle(balls[i].x, balls[i].y, balls[i].r)
        ctx.fill()
      }

      function circle(x, y, r) {
        ctx.fillStyle = '#058'
        ctx.beginPath()
        ctx.arc(x, y, r, 0, Math.PI * 2)
        ctx.closePath()
      }

      // 监听鼠标事件
      cvs.addEventListener('mousemove', function (e) {
        // 获取鼠标的坐标
        let x = e.clientX - cvs.getBoundingClientRect().left,
          y = e.clientY - cvs.getBoundingClientRect().top
        ctx.clearRect(0, 0, 400, 400)

        // 遍历绘制图形
        for (let i = balls.length; i--; ) {
          circle(balls[i].x, balls[i].y, balls[i].r)
          // 每绘制一个图形就判断一次当前鼠标的坐标是否在这个图形上,然后进行自定义操作
          if (ctx.isPointInPath(x, y)) {
            ctx.fillStyle = '#f00'
          } else {
            ctx.fillStyle = '#058'
          }
          ctx.fill()
        }
      })
    </script>
  </body>
</html>

17. canvas剪辑区域clip()

<!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>canvas</title>
    <style>
      .canvas-box {
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="canvas-box">
      <canvas id="cvs" width="400" height="400">不支持canvas</canvas>
    </div>

    <script>
      let cvs = document.getElementById('cvs')
      let ctx = cvs.getContext('2d')

      cvs.width = 400
      cvs.height = 400

      function circle(x, y, r) {
        ctx.fillStyle = '#058'
        ctx.beginPath()
        ctx.arc(x, y, r, 0, Math.PI * 2)
        ctx.closePath()
      }

      circle(100, 100, 100)
      ctx.fill()
      ctx.clip()

      ctx.font = 'bold 48px Arial'

      ctx.fillStyle = 'white'
      ctx.fillText('canvas text', 0, 100)
      ctx.lineWidth = 3
      ctx.strokeStyle = 'white'
      ctx.strokeText('canvas text', 0, 150)
    </script>
  </body>
</html>