Canvas

Canvas

canvas概述

  1. canvas的像素化

    我们使用canvas绘制了一个图形,一旦绘制成功了,canvas就像素化了它们。

    canvas没有能力,从画布上再次得到这个图形,也就是说我们没有能力去修改已经在画布上的内容,这个就是canvas比较轻量的原因

    ​ 如果我们想要让这个canvas图形移动,必须按照 清屏-更新-渲染的逻辑进行编程

  2. canvas的动画思想

    动画思想就是 清屏-更新-渲染

    实际上动画的生成就是相关静态画面连续播放,这个就是动画的过程,我们把每一次绘制的静态画面叫做 “一帧”

    时间的间隔就表示的是帧的间隔

  3. 面向对象思维实现canvas动画

    因为canvas不能得到已经上屏的对象,所以我们要维持对象的状态。在canvas动画中,我们都使用面向对象来进行编程,因为我们可以使用面向对象的方式来维持canvas需要的属性和状态

canvas的绘制功能

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
绘制矩形
  1. 填充矩形

    ctx.fillStyle = "green";
    ctx.fillRect(x,y,width,height);
    
  2. 绘制矩形边框

    ctx.strokeStyle = "green";
    ctx.strokeRect(x,y,width,height);
    
  3. 清除画布

    ctx.clearRect(x,y,width,height)
    
绘制路径

​ 绘制路径作用是为了设置一个不规则的多边形状态

​ 路径都是闭合的,使用路径进行绘制的时候需要既定的步骤

  1. 设置路径起点

  2. 使用绘制命令画出路径

  3. 封闭路径

  4. 填充或者绘制已经封闭路径的形状

    // 创建一个路径
    ctx.beginPath();
    // 移动绘制点
    ctx.moveTo(100, 100);
    // 描述行进路径
    ctx.lineTo(200, 200);
    ctx.lineTo(400, 180);
    ctx.lineTo(380, 50);
    // 封闭路径
    ctx.closePath();
    // 绘制图形
    ctx.strokeStyle = "pink";
    ctx.stroke();
    ctx.fillStyle = "orange";
    ctx.fill();
    

    stroke()通过线条来绘制图形轮廓

    fill()通过填充路径的内容区域生成实心的图形

    我们在绘制路径的时候可以选择不关闭路径,这个时候会实现自封闭现象(只针对fill)

圆弧
arc(x,y,radius,starAngle,endAngle,anticlockwise)

画一个以(x,y)为圆心的radius为半径的圆弧(圆),从starAngle到endAngle结束,按照anticlockwise给定的方向(默认为顺时针 false)来生成

圆弧也是绘制路径的一种也需要 beginPath() 和 stroke()

透明度

透明度的值是 0~1之间

ctx.globalAlpha = 0.2
线型

我们可以利用 lineWidth 设置线的粗细,属性值必须是数字,默认是1,没有单位。

ctx.lineWidth = 20;

lineCap决定了线段末端的属性

// 默认,线段末端以方形结束
ctx.lineCap = "butt"   
// 线段末端以圆形结束
ctx.lineCap = "round"
// 方形 线段末端以方形结束,增加了一个宽度和线段相同,高度是线段厚度一半的矩形区域
ctx.lineCap = "square" 

lineJoin用来设置图形两端连接处所显示的样子(线段,圆弧,曲线)

ctx.lineJoin = "round";	// 圆弧
ctx.lineJoin = "bevel";	// 平的
ctx.lineJoin = "miter"; // 默认

setLineDash()方法来定义虚线的样式,内部接收一个数组(数组内部是虚线的交替状态)

// 10表示每段虚线的长度 20标识虚线和虚线之间的距离
// 数组内部可以是一组状态 最少接收两个参数,也可以传多个
ctx.setLineDash([10,20]);

lineDashOffset设置虚线的起始偏移量

ctx.setLineDash([15, 12]);
ctx.lineDashOffset = 20;
ctx.moveTo(0, 100);
ctx.lineTo(400, 100);
ctx.stroke();
文本

我们可以在画布上绘制文字内容

ctx.font = "30px 宋体";
ctx.fillText("你好,我是宋体的文字"100,100);

textAlign设置文字对齐方式 start(默认), end, left, right, center

// 对齐基于 fillText方法的x值
ctx.font = "30px 宋体";
ctx.textAlign = "center";
ctx.fillText("你好,我是宋体的文字", 50, 50);
渐变

createLinearGradient(x0,y0,x1,y1) 线型渐变

x0 起点的x轴坐标 y0 起点的y轴坐标 x1 终点的x轴坐标 y1 终点的y轴坐标

