前端之学习 canvas 的基础语法

前端之学习 canvas 的基础语法

canvas

前言

今天,学习了 canvas 的一些语法知识和使用,在这里分享给大家一起学习交流
首先,我们应该了解 canvas 是什么,有什么用,文章最后,我们将使用 canvas 绘制一个刮刮卡的中奖特效!

什么是 canvas

定义:是 HTML5 提供的一种新标签, ie9 才开始支持的,Canvas 是一个矩形区域的画布,可以用 JS 控制每一个像素在上面绘画。canvas 标签使用 JavaScript 在网页上绘制图像,本身不具备绘图功能。canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。

vscode 代码提示

在了解 canvas 之后,大家一定迫不及待地想加入 canvas 的学习之路吧。

  • 首先,我们使用 vscode 新建一个 html 文件,
    并在body标签中使用 canvas 标签,给定一定的大小,这样,canvas 画布就建好了
<canvas class="canvas" width="600" height="400"></canvas>
  • script标签中,开始使用我们神奇的画笔,工欲善其事必先利其器,开始之前,我们使用 /** @type {HTMLCanvasElement} */ ,vscode 会给出 canvas 相关 api 的智能提示,让我们更顺畅的完成我们的代码
  // 获取画布
  /** @type {HTMLCanvasElement} */
  let canvas = document.querySelector('.canvas')
  // 获取画布上下文对象
  let ctx = canvas.getContext('2d')
绘制二次贝塞尔曲线