const linear = ctx.createLinearGradient(0, 0, 200, 200);
linear.addColorStop(0, "red");
linear.addColorStop(0.3, "blue");
linear.addColorStop(0.5, "yellow");
linear.addColorStop(1, "purple");
ctx.fillStyle = linear;
ctx.fillRect(10,10,200,100);

createRadialGradient(x0,y0,r0,x1,y1,r1) 径向渐变

x0 开始圆的x轴坐标 y0 开始圆的y轴坐标 r0 开始圆的半径

x1 结束圆的x轴坐标 y1 结束圆的y轴坐标 r1 结束圆的半径

用的少

阴影

我们可以在画布中设置阴影的状态

ctx.shadowOffsetX = 20;	 // 左右偏移量
ctx.shadowOffsetY = 8;	 // 上下偏移量
ctx.shadowBlur = 4; 	 // 模糊状态
ctx.shadowColor = "red"; // 阴影颜色
ctx.font = "30px 宋体";
ctx.fillText("李银河", 50, 50);

使用图片

canvas中使用**drawImage()**来绘制图片,主要是外部的图片导入进来绘制到画布上

如果我们设置的是参数一共是两个(不包含第一个image),表示的是图片的加载位置

如果有四个参数,分别表示位置和宽高

// 创建一个image元素
const image = new Image();
// 用src设置图片的地址
image.src = "./image/green.jpg";
// 必须要在onload之后再进行绘制图片,否则不会渲染
image.onload = () => {
  // ctx.drawImage(image, 0, 0);
  ctx.drawImage(image, 250, 150,100,100);
};

注意:如果有8个参数,代表的是切片在图片上切下一块内容之后再画布中进行渲染。

前四个参数是图片中设置切片的宽度和高度,以及切片位置

后四个参数指的是切片在画布上的位置和切片宽度高度

资源管理器

我们在开发游戏的时候,有一些静态资源是需要请求回来的,否则如果直接开始,有些静态资源没有,会报错或者空白,比如我们的游戏背景图,如果没有请求回来就直接开始,页面会有空白现象

资源管理器就是当游戏需要资源全部加装完毕的时候,再开始游戏

获取对象中属性的长度
this.srcList = {
  beibei: "./image/beibei.jpg",
  green: "./image/green.jpg",
};
// 获取资源图片的总数  Object.keys 获取对象中属性的长度
const allAmount = Object.keys(this.srcList);
console.log(allAmount);  // ["beibei","green"]
      // 游戏类
      function Game() {
        this.canvas = document.querySelector("#myCanvas");
        this.ctx = this.canvas.getContext("2d");
        // 添加属性,保存需要的图片地址
        this.srcList = {
          beibei: "./image/beibei.jpg",
          green: "./image/green.jpg",
        };
        // 获取资源图片的总数  Object.keys 获取对象中属性的长度
        const allAmount = Object.keys(this.srcList).length;
        // 计数器 记录的是加载完毕的数量
        let count = 0;
        // 遍历 获取每一个路径地址
        for (k in this.srcList) {
          // 备份每一张图片的地址
          const src = this.srcList[k];
          // 创建图片
          this.srcList[k] = new Image();
          // 赋值src图片地址
          this.srcList[k].src = src;
          // 判断图片是否加载完成,如果完成了,计数,如果加载完毕的数量和总数量相同了,则说明资源加载完毕,开始游戏
          this.srcList[k].onload = () => {
            // 这里使用箭头函数捕获上一层 this
            // 增加计数器
            count++;
            this.ctx.clearRect(0, 0, 600, 400);
            this.ctx.font = "16px Arial";
            this.ctx.fillText(`图片已经加载${count}/${allAmount}`, 10, 50);
            // 判断图片是否加载完毕,如果加载完毕了,开始游戏
            if (count == allAmount)this.star();
          };
        }
      }
      Game.prototype = {
        constructor: Game,
        star: function () {
          this.ctx.drawImage(this.srcList["beibei"], 100, 150, 100, 100);
        },
      };

变形

​ canvas是可以进行变形的,但是变形的不是元素,而是ctx,ctx就是画布的渲染区域,整个画布再变形,我们需要在画布变形前,进行保存和恢复。

save()保存画布的所有状态 无参数

restore()恢复画布的所有状态 无参数

如果使用到变形,变形之前先备份,将世界和平的状态进行备份,然后再变形,变形完毕后再恢复到世界和平的样子。

移动

translate

ctx.translate(x, y);
旋转

rotate

ctx.rotate(deg);
缩放

scale

ctx.scale(0.5,0.5);

合成

合成就是我们常见的蒙版状态,本质就是如何进行压盖,如何进行显示。

我们可以通过 globalCompositeOperation来设置压盖顺序

比如我们先绘制了一个矩形再绘制了一个圆形,这时会出现圆压盖矩形的现象

ctx.fillStyle = "skyblue";
ctx.fillRect(100, 100, 100, 100);

// ctx.globalCompositeOperation = "destination-over"

ctx.fillStyle = "deeppink";
ctx.beginPath();
ctx.arc(200, 200, 60, 0, Math.PI * 2, false);
ctx.fill();

如果我们想要粉色在下面

ctx.globalCompositeOperation = "destination-over";

更多案例访问MDN
https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Compositing

刮刮乐

效果图:
在这里插入图片描述
代码如下

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>刮刮乐</title>
    <style>
      div {
        position: relative;
        width: 250px;
        height: 60px;
        line-height: 60px;
        text-align: center;
        font-size: 40px;
        border: 1px solid #000;
        user-select: none;
      }
      #myCanvas {
        position: absolute;
        top: 0;
        left: 0;
      }
    </style>
  </head>
  <body>
    <div>
      一等奖
      <canvas id="myCanvas" width="250" height="60"></canvas>
    </div>
    <script>
      const canvas = document.querySelector("#myCanvas");
      const ctx = canvas.getContext("2d");
      ctx.fillStyle = "#333";
      ctx.fillRect(0, 0, 250, 60);
      // 设置新画上的元素,实际上就是擦除之前的元素
      ctx.globalCompositeOperation = "destination-out";
      let flag = true;
      /* ==== PC端 ==== */
      // 鼠标按下
      canvas.addEventListener("mousedown", () => {
        flag = true;
        // 鼠标拖动
        canvas.addEventListener("mousemove", (e) => flag && erasure(e));
      });
      // 鼠标离开
      canvas.addEventListener("mouseup", () => (flag = false));

      /* ==== 移动端 ==== */
      // 手指触摸
      canvas.addEventListener("touchstart", () => {
        flag = true;
        // 手指滑动
        canvas.addEventListener("touchmove", (e) => flag && erasure(e));
      });
      // 手指离开
      canvas.addEventListener("touchend", () => (flag = false));

      // 擦除函数
      function erasure(e) {
        let x = e.offsetX || e.targetTouches[0].clientX;
        let y = e.offsetY || e.targetTouches[0].clientY;
        ctx.beginPath();
        ctx.arc(x, y, 5, 0, Math.PI * 2, false);
        ctx.fill();
      }
    </script>
  </body>
</html>

全景移动

效果图:
在这里插入图片描述
代码如下:

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    canvas {
      border: 1px solid #000000;
    }
  </style>
</head>
<body>
<canvas id="myCanvas"></canvas>
<canvas id="beiBei"></canvas>
<script src="./PanoramaMove.js"></script>
<script>
  let panoramaMove_1 = new PanoramaMove("#myCanvas","./image/bg.jpg");
  panoramaMove_1.render();
</script>
</body>
</html>

PanoramaMove.js文件

/** ==== 全景滚动构造函数 ==== **/
/**
 * @param {String} element DOM元素
 * @param {String} src     图片路径
 */
function PanoramaMove(element,src) {
  this.element = element;
  this.c = null;
  this.ctx = null;
  this.src = src;
  this.translateX = 0;
}

PanoramaMove.prototype = {
  constructor: PanoramaMove,
  /** ==== 初始化canvas函数 ==== **/
  /**
   * @param {Number} width  图片的宽度
   * @param {Number} height 图片的高度
   */
  canvasInit: function (width,height) {
    this.c = document.querySelector(this.element);
    this.c.width = width;
    this.c.height = height;
    this.ctx = this.c.getContext("2d");
  },
  /** ==== 渲染图片函数 ==== **/
  render: function () {
    const img = new Image();
    img.src = this.src;
    img.onload = () => {
      this.canvasInit(img.width,img.height);
      this.drawImg(img)();
    };
  },
  /** ==== 绘制图片函数 ==== **/
  /**
   * @param {HTMLElement} img DOM元素
   * @returns {function(): void} void
   */
  drawImg: function (img) {
    return () => {
      this.ctx.save();
      this.ctx.clearRect(0,0,this.c.width,this.c.height);
      this.ctx.translate(-this.translateX,0);
      this.ctx.drawImage(img,0,0);
      this.ctx.drawImage(img,this.c.width,0);
      this.translateX++;
      if(this.translateX >= this.c.width) this.translateX = 0;
      window.requestAnimationFrame(this.drawImg(img));
      this.ctx.restore();
    }
  }
}

圆环倒计时

效果图:在这里插入图片描述