首先,我们使用贝塞尔曲线绘制一个聊天气泡
在 canvas 中,一般相邻两个值为一组对象,代表 x 坐标和 y 坐标,而浏览器左上角即为原点位置
比如:
ctx.moveTo(200, 300)中 200 和 300 分别代表 x 轴和 y 轴坐标,即表示一个点
ctx.quadraticCurveTo(150, 300, 150, 200) 中两两为一组,即代表了两个点的坐标
二次贝塞尔曲线通过和起始点的三个点既可以画出一条圆滑的曲线,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bIfBgoG9-1675793087508)(https://yhyblog-2023-2-8.oss-cn-hangzhou.aliyuncs.com/Snipaste_2023-02-08_00-25-27.png “贝塞尔曲线”)]

   // 画笔移动到指定位置
  ctx.moveTo(200, 300)
  // 开始画笔
  ctx.beginPath()
  // 绘制多条贝塞尔曲线
  ctx.quadraticCurveTo(150, 300, 150, 200)
  ctx.quadraticCurveTo(150, 100, 300, 100)
  ctx.quadraticCurveTo(450, 100, 450, 200)
  ctx.quadraticCurveTo(450, 300, 250, 300)
  ctx.quadraticCurveTo(250, 350, 150, 350)
  ctx.quadraticCurveTo(200, 350, 200, 300)
  // 填充颜色
  ctx.stroke()
  // 结束画笔
  ctx.closePath()

聊天气泡
这样,一个聊天气泡框就画好了,后面我们还会学习如何修改颜色或填充颜色

绘制三次贝塞尔曲线

三次贝塞尔曲线和二次贝塞尔曲线类似,不同的是每次使用四个点完成一条曲线的绘画,使用的方法名也不同
接下来,我们使用三次贝塞尔曲线绘画一个爱心

一段三次贝塞尔曲线如下:

  //开始画笔
  ctx.beginPath()
  //三次贝塞尔曲线画一个爱心
  ctx.moveTo(300, 200)
  ctx.bezierCurveTo(350, 150, 500, 200, 300, 350)

  //开始画笔
  ctx.beginPath()
  //三次贝塞尔曲线画一个爱心
  ctx.moveTo(300, 200)
  ctx.bezierCurveTo(350, 150, 500, 200, 300, 350)
  ctx.bezierCurveTo(100, 200, 250, 150, 300, 200)
  ctx.stroke()
  // 结束画笔
  ctx.closePath()

绘制文字
  • font 设置文字大小和字体

     //设置文字大小和字体
      ctx.font = "30px Microsoft Yahei"
    
  • strokeStyle 设置文字颜色

      // 设置文字颜色
       ctx.strokeStyle = "#ffcc2a"
    
  • fillText 设置文字内容和位置

      // 设置文字内容和位置
       ctx.fillText("Hello World", 100, 100)
    
  • textAlign 设置文字对齐方式

      //设置文字对齐方式 start left center right end
      ctx.textAlign = "start"
    
  • textBaseline 文本基线对齐

      //文本基线对齐 start top middle bottom end
      ctx.textBaseline = "middle"
    
  • direction 文字方向

       //文字方向 rtl ltr
       ctx.direction = "rtl"
    
  • strokeText 设置文字轮廓

      //设置文字轮廓
      ctx.strokeText("Hello World", 300, 100)
    
  • 预测量文字宽度

/*
    预测量文字宽度
    actualBoundingBoxAscent 实际文字上边距
    actualBoundingBoxDescent 实际文字下边距
    actualBoundingBoxLeft 实际文字左边距
    actualBoundingBoxRight 实际文字右边距
    fontBoundingBoxAscent 字体上边距
    fontBoundingBoxDescent 字体下边距
    width 文字宽度
 */
  let result = ctx.measureText("Hello World")
  console.log(result)

绘制图片

绘制图片有三种方式:

  • 第一种:默认绘制图片的长宽
    let img = new Image()
    img.src = "./imgs/girl.webp"
    img.onload = () => {
      // 第一种绘制图片方式 (默认绘制图片的长宽)
      ctx.drawImage(img, 0, 0)
    }
    
  • 第二种绘制图片方式: 第一组坐标参数:绘制起点位置,第二组坐标参数:绘制长宽
  let img = new Image()
  img.src = "./imgs/girl.webp"
  img.onload = () => {
    //第二种绘制图片方式 (第一组:绘制起点位置,第二组:绘制长宽)
    ctx.drawImage(img, 0, 0, 200, 400)
  }

  • 第三种绘制图片方式 第一组坐标参数:裁剪起点位置,第二组坐标参数:裁剪长宽,第三组坐标参数:绘制起点位置,第四组坐标参数:绘制长宽
  let img = new Image()
  img.src = "./imgs/girl.webp"
  img.onload = () => {
    //第三种绘制图片方式(第一组:裁剪起点位置,第二组:裁剪长宽,第三组:绘制起点位置,第四组:绘制长宽)
    ctx.drawImage(img, 50, 50, 300, 150, 0, 0, 200, 200)
  }

绘制视频

body 标签中添加 video 标签和 button 按钮控制视频播放

<video class="video" src="./video/video.mp4" controls></video>
<button class="btn">播放/暂停</button>
  //获取视频对象
  let video = document.querySelector('.video')
  //获取按钮
  let btn = document.querySelector('.btn')
  btn.onclick = () => {
    //播放或暂停视频
    video.paused ? video.play() : video.pause()
    render()
  }
  function render() {
    //画出视频图像
    ctx.drawImage(video, 0, 0, 600, 400)

    //requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。循环调用 render,就可以实现播放视频的效果
    requestAnimationFrame(render)
  }

位移变换

首先,给 canvas 添加 border 样式,并且绘制一个矩形

<style>
  .canvas {
    border: 1px solid #000;
  }
</style>
<script lang="js">
  /** @type {HTMLCanvasElement} */
  let canvas = document.querySelector('.canvas')
  //获取画笔上下文对象
  let ctx = canvas.getContext('2d')
  ctx.fillRect(0, 0, 100, 50)
</script>

canvas 的位移变化都是坐标系的改变,即浏览器左上角原点形成的坐标系的变化

  //坐标系位移
  ctx.translate(100, 100)

 // 缩放
  ctx.scale(2, 2)

  // 旋转
  ctx.rotate(Math.PI / 4)

通过矩阵的方式位移变换

  //平移 1,0,0,1分别代表x轴和y轴的缩放比例,200,200分别代表x轴和y轴的位移
  ctx.transform(1, 0, 0, 1, 200, 200)
  //旋转 transform(Math.cos(Math.PI / 4), Math.sin(Math.PI / 4), -Math.sin(Math.PI / 4), Math.cos(Math.PI / 4), 0, 0)
  ctx.transform(1, 1, -1, 1, 0, 0)
  //缩放  2,2分别代表x轴和y轴的缩放比例
  ctx.transform(2, 0, 0, 2, 0, 0)
线段和虚线样式设置

绘制线段

    ctx.moveTo(200, 200)
    // 绘制直线
    ctx.lineTo(350, 250)
    // 绘制直线
    ctx.lineTo(440, 200)

绘制虚线

绘制虚线先绘制线段,再设置让线段分割成为虚线

    ctx.moveTo(200, 200)
    // 绘制直线
    ctx.lineTo(350, 250)
    // 绘制直线
    ctx.lineTo(440, 200)
    //虚线样式
    ctx.setLineDash([20, 20])
    //虚线偏移量
    ctx.lineDashOffset = index

使用 render 方法让虚线移动

  let index = 0
  //线段移动
  function render() {
    //清除上次绘画的矩形
    ctx.clearRect(0, 0, 600, 400)

    index++

    if (index > 400) {
      index = 0
    }

    ctx.moveTo(200, 200)
    // 绘制直线
    ctx.lineTo(350, 250)
    // 绘制直线
    ctx.lineTo(440, 200)

    // 设置线条样式
    ctx.lineWidth = 1

    // 设置线条端点样式,round为圆形,square为方形,butt为直角
    ctx.lineCap = 'butt'

    //设置线段连接处样式,round为圆形,bevel为斜角,mitter为直角
    ctx.linejoin = 'mitter'
    // 设置线段连接处最大长度
    ctx.miterLimit = 6
    //虚线样式
    ctx.setLineDash([20, 20])
    //虚线偏移量
    ctx.lineDashOffset = index

    ctx.stroke()
    //循环调用,实现动画效果
    requestAnimationFrame(render)
  }
  render()

阴影

阴影属性和 css 的阴影类似

  • shadowOffsetX: 阴影的水平偏移量
  • shadowOffsetY: 阴影的垂直偏移量
  • shadowBlur: 阴影的模糊程度
  • shadowColor: 阴影的颜色
  // path2D 对象 用于存储路径
  let heartPath = new Path2D()
  //二次贝塞曲线画一个爱心
  heartPath.moveTo(300, 200)
  heartPath.bezierCurveTo(350, 150, 500, 200, 300, 350)
  heartPath.bezierCurveTo(100, 200, 250, 150, 300, 200)

  // shadowOffsetX: 阴影的水平偏移量 shadowOffsetY: 阴影的垂直偏移量 shadowBlur: 阴影的模糊程度 shadowColor: 阴影的颜色
  ctx.shadowOffsetX = 10
  ctx.shadowOffsetY = 10
  ctx.shadowBlur = 5
  ctx.shadowColor = 'rgba(255, 200, 200, 1)'
  ctx.stroke(heartPath)

裁剪图片

绘制图片后,使用 ctx.clip()则可对图片进行裁剪
如下:原本图片被爱心形状裁剪

  // path2D对象 用于创建路径
  let heart = new Path2D()
  //二次贝塞曲线画一个爱心
  heart.moveTo(300, 200)
  heart.bezierCurveTo(350, 150, 500, 200, 300, 350)
  heart.bezierCurveTo(100, 200, 250, 150, 300, 200)
  //clip() 方法从原始画布中剪切任意形状和尺寸的区域
  ctx.clip(heart)

  let img = new Image()
  img.src = "./imgs/girl.webp"
  img.onload = () => {
    //第三种绘制图片方式(第一组:裁剪起点位置,第二组:裁剪长宽,第三组:绘制起点位置,第四组:绘制长宽)
    ctx.drawImage(img, 80, 70, 250, 250, 200, 180, 200, 300)
    ctx.lineWidth = 5
    ctx.strokeStyle = "red"
    ctx.stroke(heart)
  }
  ctx.stroke(heart)

合成图像

使用合成图像,做一个刮刮卡抽奖效果

  • 首先画布 canvas 样式和刮刮卡都定位到左上角,使用 z-index 使画布居于抽奖文字上层
  • 绘制刮刮卡图片,遮盖文字
  • 鼠标按下时,开启绘画,globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上,由于属性很多,具体参考canvas 中的合成------globalCompositeOperation 一文 ,这里使用**destination-out** 从画布中删除刮刮卡图像,画笔绘画时绘画弧形模仿橡皮差效果
  • 鼠标抬起时,停止绘画
<style>
  * {
    margin: 0;
    padding: 0;
  }

  .ggk {
    width: 600px;
    height: 400px;
    line-height: 400px;
    text-align: center;
    font-weight: 900;
    overflow: hidden;
    font-size: 30px;
    position: absolute;
    left: 0;
    top: 0;
    z-index: -1;
  }

  .canvas {
    border: 1px solid #000;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 1;
  }
</style>

<body>
  <div class="ggk">谢谢惠顾</div>
  <canvas class="canvas" width="600" height="400"></canvas>
</body>
  //绘制刮刮卡图片
  let img = new Image()
  img.src = './imgs/ggk.webp'
  img.onload = () => {
    ctx.drawImage(img, 0, 0, 600, 400)
  }

  // isDraw 用来判断是否在画布上绘制
  let isDraw = false
  canvas.onmousedown = (e) => {
    isDraw = true
  }
  canvas.onmouseup = (e) => {
    isDraw = false
  }
  // onmousemove 在鼠标指针移动时,不断绘画
  canvas.onmousemove = (e) => {
    if (isDraw) {
      // 获取鼠标指针的坐标
      let x = e.pageX
      let y = e.pageY
      // globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上
      // destination-out 从目标图像中删除源图像
      ctx.globalCompositeOperation = 'destination-out'
      // arc() 方法使用一个中心点和半径,为画布的当前子路径添加一条弧。
      ctx.arc(x, y, 20, 0, Math.PI * 2)
      ctx.fill()
    }
  }
  let random = Math.random()
  if (random < 0.5) {
    let ggk = document.querySelector('.ggk')
    ggk.innerHTML = '恭喜你中奖了'
  }

刮刮卡刮奖前

刮刮卡刮奖后

原创不易,觉得文章还不错或者感兴趣可以点赞或者收藏一下哦!欢迎留言评论和关注一下!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值