代码如下:

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>圆环倒计时</title>
  <style>
    body {
      background-color: #cccccc;
    }
  </style>
</head>
<body>
<canvas id="myCanvas"></canvas>
<script src="./ClockCountDown.js"></script>
<script>
  const clock = new ClockCountDown("#myCanvas", 400, 400, 10);
  clock.render();
</script>
</body>
</html>

ClockCountDown.js文件

/**
 *  圆环倒计时 构造函数
 * @param {String} element DOM类名或ID
 * @param {Number} canvasWidth canvas宽度
 * @param {Number} canvasHeight canvas高度
 * @param {Number} allSeconds 一共要倒计时多少秒
 * @constructor
 */
function ClockCountDown(element, canvasWidth, canvasHeight, allSeconds) {
  /**
   * this.element -- DOM类名或ID
   * this.c -- canvas
   * this.ctx -- canvas.ctx
   * this.width -- canvas.width
   * this.height -- canvas.height
   * this.startAngle -- 开始的弧度
   * this.endAngle -- 结束的弧度
   * this.step -- 动画的次数
   * this.num -- 倒计时已经过去了的秒数
   * this.allSeconds -- 总秒数
   * this.timer -- 定时器容器
   */
  this.element = element;
  this.c = null;
  this.ctx = null;
  this.width = canvasWidth;
  this.height = canvasHeight;
  this.startAngle = 1.5 * Math.PI;
  this.endAngle = -0.5 * Math.PI;
  this.step = 1;
  this.num = 0;
  this.allSeconds = allSeconds;
  this.timer = null;
  /** ==== 外圆环数据 ==== */
  this.outAnnulusData = {
    lineWidth: 16,
    strokeStyle: "#ffffff",
    r: 120,
  }
  /** ==== 内圆环数据 ==== */
  this.innerAnnulusData = {
    lineWidth: 8,
    lineCap: "round",
    strokeStyle: "#6699ff",
    r: 120,
  }
  /** ==== 倒计时文字数据 ==== */
  this.countDownTextData = {
    font: "60px Arial",
    fillStyle: "#333333",
    textAlign: "center",
    textBaseline: "middle",
  }
}
/** ==== 往原型上添加方法 ==== */
ClockCountDown.prototype = {
  constructor: ClockCountDown,
  /** ==== canvas初始化 ==== */
  canvasInit() {
    this.c = document.querySelector(this.element);
    this.ctx = this.c.getContext("2d");
    this.c.width = this.width;
    this.c.height = this.height;
  },
  /** ==== 绘制外圆环 ==== */
  outerAnnulus() {
    const {lineWidth, strokeStyle, r} = this.outAnnulusData;
    this.ctx.lineWidth = lineWidth;
    this.ctx.strokeStyle = strokeStyle;
    this.ctx.arc(this.width / 2, this.height / 2, r, 0, 2 * Math.PI);
    this.ctx.stroke();
  },
  /** ==== 绘制内圆环 ==== */
  innerAnnulus() {
    const {lineWidth, lineCap, strokeStyle, r} = this.innerAnnulusData;
    this.ctx.beginPath();
    this.ctx.lineWidth = lineWidth;
    this.ctx.lineCap = lineCap;
    this.ctx.strokeStyle = strokeStyle;
    this.ctx.arc(this.width / 2, this.height / 2, r, this.startAngle, this.endAngle, true);
    this.ctx.stroke();
  },
  /** ==== 绘制倒计时文字 ==== */
  countDownText() {
    const {font, fillStyle, textAlign, textBaseline} = this.countDownTextData;
    this.ctx.beginPath();
    this.ctx.font = font;
    this.ctx.fillStyle = fillStyle;
    this.ctx.textAlign = textAlign;
    this.ctx.textBaseline = textBaseline;
    this.ctx.fillText(`${this.allSeconds - this.num}`, this.width / 2, this.height / 2);
    this.ctx.closePath();
    this.num++;
  },
  /** ==== 动画函数 ==== */
  animation() {
    /** ==== 返回箭头函数 纠正this指向 */
    return () => {
      if (this.step <= this.allSeconds) {
        this.endAngle = this.endAngle + 2 * Math.PI / this.allSeconds;
        this.ctx.clearRect(0, 0, this.c.width, this.c.height);
        this.outerAnnulus();
        this.innerAnnulus();
        this.countDownText();
        this.step++;
      } else {
        this.innerAnnulus()
        clearInterval(this.timer);
      }
    }
  },
  /** ==== 渲染函数 ==== */
  render() {
    this.canvasInit();
    this.outerAnnulus();
    this.innerAnnulus();
    this.countDownText();
    this.timer = setInterval(this.animation(), 1000);
  }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值