Shinkai005

V1

2022/04/27阅读:38主题:红绯

完整canvas学习笔记+贪吃蛇

canvas学习第一天

模板

  • 模板就是这玩意
function draw(){
      /** @type {HTMLCanvasElement} */ 
      const canvas = document.getElementById("canvas");
      if(canvas.getContext){
        const ctx = canvas.getContext("2d");
        ctx.fillStyle="rgb(200,0,0)";
        ctx.fillRect(10,10,350,300);

        ctx.fillStyle = "rgba(0,0,200,0.5)";
        ctx.fillRect(30,30,350,300);
      }
    }
<canvas id="canvas" width="800" height="600">
    你这浏览器不支持这玩意啊
 </canvas>

css

body{
  text-align: center;
  padding-top20px;
}
canvas{
  box-shadow0 0 10px #333;
  margin0 auto;
  border-radius25px;
}
image-20220419220254148
image-20220419220254148

描边矩形

// 描边矩形
        ctx.strokeStyle = "rgb(200,0,0)";
        ctx.strokeRect(10,10,350,300);

        ctx.strokeStyle = "rgba(0,0,200,0.5)";
        ctx.strokeRect(30,30,350,300);

填充矩形

// 填充矩形
        ctx.fillStyle="rgb(200,0,0)";
        ctx.fillRect(100,100,350,300);

        ctx.fillStyle = "rgba(0,0,200,0.5)";
        ctx.fillRect(250,250,350,300);

图形 (三角形)

function draw(){
      /** @type {HTMLCanvasElement} */ 
      const canvas = document.getElementById("canvas");
      if(canvas.getContext){
        const ctx = canvas.getContext("2d");
        /** 图形的基本元素就是路径
         * 
         * 1.首先,创建起始路径 beginPath();
         * 2. 使用画图命令画出路径 moveTo()
         * 3. 把路径闭合
         * 4. 通过描边或者填充来绘制.
        */

       
        /*绘制一个三角形*/
        ctx.beginPath();
        // 起始点
        ctx.moveTo(50,500);
        // 第一条线 连接两点
        ctx.lineTo(400,250);
        // 第二条线 
        ctx.lineTo(50,0);
        // 第三条线
        // ctx.lineTo(50,500);
        // 可以不使用第三条线,用closePath()闭合图形
        ctx.closePath();
        
        // 填充三角形
        // ctx.fill();
        // 描线三角形
        ctx.stroke();
      }
    }

作业 写一个图形图形

image-20220419231210798
function draw(){
      /** @type {HTMLCanvasElement} */ 
      const canvas = document.getElementById("canvas");
      if(canvas.getContext){
        const ctx = canvas.getContext("2d");
        /** 图形的基本元素就是路径
         * 
         * 1.首先,创建起始路径 beginPath();
         * 2. 使用画图命令画出路径 moveTo()
         * 3. 把路径闭合
         * 4. 通过描边或者填充来绘制.
        */

       
        /* 绘制一个矩形 */
        ctx.strokeRect(50,50,500,500)
        /* 绘制左上方三角形 */
        // 开始绘制路径
        ctx.beginPath();
        // 起始点
        ctx.moveTo(150,150);
        // 第一条线
        ctx.lineTo(400,150);
        // 第二条线
        ctx.lineTo(150,400);
        // 闭合
        ctx.closePath();
        // 绘制
        ctx.fill();
        
        /* 绘制第二个三角形 */
        ctx.beginPath();
        ctx.moveTo(450,450);
        ctx.lineTo(200,450);
        ctx.lineTo(450,200);
        ctx.closePath();
        ctx.stroke();

        /* 绘制中间十字 */
        // 第一条线
        ctx.beginPath();
        ctx.moveTo(300,275);
        ctx.lineTo(300,325);
        ctx.stroke();
        // 第二条线
        ctx.beginPath();
        ctx.moveTo(275,300);
        ctx.lineTo(325,300);
        ctx.stroke();

        
      }

画圆

function draw(){
      /** @type {HTMLCanvasElement} */ 
      const canvas = document.getElementById("canvas");
      if(canvas.getContext){
        const ctx = canvas.getContext("2d");
        // 圆的组成: 圆心(x,y) 半径 开始角度 结束角度 顺时针/逆时针
        ctx.strokeStyle ='orange';
        ctx.lineWidth = 10;
        
        // 画圆
        //ctx.arc(x,y,radius,startAngle,endAngle,anticlockwise);
        //ctx.arc(400,300,150,0,Math.PI/2,false);
        ctx.arc(400,300,150,0,Math.PI*3/2,true);
        ctx.stroke();
      
      }
    }

循环画圆

image-20220420005146512
image-20220420005146512
function draw(){
      /** @type {HTMLCanvasElement} */ 
      const canvas = document.getElementById("canvas");
      if(canvas.getContext){
        const ctx = canvas.getContext("2d");
        // 圆的组成: 圆心(x,y) 半径 开始角度 结束角度 顺时针/逆时针
        /*循环生成圆弧*/
        let x=150;
        let y=60;
        const r=50;
        let end = Math.PI;
        let anticlockWise=false;   
        ctx.lineWidth="2"
        const rule1 = [0,1];  
        const rule2 = [2,3];  
        for (let i = 0; i < 4; i++) {
          
          i!==0&&(x=150,y+=150,end=Math.PI,anticlockWise =!anticlockWise);
          for (let j = 0; j < 3; j++) {
            ctx.beginPath();
            ctx.arc(x,y,r,0,end,anticlockWise);
            rule1.includes(i)&&ctx.stroke();
            rule2.includes(i)&&ctx.fill();
            x+=250;
            end+=1/2*Math.PI;
            
          }
          
        }
      }
    }

优化一下~

includes实在是为了炫技而炫技..

function draw(){
      /** @type {HTMLCanvasElement} */ 
      const canvas = document.getElementById("canvas");
      if(canvas.getContext){
        const ctx = canvas.getContext("2d");
        // 圆的组成: 圆心(x,y) 半径 开始角度 结束角度 顺时针/逆时针
        /*循环生成圆弧*/
        let x=150;
        let y=60;
        const r=50;
        let end = Math.PI;
        let anticlockWise=false;   
        ctx.lineWidth="2"
        const rule1 = [0,1];  
        const rule2 = [2,3];  
        for (let i = 0; i < 4; i++) {
          
          i!==0&&(x=150,y+=150,end=Math.PI,anticlockWise =!anticlockWise);
          for (let j = 0; j < 3; j++) {
            ctx.beginPath();
            ctx.arc(x,y,r,0,end,anticlockWise);
            (i<2)&&ctx.stroke();
            (i>=2)&&ctx.fill();
            x+=250;
            end+=1/2*Math.PI;
            
          }
          
        }
      }
    }

和老师写的不一样,也没啥谁更优的,当然是我了!人要自信

老师在循环里声明变量,其实理论上会消耗性能一些,但是如今的V8太先进了可以忽略不计.

画笑脸

image-20220420092632178
image-20220420092632178
function draw(){
      /** @type {HTMLCanvasElement} */ 
      const canvas = document.getElementById("canvas");
      if(canvas.getContext){
        const ctx = canvas.getContext("2d");
        // 圆的组成: 圆心(x,y) 半径 开始角度 结束角度 顺时针/逆时针
        /* 外部圆 */
        ctx.beginPath();
        ctx.arc(400,300,300,0,Math.PI*2,false);
        ctx.stroke();
        /* 两个眼睛 */
        ctx.beginPath();
        ctx.arc(250,200,25,0,Math.PI*2,false);
        ctx.stroke();
        ctx.beginPath();
        ctx.arc(550,200,25,0,Math.PI*2,false);
        ctx.stroke();
        /* 嘴巴 */
        ctx.beginPath();
        ctx.arc(400,300,200,Math.PI/4,Math.PI*3/4,false);
        ctx.stroke();
      }
    }

优化:

一个图,分成几个部分画不太好~可以采用重绘起点的方式来让他们之间断开连接.

function draw(){
      /** @type {HTMLCanvasElement} */ 
      const canvas = document.getElementById("canvas");
      if(canvas.getContext){
        const ctx = canvas.getContext("2d");
        // 圆的组成: 圆心(x,y) 半径 开始角度 结束角度 顺时针/逆时针
        /* 外部圆 */
        ctx.beginPath();
        ctx.arc(400,300,300,0,Math.PI*2,false);
        /* 两个眼睛 */
        ctx.moveTo(275,200);
        ctx.arc(250,200,25,0,Math.PI*2,false);
        ctx.moveTo(575,200);
        ctx.arc(550,200,25,0,Math.PI*2,false);
        ctx.stroke();
        /* 嘴巴 */
        ctx.beginPath()
        ctx.arc(400,300,200,Math.PI/4,Math.PI*3/4,false);
        ctx.stroke();
      }
    }

嘴巴位置起点位置不好找我就重建路径了.

线性渐变linear gredient

用的不多~

function draw({
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          /**
           * 线性渐变
           * 参数1: 起点x1
           * 参数2: 起点y1
           * 参数3: 起点x2
           * 参数4: 起点y2
           *
           */

          const linearGradient = ctx.createLinearGradient(000150)
          /**
           * linearGradient.addColorStop(0,'#cc6677');
           * 参数1: 必须是0.0~1.0之间的一个数值,数值表示颜色所在的相对位置
           * 参数2: 颜色,
           */

          linearGradient.addColorStop(0'#cc6677')
          linearGradient.addColorStop(0.5'#fff')
          linearGradient.addColorStop(0.5'#c6c776')
          const linearGradient2 = ctx.createLinearGradient(050090)
          linearGradient2.addColorStop(0.5'#000')
          linearGradient2.addColorStop(1'rgba(0,0,0,0)')

          ctx.fillStyle = linearGradient
          ctx.fillRect(1010150150)
          ctx.strokeStyle = linearGradient2
          ctx.strokeRect(60,50,50,50)
        }
      }

Radial gradient 辐射渐变

function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          /**
           * 径向渐变 radial gradient
           * 参数1: x1
           * 参数2: y1
           * 参数3: r1
           * 参数4: x2
           * 参数5: y2
           * 参数6: r2
           */

          const radialGradient = ctx.createRadialGradient(170,170,10,180,180,30);
          radialGradient.addColorStop(0,'#a7d30c');
          radialGradient.addColorStop(0.9,'#019f62');
          radialGradient.addColorStop(1,'rgba(1,159,98,0)');
          const radialGradient2 = ctx.createRadialGradient(280,290,10,280,280,80
          radialGradient2.addColorStop(0,'#ff5f98');
          radialGradient2.addColorStop(0.9,'#ff0188');
          radialGradient2.addColorStop(1,'rgba(255,1,136,0)');
          //画图
          ctx.fillStyle = radialGradient;
          ctx.fillRect(0,0,800,800);
          ctx.fillStyle = radialGradient2;
          ctx.fillRect(0,0,325,325);
          
        }
      }

绘制图片

绘制图片有个问题,一定要先让图片加载出来再渲染.

我这块用了promise.

function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')

          // 用promise对象判断是否加载完毕
          const loadImage = function (src{
            
            return new Promise((resolve, reject) => {
              const img = new Image()
              img.src = src
              aim = img
              aim.onload = () => {
                resolve(aim)
              }
              aim.onerror = (e) => {
                reject(e)
              }
            })
          }
     // loadResult是promise对象
          const loadResult = loadImage('hello.png')
          // .then是成功情况,.catch是失败会报错在控制台.
          loadResult
            .then((res) => {
              const pattern = ctx.createPattern(res,"no-repeat")
              ctx.fillStyle = pattern
              ctx.fillRect(0010001000)
            })
            .catch((e) => {
              console.log('error', e)
            })
        }
      }

绘制文字阴影

function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          //设置线性渐变linear gradient
          const linerGradient = ctx.createLinearGradient(100,200,500,200); 
          linerGradient.addColorStop(0.5,'#cc6677');
          linerGradient.addColorStop(1,'#000');

          //设置阴影
          ctx.shadowColor = 'orange'
          ctx.shadowBlur = 10
          ctx.shadowOffsetX = 20
          ctx.shadowOffsetY = 20
          /**
           * font-weight
           * font-style
           * font-size
           * font-family
           */

          ctx.font = 'bold italic 160px arial'
          ctx.fillStyle = linerGradient;
          ctx.fillText('Shinkai'100200)

        }
      }

drawImage

不想写了

function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          const img = new Image()
          img.src = 'hello.png'
          img.onload = function ({
            /**
             * 1.图像
             * 2.x轴
             * 3.y轴
             * 4.宽度
             * 5.高度
             * 6.绘制x轴
             * 7.绘制y轴
             * 8.绘制的宽度
             * 9.绘制的高度
             */

            // ctx.drawImage(this,0,0); //正常
            // ctx.drawImage(this,0,0,300,200); //缩放
            ctx.drawImage(this001201001505010050//裁剪
          }
        }
      }

制作房产证

function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          const loadImage = function (src{
            return new Promise((resolve, reject) => {
              const img = new Image()
              img.src = src
              img.onload = () => {
                resolve(img)
              }
              img.onerror = (e) => {
                reject(e)
              }
            })
          }
          const loadResult = loadImage('fangchanzheng.jpg')
          loadResult
            .then((res) => {
              // const pattern = ctx.createPattern(res, 'no-repeat')
              // ctx.fillStyle = pattern
              // ctx.fillRect(0, 0, 800, 600)
              ctx.drawImage(res,0,0);
              ctx.font = '10 Arial';
              ctx.strokeStyle='rgb(131,121,118)'
              ctx.strokeText('Shinkai',178,120);
              ctx.stroke();
            })
            .catch((e) => {
              console.log(e)
            })
        }
      }

clip

在画布中呈现想要的形状带图案

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas" width="800" height="600">
      你这浏览器不支持这玩意啊
    </canvas>
    <img src="fitness.jpg" alt="">
    <script src="app.js"></script>
    <script>
      function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          
          const img = new Image();
          img.src = "fitness.jpg";
          
          img.onload = function(){
            ctx.arc(400,300,150,0,Math.PI*2)
            ctx.fill();//这句话图像不显示的时候显示的黑色
            ctx.clip();
            context.drawImage(this,0,0);
          }
        }
      }
    
</script>
  </body>
</html>

canvas学习第二天

涂鸦

function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')

          /* 涂鸦 */
          // 添加鼠标的按下event
          canvas.onmousedown = function (e{
            console.log('执行')
            const ev = e || window.event //兼容
            // xy 是 在画布上的坐标. 就是屏幕上当前的坐标-画布离左上角的坐标.
            const x = ev.clientX - canvas.offsetLeft

            const y = ev.clientY - canvas.offsetTop
            console.log(x, y)
            // 配置画笔
            ctx.strokeStyle = 'green'
            ctx.lineWidth = 10

            //
            ctx.beginPath()
            ctx.moveTo(x, y)
            // 因为不是只需要线,而是涂鸦所以还需要检测移动
            canvas.onmousemove = function (e{
              console.log('移动')
              const ev = e || window.event //兼容
              // xy 是 在画布上的坐标. 就是屏幕上当前的坐标-画布离左上角的坐标.
              const x = ev.clientX - canvas.offsetLeft
              const y = ev.clientY - canvas.offsetTop

              ctx.lineTo(x, y)
              ctx.stroke()
            }

            // 设置结束事件也就是 mouseup
            canvas.onmouseup = function (e{
              canvas.onmousemove = ''
            }
          }
        }
      }

优化要成为习惯~

励志刷完b站所有课的男人+打死不用老师源码的人

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <!-- <link rel="stylesheet" href="style.css" /> -->
    <style>
      body {
        text-align: center;
        padding-top20px;
        /* margin: 0; */
      }
      canvas {
        box-shadow0 0 10px #333;
        margin0 auto;
        border-radius25px;
        /* position: absolute; */
        /* left: 0; */
      }
    
</style>
    <script src="vue.js"></script>
  </head>
  <body>
    <div id="vue-app">
      <canvas id="canvas" width="800" height="600">
        你这浏览器不支持这玩意啊
      </canvas>
      <button @click="mountedMethod()" :value="control">
        {{control?'正在涂鸦':'已停用涂鸦'}}
      </button>
    </div>

    <!-- <script src="app.js"></script> -->
    <script>
      new Vue({
        el'#vue-app',
        data: {
          controlfalse,
        },
        created() {},
        methods: {
          draw() {
            // 让vscode可以显示canvas智能提示
            /** @type {HTMLCanvasElement} */
            const canvas = document.getElementById('canvas')
            if (canvas.getContext) {
              const ctx = canvas.getContext('2d')

              /* 涂鸦 */
              // 添加鼠标的按下event
              canvas.onmousedown = function (e{
                console.log('执行')
                const ev = e || window.event //兼容
                // xy 是 在画布上的坐标. 就是屏幕上当前的坐标-画布离左上角的坐标.
                const x = ev.clientX - canvas.offsetLeft

                const y = ev.clientY - canvas.offsetTop
                console.log(x, y)
                // 配置画笔
                ctx.strokeStyle = 'green'
                ctx.lineWidth = 10

                //
                ctx.beginPath()
                ctx.moveTo(x, y)
                // 因为不是只需要线,而是涂鸦所以还需要检测移动
                canvas.onmousemove = function (e{
                  console.log('移动')
                  const ev = e || window.event //兼容
                  // xy 是 在画布上的坐标. 就是屏幕上当前的坐标-画布离左上角的坐标.
                  const x = ev.clientX - canvas.offsetLeft
                  const y = ev.clientY - canvas.offsetTop

                  ctx.lineTo(x, y)
                  ctx.stroke()
                }

                // 设置结束事件也就是 mouseup
                canvas.onmouseup = function (e{
                  canvas.onmousemove = ''
                }
              }
            }
          },
          mountedMethod() {
            this.control = !this.control
            this.$nextTick(() => {
              this.draw()
            })
          },
        },
      })
    
</script>
  </body>
</html>



上面是蛇精病写法....希望大家不会有一天遇到这样的需求....

主要的点就在于 dom渲染后在执行需要用this.$nextTick方法.

然后还有个bug是 开了控制台 绘画位置和鼠标不在一起了. 需要检测一下是否开了控制台,然后再重新写一个计算xy方式.懒得改了

蒙版实现刮刮乐

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <img src="beautiful.jpg" alt="" />
    <canvas id="canvas" width="800" height="600">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          //蒙版
          ctx.beginPath()
          ctx.fillStyle = 'rgba(100,100,100,0.99)'
          ctx.fillRect(00240360)

          ctx.globalCompositeOperation = 'destination-out'
          ctx.lineWidth = 20
          ctx.lineCap = 'round'

          canvas.onmousedown = function (e{
            const ev = e || window.event
            const x = ev.clientX
            const y = ev.clientY
            ctx.moveTo(x, y)
            canvas.onmousemove = function (e{
              const ev = e || window.event
              const x = ev.clientX
              const y = ev.clientY
              ctx.lineTo(x, y)
              ctx.stroke()
            }
            canvas.onmouseup = function (e{
              canvas.onmousemove = ''
            }
          }
        }
      }
    
</script>
  </body>
</html>


图像组合

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas1" width="300" height="300"></canvas>
    <canvas id="canvas2" width="300" height="300"></canvas>
    <canvas id="canvas3" width="300" height="300"></canvas>
    <canvas id="canvas4" width="300" height="300"></canvas>
    <canvas id="canvas5" width="300" height="300"></canvas>
    <canvas id="canvas6" width="300" height="300"></canvas>
    <canvas id="canvas7" width="300" height="300"></canvas>
    <canvas id="canvas8" width="300" height="300">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      //创建画布,返回画笔画布
      const createCanvas = function (id{
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById(id)
        let ctx = ''
        if (canvas.getContext) {
          ctx = canvas.getContext('2d')
        }

        return { ctx, canvas }
      }

      function draw({
        // 让vscode可以显示canvas智能提示
        /**
         * 四个画布来并行比较 
         */

        //想在优化可以用循环传个数组进去.
        const { ctx: ctx1, canvas: canvas1 } = createCanvas('canvas1')
        const { ctx: ctx2, canvas: canvas2 } = createCanvas('canvas2')
        const { ctx: ctx3, canvas: canvas3 } = createCanvas('canvas3')
        const { ctx: ctx4, canvas: canvas4 } = createCanvas('canvas4')
        const { ctx: ctx5, canvas: canvas5 } = createCanvas('canvas5')
        const { ctx: ctx6, canvas: canvas6 } = createCanvas('canvas6')
        const { ctx: ctx7, canvas: canvas7 } = createCanvas('canvas7')
        const { ctx: ctx8, canvas: canvas8 } = createCanvas('canvas8')

        //操作第一个
        ctx1.beginPath()
        ctx1.globalCompositeOperation = 'source-over'

        ctx1.fillStyle = 'red'
        ctx1.fillRect(5050100100)
        
        ctx1.fillStyle = 'green'
        ctx1.arc(150150500Math.PI * 2)
        ctx1.fill()
        /**
         * 我这块用的和老师讲法不一样的
         * 我看的mdn的解释.
         * 等最后我全部解释下.
         * 并不是简单的换source和destination就是换上下.
         */

        //操作第二个
        ctx2.beginPath()
        ctx2.fillStyle = 'red'
        ctx2.fillRect(5050100100)
        ctx2.globalCompositeOperation = 'destination-over'

        ctx2.fillStyle = 'green'
        ctx2.arc(150150500Math.PI * 2)
        ctx2.fill()

        // 操作第三个
        ctx3.beginPath()
        ctx3.fillStyle = 'red'
        ctx3.fillRect(5050100100)
        ctx3.globalCompositeOperation = 'source-atop'

        ctx3.fillStyle = 'green'
        ctx3.arc(150150500Math.PI * 2)
        ctx3.fill()

        // 操作第四个
        ctx4.beginPath()
        ctx4.fillStyle = 'red'
        ctx4.fillRect(5050100100)
        ctx4.globalCompositeOperation = 'destination-atop'

        ctx4.fillStyle = 'green'
        ctx4.arc(150150500Math.PI * 2)
        ctx4.fill()

        // 操作第五个
        ctx5.beginPath()
        ctx5.fillStyle = 'red'
        ctx5.fillRect(5050100100)
        ctx5.globalCompositeOperation = 'source-in'

        ctx5.fillStyle = 'green'
        ctx5.arc(150150500Math.PI * 2)
        ctx5.fill()
        // 操作第六个
        ctx6.beginPath()
        ctx6.fillStyle = 'red'
        ctx6.fillRect(5050100100)
        ctx6.globalCompositeOperation = 'destination-in'

        ctx6.fillStyle = 'green'
        ctx6.arc(150150500Math.PI * 2)
        ctx6.fill()
        // 操作第七个
        ctx7.beginPath()
        ctx7.fillStyle = 'red'
        ctx7.fillRect(5050100100)
        ctx7.globalCompositeOperation = 'source-out'

        ctx7.fillStyle = 'green'
        ctx7.arc(150150500Math.PI * 2)
        ctx7.fill()
        // 操作第八个
        ctx8.beginPath()
        ctx8.fillStyle = 'red'
        ctx8.fillRect(5050100100)
        ctx8.globalCompositeOperation = 'destination-out'

        ctx8.fillStyle = 'green'
        ctx8.arc(150150500Math.PI * 2)
        ctx8.fill()
      }
      /**
       * 所有的解释
       * 所有的都是先生成 "红色"  -> 声明 -> "绿色"
       * 1357全是source,2468全是destination
       * 
       * 大家观察1357,全部是绿在上.  
       * 看2468 红在上. 
       * => 结论1. source开头的 颜色会是最后一个.之后的都在原图案上.
       * => 推论1  destination开头的 颜色会是第一个,之后的都在原图案下.
       * 
       * source-over  
       * source-atop
       * source-in
       * source-out
        */

    
</script>
  </body>
</html>


我已经优化了一下代码了在优化狠点.其实上面的可读性就蛮不错了.小改一下

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <!-- 别惹我惹我我就扔vue里vue-for生成了 -->
    <canvas id="canvas1" width="300" height="300"></canvas>
    <canvas id="canvas2" width="300" height="300"></canvas>
    <canvas id="canvas3" width="300" height="300"></canvas>
    <canvas id="canvas4" width="300" height="300"></canvas>
    <canvas id="canvas5" width="300" height="300"></canvas>
    <canvas id="canvas6" width="300" height="300"></canvas>
    <canvas id="canvas7" width="300" height="300"></canvas>
    <canvas id="canvas8" width="300" height="300">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      //创建画布,返回画笔画布
      const createCanvas = function (id,operation{
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById(id)
        let ctx = ''
        if (canvas.getContext) {
          ctx = canvas.getContext('2d')
        }
        ctx.beginPath()
        ctx.fillStyle = 'red'
        ctx.fillRect(5050100100)
        ctx.globalCompositeOperation = operation
        ctx.fillStyle = 'green'
        ctx.arc(150150500Math.PI * 2)
        ctx.fill()

        return { ctx, canvas }
      }

      function draw({
        // 让vscode可以显示canvas智能提示
        /**
         * 四个画布来并行比较 
         */

        //想在优化可以用循环传个数组进去.
        const { ctx: ctx1, canvas: canvas1 } = createCanvas('canvas1','source-over')
        const { ctx: ctx2, canvas: canvas2 } = createCanvas('canvas2','destination-over')
        const { ctx: ctx3, canvas: canvas3 } = createCanvas('canvas3','source-atop')
        const { ctx: ctx4, canvas: canvas4 } = createCanvas('canvas4','destination-atop')
        const { ctx: ctx5, canvas: canvas5 } = createCanvas('canvas5','source-in')
        const { ctx: ctx6, canvas: canvas6 } = createCanvas('canvas6','destination-in')
        const { ctx: ctx7, canvas: canvas7 } = createCanvas('canvas7','source-out')
        const { ctx: ctx8, canvas: canvas8 } = createCanvas('canvas8','destination-out')
    
</script>
  </body>
</html>


我还可以优化

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <!-- 别惹我惹我我就扔vue里vue-for生成了 -->
    <canvas id="canvas1" width="300" height="300"></canvas>
    <canvas id="canvas2" width="300" height="300"></canvas>
    <canvas id="canvas3" width="300" height="300"></canvas>
    <canvas id="canvas4" width="300" height="300"></canvas>
    <canvas id="canvas5" width="300" height="300"></canvas>
    <canvas id="canvas6" width="300" height="300"></canvas>
    <canvas id="canvas7" width="300" height="300"></canvas>
    <canvas id="canvas8" width="300" height="300">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      //创建画布,返回画笔画布
      const createCanvas = function (ids,operations{
        /** @type {HTMLCanvasElement} */
        if(ids.length!==operations.length)return "给爷滚,数据都给错了"
        for (let i = 0; i < ids.length; i++) {
        const canvas = document.getElementById(ids[i])
        let ctx = ''
        if (canvas.getContext) {
          ctx = canvas.getContext('2d')
        }
        ctx.beginPath()
        ctx.fillStyle = 'red'
        ctx.fillRect(5050100100)
        ctx.globalCompositeOperation = operations[i];
        ctx.fillStyle = 'green'
        ctx.arc(150150500Math.PI * 2)
        ctx.fill()
        }
      }

      function draw({
        const arr=[];
        let str=''
        for (let i = 1; i <= 8; i++) {
           str = `canvas${i}`
           arr.push(str)
        }
        console.log(arr);
        createCanvas(arr,['source-over','destination-over','source-atop','destination-atop','source-in','destination-in','source-out','destination-out'])
      }
    
</script>
  </body>
</html>


完了好烦看着div也想改了,不说了dom操作一波.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head> 
  <body onload="createContains('canvas',8),draw()">
    <div id="vue-app"></div>
    <div id="canvasBox"></div>
    <script src="app.js"></script>
    <script>
      const createCanvas = function (ids, operations{
        /** @type {HTMLCanvasElement} */
        if (ids.length !== operations.length) return '给爷滚,数据都给错了'
        for (let i = 0; i < ids.length; i++) {
          const canvas = document.getElementById(ids[i])
          let ctx = ''
          if (canvas.getContext) {
            ctx = canvas.getContext('2d')
          }
          ctx.beginPath()
          ctx.fillStyle = 'red'
          ctx.fillRect(5050100100)
          ctx.globalCompositeOperation = operations[i]
          ctx.fillStyle = 'green'
          ctx.arc(150150500Math.PI * 2)
          ctx.fill()
        }
      }

      function draw({
        const arr = []
        let str = ''
        for (let i = 1; i <= 8; i++) {
          str = `canvas${i}`
          arr.push(str)
        }
        // console.log(arr)
        createCanvas(arr, [
          'source-over',
          'destination-over',
          'source-atop',
          'destination-atop',
          'source-in',
          'destination-in',
          'source-out',
          'destination-out',
        ])
      }

      function createContains(element, number{
        console.log('执行这里')
        let html = ''
        for (let i = 1; i <= number; i++) {
          html += `<${element} id="canvas${i}" width="300" height="300"></${element}>`
        }
        document.getElementById('canvasBox').innerHTML = html
      }
    
</script>
  </body>
</html>


矩形直线运动+反弹效果

代码如下

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas" width="400" height="300">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      function draw() {
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          /**
           * canvas 动画原理
           * 1.绘制图像
           * 2.清除图像
           * 3.更新位置
           * 4.重绘
           */
          let x = 0,
            y = 0,
            width = 10,
            height = 10
          ctx.fillRect(x, y, width, height)
          // x+=20
          // y+=20
          // ctx.fillRect(x, y, width, height)


          //速度
          let speedX = 20
          let speedY = 20

          setInterval(function(){
            // 2.清除图像
            ctx.clearRect(0, 0, canvas.width, canvas.height)
            // 3.更新位置
            x += speedX
            if (x > canvas.width - width) {
              // rebound
              speedX *= -1
            } else if(x<0) {
              speedX *= -1
            }

            y += speedY
            if (y > canvas.height - height) {
              // rebound
              speedY *= -1
            } else if (y<0) {
              speedY *= -1
            }

            // 4.绘制图像
            ctx.fillRect(x, y, width, height)
          
          },60)
          //添加弹回动画

          
          }
        }
      
    </script>
  </body>
</html>

想要实现多个怎么操作? 下面代码千万别运行,会笑死你.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas" width="500" height="500">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          /**
           * canvas 动画原理
           * 1.绘制图像
           * 2.清除图像
           * 3.更新位置
           * 4.重绘
           */

          let width = 10,
            height = 10

          // 写个函数自动生成xy
          function createAxis(elements, num{
            for (let i = 0; i < num; i++) {
              //随机生成位置
              elements[i] = {
                //Math.floor(Math.random() * (max - min + 1) ) + min;
                //10-290
                xMath.floor(Math.random() * (canvas.width-width -width + 1) + width),
                yMath.floor(Math.random() * (canvas.height-width-height + 1) + height),
              }
            }
          }
          const elements = []
          createAxis(elements, 2)
          console.log('old', elements)
          //渲染最初图像
          renderEle(elements)
          //绘制图像
          function renderEle(elements{
            const colors = ['blue','yellow','green','lightgreen','lightblue''darkred']
            for (let i = 0; i < elements.length; i++) {
              ctx.fillStyle = colors[Math.floor(Math.random()*(colors.length-1+1)+1)]
              ctx.fillRect(elements[i].x, elements[i].y, width, height)
            }
          }

          //速度
          // let speedX = 10,
          //   speedX1 = 15
          // let speedY = 10,
          //   speedY1 = 15
          randomSpeed(elements,4)
          function randomSpeed(elements,max{
            for (let i = 0; i < elements.length; i++) {
              //随机生成速度
              elements[i].speedX = Math.floor((Math.random()*max)+1);
              elements[i].speedY = Math.floor((Math.random()*max)+1);
            }
          }
          // console.log(elements)
          setInterval(function ({
            // 2.清除图像
            // ctx.clearRect(0, 0, canvas.width, canvas.height)
            // 3.更新位置
            for (let i = 0; i < elements.length; i++) {
              elements[i].speedX = determineXPosition(
                elements[i].x,
                elements[i].speedX
              )
              elements[i].x += elements[i].speedX

              elements[i].speedY = determineYPosition(
                elements[i].y,
                elements[i].speedY
              )
              elements[i].y += elements[i].speedY

            }
            // console.log('new',elements)
            function determineYPosition(axis, orientation{
              if (axis > canvas.height - height) {
                // rebound
                orientation *= -1
              } else if (axis < 0) {
                orientation *= -1
              }
              return orientation
            }
            function determineXPosition(axis, orientation{
              if (axis > canvas.width - width) {
                // rebound
                orientation *= -1
              } else if (axis < 0) {
                orientation *= -1
              }
              return orientation
            }

            // 4.绘制图像
            renderEle(elements)
          }, 30)
        }
      }
    
</script>
  </body>
</html>


小bug

image-20220422093912071
image-20220422093912071

这个后面不可以写(),因为并不是立即执行的

好看点的+优化啦

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas" width="500" height="500">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          /**
           * canvas 动画原理
           * 1.绘制图像
           * 2.清除图像
           * 3.更新位置
           * 4.重绘
           */


          //矩形小球宽高不变
          let width = 10,height = 10
          //创建小球
          const elements = []
          createAxis(elements, 8)
          renderEle(elements)
          randomSpeed(elements, 4)
          // 随机生成xy
          function createAxis(elements, num{
            for (let i = 0; i < num; i++) {
              //随机生成位置
              elements[i] = {
                //Math.floor(Math.random() * (max - min + 1) ) + min;
                //10-290
                xMath.floor(
                  Math.random() * (canvas.width - width - width + 1) + width
                ),
                yMath.floor(
                  Math.random() * (canvas.height - width - height + 1) + height
                ),
              }
            }
          }
          // 随机生成速度
          function randomSpeed(elements, max{
            for (let i = 0; i < elements.length; i++) {
              elements[i].speedX = Math.floor(Math.random() * max + 1)
              elements[i].speedY = Math.floor(Math.random() * max + 1)
            }
          }
          //绘制图像
          function renderEle(elements{
            const colors = [
              'blue',
              'yellow',
              'green',
              'lightgreen',
              'lightblue',
            ]
            for (let i = 0; i < elements.length; i++) {
              ctx.fillStyle =
                colors[Math.floor(Math.random() * (colors.length - 1 + 1) + 1)]
              ctx.fillRect(elements[i].x, elements[i].y, width, height)
            }
          }
         
          function optimize({
            // 2.清除图像
            ctx.clearRect(00, canvas.width, canvas.height)
            // 3.更新位置
            for (let i = 0; i < elements.length; i++) {
              elements[i].speedX = determineXPosition(
                elements[i].x,
                elements[i].speedX
              )
              elements[i].x += elements[i].speedX

              elements[i].speedY = determineYPosition(
                elements[i].y,
                elements[i].speedY
              )
              elements[i].y += elements[i].speedY
            }
            // 判断y坐标是否超越边界
            function determineYPosition(axis, orientation{
              if (axis > canvas.height - height) {
                // rebound
                orientation *= -1
              } else if (axis < 0) {
                orientation *= -1
              }
              return orientation
            }
            // 判断x坐标是否超越边界
            function determineXPosition(axis, orientation{
              if (axis > canvas.width - width) {
                // rebound
                orientation *= -1
              } else if (axis < 0) {
                orientation *= -1
              }
              return orientation
            }

            // 4.绘制图像
            renderEle(elements)
            //递归调用
            window.requestAnimationFrame(optimize);
        }
        optimize()
      }
    }
    
</script>
  </body>
</html>


圆形小球动画

面向对象版本的

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas" width="400" height="300">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          // 圆形碰撞反弹
          function Ball(x, y, r, speedX, speedY, color{
            // 设置属性
            this.x = x
            this.y = y
            this.r = r
            this.speedX = speedX
            this.speedY = speedY
            this.color = color
            // 设置方法
            this.draw = function ({
              ctx.beginPath()
              ctx.fillStyle = this.color
              ctx.arc(this.x, this.y, this.r, 0Math.PI * 2)
              ctx.fill()
            }
            this.move = function ({
              this.x += this.speedX
              if (this.x > canvas.width - this.r) {
                this.speedX *= -1
              } else if (this.x < this.r) {
                this.speedX *= -1
              }
              this.y += this.speedY
              if (this.y > canvas.height - this.r) {
                this.speedY *= -1
              } else if (this.y < this.r) {
                this.speedY *= -1
              }
            }
          }
          const ball = new Ball(1001005022'blue')
          //1. 绘制图像
          ball.draw()
          //2. 删除图像 3. 更新图像 4. 绘制图像
          begin()
          function begin({
            ctx.clearRect(00, canvas.width, canvas.height)
            ball.draw()
            ball.move()
            window.requestAnimationFrame(begin)
          }
        }
      }
    
</script>
  </body>
</html>


水平全景滚动

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas" width="320" height="565">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          let y = 0
          function bgMove({
            ctx.save()
            ctx.clearRect(00, canvas.width, canvas.height)
            ctx.translate(0, y)
            //两幅图片的原因是俩图片收尾相接,然后向左,然后到最左边瞬间重置是看不出来的.
            ctx.drawImage(bgImage, 00)
            ctx.drawImage(bgImage, 0, -canvas.height)

            // 判断x的偏移量
            y++
            if (y >= canvas.height) {
              y = 0
            }
            ctx.restore()
            window.requestAnimationFrame(bgMove)
          }
          const bgImage = new Image()
          bgImage.src = 'background.png'
          bgImage.onload = function ({
            bgMove()
          }
        }
      }
    
</script>
  </body>
</html>


restore&save

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas" width="800" height="600">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas')
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d')
          /**
           * 看看save和restore做了什么
          */

          //第一个矩形
          ctx.beginPath()
          ctx.fillStyle = 'blue'
          ctx.save()
          ctx.fillRect(100,100,150,150);
          ctx.closePath()
          //第二个矩形
          ctx.beginPath()
          ctx.fillStyle = 'red'
          ctx.save()
          ctx.fillRect(100,400,150,150);
          ctx.closePath()
          //第三个矩形
          ctx.beginPath()
          ctx.restore()
          ctx.fillRect(400,100,150,150)
          ctx.closePath()
          //第四个矩形
          ctx.beginPath()
          ctx.restore()
          ctx.fillRect(400,400,150,150);
          ctx.closePath()
        }
      }
    
</script>
  </body>
</html>


只需要记得 save使用栈数据结构存的, 后进先出

矩形碰撞

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas" width="800" height="600">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas');
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d');
          /**
           * 用面向对象方式写
           */

          // Rectangle类
          function Rect(x, y, width, height, color, speed{
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
            this.color = color;
            this.speed = speed;
            // 绘制方法
            Rect.prototype.draw = function ({
              ctx.beginPath();
              ctx.fillStyle = this.color;
              ctx.fillRect(this.x, this.y, this.width, this.height);
              ctx.closePath();
            };
            //移动
            Rect.prototype.move = function ({
              this.x += this.speed;
              if (this.x >= canvas.width - this.width) {
                this.speed *= -1;
              } else if (this.x < 0) {
                this.speed *= -1;
              }
              // this.y += this.speed;
              // if (this.x >= canvas.height - this.height) {
              //   this.speed *= -1;
              // } else if (this.x < 0) {
              //   this.speed *= -1;
              // }
            };
          }
          const rect1 = new Rect(0100100100'red'4);
          const rect2 = new Rect(700100100100'blue'-2);
          rect1.draw();
          rect2.draw();
          function animate({
            ctx.clearRect(00, canvas.width, canvas.height);
            rect1.move();
            rect2.move();
            rect1.draw();
            rect2.draw();
            //判断是否碰撞
            if (isRectHit(rect1, rect2)) {
              rect1.speed *= -1;
              rect2.speed *= -1;
            }
            //递归调用
            window.requestAnimationFrame(animate);
          }
          function isRectHit(rect1, rect2{
            // 获取矩形的最小x和最大x
            const min1 = rect1.x;
            const min2 = rect2.x;
            const max1 = rect1.x + rect1.width;
            const max2 = rect2.x + rect2.width;
            const min3 = Math.max(min1, min2);
            const max3 = Math.min(max1, max2);
            if (min3 < max3) {
              return true;
            } else {
              return false;
            }
          }
          animate();
        }
      }
    
</script>
  </body>
</html>



最终版小球碰撞可迭代详细注释~

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="style.css" />
    <script src="vue.js"></script>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas" width="800" height="600">
      你这浏览器不支持这玩意啊
    </canvas>
    <script src="app.js"></script>
    <script>
      function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas');
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d');
          /**
           * 面向对象方式
           */

          // 小球类
          function Ball(x, y, r, color, speedX, speedY{
            this.x = x;
            this.y = y;
            this.r = r;
            this.color = color;
            this.speedX = speedX;
            this.speedY = speedY;
          }
          // 因为所有小球都有这个方法,其实move可以放成私有的,但是没更改需求就先放原型上.
          // 绘制方法
          Ball.prototype.draw = function ({
            ctx.beginPath();
            ctx.fillStyle = this.color;
            ctx.arc(this.x, this.y, this.r, 0Math.PI * 2);
            ctx.fill();
          };
          // 移动方法
          Ball.prototype.move = function ({
            this.x += this.speedX;
            if (this.x > canvas.width - this.r) {
              this.speedX *= -1;
            } else if (this.x < this.r) {
              this.speedX *= -1;
            }
            this.y += this.speedY;
            if (this.y > canvas.height - this.r) {
              this.speedY *= -1;
            } else if (this.y < this.r) {
              this.speedY *= -1;
            }
          };
          //收集实例方法
          // Ball.prototype.balls = []; //不想放这里
          // 让用户用这个方法前自己新建一个数组balls
          const balls = [];
          Ball.prototype.collect = function ({
            balls.push(this);
            console.log(balls);
          };

          const ball1 = new Ball(10010050'gold'50);
          const ball2 = new Ball(70010050'grey'-50);
          const ball3 = new Ball(50040050'lightblue'36);
          const ball4 = new Ball(30020050'lightgreen'7-7);
          const ball5 = new Ball(40010050'pink'4-2);
          //我准备要把他们新建以后就放到balls数组里,
          // 有个点要注意,是引用传值,所以ball1被修改balls里的也会改哦.
          //要利用这个js特性可以方便但是要注意

          ball1.collect();
          ball2.collect();
          ball3.collect();
          ball4.collect();
          ball5.collect();
          //收集完就可以循环创建了
          balls.forEach((item) => {
            item.draw();
          });

          // 动画 封装方法 这方法封装了个啥玩意啊...
          animate();
          function animate({
            //动画四步骤, 绘制,清除,挪位置,重绘
            ctx.clearRect(00, canvas.width, canvas.height);

            /**
             * 捋一下
             * 两个小球开始移动,一个右一个左,移动距离是speed.
             * 当快要碰到,也就是圆心距离<=两球半径 就要转向.
             * 逻辑没问题,一定是变量问题,每个都打印一遍.
             */


            // '圆心距离小于两个半径之和'Math.sqrt((x1-x2)^2 +(y1-y2)^2)
            // 碰撞检测 不用高级的查询了,就正常冒泡挨个查
            function isHit(item1, item2{
              let distance =
                Math.sqrt(
                  Math.pow(item1.x - item2.x, 2) +
                    Math.pow(item1.y - item2.y, 2)
                ) -
                (item1.r + item2.r);
              if (distance <= 0) {
                
                item1.speedX *=-1;
                item1.speedY *= -1;
                item2.speedX *=-1;
                item2.speedY *= -1;
              }
            }

            

            //移动
            balls.forEach((item) => {
              item.move();
            });

            //检测
            for (let i = 0; i < balls.length; i++) {
              for (let j = i; j < balls.length; j++) {
                isHit(balls[i],balls[j])
              }
            }
            
            //重绘
            balls.forEach((item) => {
              item.draw();
            });

            window.requestAnimationFrame(animate);
          }
        }
      }
    
</script>
  </body>
</html>


VScode-html检查插件-htmlhint

{
  //标签名必须小写
  "tagname-lowercase"true,
  //属性名必须小写
  "attr-lowercase"true,
  //属性值必须放在双引号中
  "attr-value-double-quotes"true,
  //属性值一定不可为空
  "attr-value-not-empty"false,
  //属性值一定不可重复
  "attr-no-duplication"true,
  //Doctype必须是 HTML 文档的第一行
  "doctype-first"true,
  //标签必须成对
  "tag-pair"true,
  //标签必须自封闭
  "tag-self-close"true,
  //特殊字符必须
  "spec-char-escape"true,
  //ID 属性必须唯一
  "id-unique"true,
  //src 属性一定不可为空
  "src-not-empty"true,
  //title 属性必须出现在标签中
  "title-require"false,
  //img 标签必须包含 alt 属性
  "alt-require"true,
  //Doctype 必须是 HTML5
  "doctype-html5"true,
  //ID 和 Class 的命名规则必须统一
  "id-class-value""dash",
  //不该使用样式标签
  "style-disabled"true,
  //不该使用行内样式
  "inline-style-disabled"false,
  //不该使用行内脚本
  "inline-script-disabled"false,
  //空格和制表符一定不可混合在行前
  "space-tab-mixed-disabled""space2",
  //ID 和 Class 一定不可使用广告关键词
  "id-class-ad-disabled"true,
  //href 必须是绝对路径或者相对路径
  "href-abs-or-rel"false,
  //属性值一定不可使用不安全字符
  "attr-unsafe-chars"true,
  //script 标签不该使用在头部
  "head-script-disabled"false
}



最初

{
  "tagname-lowercase"true,
  "attr-lowercase"true,
  "attr-value-double-quotes"true,
  "attr-value-not-empty"false,
  "attr-no-duplication"true,
  "doctype-first"true,
  "tag-pair"true,
  "tag-self-close"true,
  "spec-char-escape"true,
  "id-unique"true,
  "src-not-empty"true,
  "title-require"false,
  "alt-require"true,
  "doctype-html5"true,
  "id-class-value""dash",
  "style-disabled"true,
  "inline-style-disabled"true,
  "inline-script-disabled"true,
  "space-tab-mixed-disabled""space4",
  "id-class-ad-disabled"true,
  "href-abs-or-rel"false,
  "attr-unsafe-chars"true,
  "head-script-disabled"true
}

demo贪吃蛇

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue.js</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <style>
      body {
        text-align: center;
        padding-top80px;
      }
      canvas {
        box-shadow0 0 40px #333;
        background-colorrgb(135192206);
        border1px solid rgb(110188207);
      }
    
</style>
  </head>
  <body onload="draw()">
    <div id="vue-app"></div>
    <canvas id="canvas" width="800" height="600">
      你这浏览器不支持这玩意啊
    </canvas>
    <script>
      function draw({
        // 让vscode可以显示canvas智能提示
        /** @type {HTMLCanvasElement} */
        const canvas = document.getElementById('canvas');
        // 定时器
        let timer = null;
        // 得分统计
        let score = 0;
        if (canvas.getContext) {
          const ctx = canvas.getContext('2d');
          /**
           * 贪吃蛇步骤
           * 1.蛇
           * 1.1蛇头蛇身绘制
           * 2让蛇动起来
           * 2.1添加键盘事件
           * 2.2animate运动
           * 3随机投放食物
           * 3.1坐标位置
           * 3.2食物是否投放到了蛇头和蛇身上(数组去重)
           * 4吃食物
           * 4.1碰撞检测
           * 4.2将食物添加到蛇身上
           * 5.边缘检测,判断游戏是否结束
           * 5.1碰撞检测
           * 5.2GameOver
           * 优化: 1.不让蛇走快
           *        2. 蛇不能回头
           *
           */

          // 1.蛇头蛇身,用类来写,因为方便后续创建嘛

          // 创建 Rect类  所有的都是Rect食物/蛇头/蛇身
          function Rect(x, y, width, height, color{
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
            this.color = color;
          }
          // 方法 绘制rect
          Rect.prototype.draw = function ({
            ctx.beginPath();
            ctx.fillStyle = this.color;
            ctx.fillRect(this.x, this.y, this.width, this.height);
            ctx.strokeRect(this.x, this.y, this.width, this.height);
          };

          // 创建 蛇类
          function Snake({
            //蛇头
            this.head = new Rect(
              canvas.width / 2,
              canvas.height / 2 + 20,
              40,
              40,
              'gold'
            );
            //蛇身
            this.snakeBody = new Array();
            let x = this.head.x - 40;
            let y = this.head.y;
            for (let i = 0; i < 3; i++) {
              let rect = new Rect(x, y, 4040'gray');
              this.snakeBody.push(rect);
              x -= 40;
            }
          }
          // 方法 绘制蛇
          Snake.prototype.sDraw = function ({
            //绘制蛇头
            this.head.draw();
            //绘制蛇身
            for (let i = 0; i < this.snakeBody.length; i++) {
              this.snakeBody[i].draw();
            }
          };
          //初始化 移动方向
          Snake.prototype.direction = 2;
          // 蛇 移动方法
          Snake.prototype.move = function ({
            // 加头 去尾
            const rect = new Rect(
              this.head.x,
              this.head.y,
              this.head.width,
              this.head.height,
              'gray'
            );
            //方向判断
            // switch (this.direction) {
            //   case 0: {
            //     // console.log('左');
            //     this.head.x -= this.head.width;
            //     break;
            //   }
            //   case 1: {
            //     // console.log('上');
            //     this.head.y -= this.head.height;
            //     break;
            //   }
            //   case 2: {
            //     // console.log('右');
            //     this.head.x += this.head.width;
            //     break;
            //   }
            //   case 3: {
            //     // console.log('下');
            //     this.head.y += this.head.height;
            //     break;
            //   }
            // }
            calcDirection(this,this.direction)

            //加头去尾
            this.snakeBody.splice(00, rect);
            this.snakeBody.pop();
          };
          let calcDirectionObj = {
            0:self => self.head.x -=self.head.width,
            1:self => self.head.y -=self.head.height,
            2:self => self.head.x +=self.head.width,
            3:self => self.head.y +=self.head.height
          }
          function calcDirection(self,target){
            console.log(target)
            return calcDirectionObj[target](self)
          }

          // 创建 食物类
          function Food({
            this.x = Math.floor(Math.random() * (800 - 40 + 1));
            this.y = Math.floor(Math.random() * (600 - 40 + 1));
            this.width = 40;
            this.height = 40;
            this.color = 'red';
          }
          // 方法 食物绘制
          Food.prototype.draw = function ({
            ctx.beginPath();
            ctx.fillStyle = this.color;
            ctx.fillRect(this.x, this.y, this.width, this.height);
            ctx.strokeRect(this.x, this.y, this.width, this.height);
          };
          // 判断 食物是否被吃
          Food.prototype.isEat = function ({
            // head
            let distanceX = Math.abs(food.x - snake.head.x);
            let distanceY = Math.abs(food.y - snake.head.y);
            if (distanceX > 40 || distanceY > 40) {
              // console.log('没吃到');
            } else {
              console.log(`bingo吃到了,`);
              score++;
              food = null;
              snake.snakeBody.push(
                new Rect(
                  snake.snakeBody[snake.snakeBody.length - 1].x - 40,
                  snake.snakeBody[snake.snakeBody.length - 1].y,
                  40,
                  40,
                  'gray'
                )
              );
            }
          };

          // main
          // 创建 蛇
          const snake = new Snake();
          // 绘制 蛇
          snake.sDraw();
          // 初始化 食物
          let food = null;
          // 预处理 食物(是否被生成在蛇身/头)
          food = preDraw();
          // 绘制 食物
          food.draw();
          // 开始动画
          timer = setInterval(animate, 150);
          // 动画 函数
          function animate({
            //清除
            ctx.clearRect(00, canvas.width, canvas.height);
            //移动
            snake.move();
            //每次移动都要判断是否游戏结束,但是要渲染出来蛇碰到的样子
            //移动后判断是否被吃
            food.isEat();
            // 被吃后会置空food,没被吃还会打印之前的food
            preDraw();
            // 绘制
            food.draw();
            snake.sDraw();
            if (isGameOver()) {
              ctx.clearRect(00, canvas.width, canvas.height);
              clearInterval(timer);
              timer = null;
              alert(`菜狗,贪吃蛇都玩不明白
              得分${score}`
);
              //刷新页面
              location.reload();
            }
          }
          // 预处理 函数
          function preDraw({
            if (food === null) {
              //吃掉以后置空
              // console.log('新建了food',score);
              console.log('目前得分',score);
              food = new Food();
            }

            if (isHit(food, snake.head) || isHit(food, snake.snakeBody)) {
              food = null;
              return (food = preDraw());
            } else {
              return food;
            }
          }

          // 绘制食物之前要判断新生成的Food的坐标行不行.1.头有没有碰到. 2.身体有没有碰到
          // 因为判断的内容不一样,所以传参吧.
          // 碰撞判断 函数
          function isHit(food, aim{
            // 这个函数不好写哦~我来梳理逻辑
            // 1. 要判断是否碰到蛇头, 所以传参食物和蛇头比较就好
            // 2. 要判断是否碰到蛇身, 蛇身是多个可以函数内循环也可以外面循环这个函数.
            // 3. 如果没有碰到正常渲染即可. 既然每次new了都要判断不如扔一个函数里?
            // 4. 如果碰到,需要重新new. 刚好符合3 扔一个函数开搞
            // ps. 因为原型上的方法是访问不到new的x和y的.所以必须写在外面~反正尽量考虑少写在全局,但是没办法也没办法啊~
            let result = true//装结果
            //每次都要判断食物和整个蛇一起传没毛病
            // 判断aim类型
            if (Array.isArray(aim)) {
              // snakeBody

              result = aim.some((item) => {
                let distanceX = Math.abs(food.x - item.x);
                let distanceY = Math.abs(food.y - item.y);
                if (distanceX > 40 || distanceY > 40) {
                  return false;
                } else {
                  return true;
                }
              });
              return result;
            } else {
              // head
              let distanceX = Math.abs(food.x - aim.x);
              let distanceY = Math.abs(food.y - aim.y);
              if (distanceX > 40 || distanceY > 40) {
                result = false;
                return result;
              } else {
                result = true;
                return result;
              }
            }

            return result;
          }

          //游戏结束 1.自己碰到自己 2.碰到边缘
          // 游戏结束判断 函数
          function isGameOver({
            //撞四周简单先写撞四周
            if (
              snake.head.x >= canvas.width ||
              snake.head.x < 0 ||
              snake.head.y >= canvas.height ||
              snake.head.y < 0
            ) {
              return true;
            }
            // 撞自己也简单
            return snake.snakeBody.some((item, i) => {
              if (i === 0) {
                return false;
              }
              let distanceX = Math.abs(snake.head.x - item.x);
              let distanceY = Math.abs(snake.head.y - item.y);
              if (distanceX >= 40 || distanceY >= 40) {
                return false;
              } else {
                alert(`你是真得儿,自己撞自己呗
                得分${score}`
);
                return true;
              }
            });
          }

          //按键检测 事件
          document.onkeydown = function (e{
            var event = e || window.event;
            switch (event.keyCode) {
              case 37: {
                snake.preDirection = snake.direction; //为了不让蛇撞自己
                if(snake.direction == 0){break}
                else if (snake.preDirection !== 2) {
                  snake.direction = 0;
                }
                
                snake.move();
                snake.sDraw();
                break;
              }
              case 38: {
                snake.preDirection = snake.direction;

                if(snake.direction == 1){break}
                else if (snake.preDirection !== 3) {
                  snake.direction = 1;
                }
                

                snake.move();
                snake.sDraw();
                break;
              }
              case 39: {
                snake.preDirection = snake.direction;

                if(snake.direction == 2){break}
                else if (snake.preDirection !== 0) {
                  snake.direction = 2;
                }
                snake.move();
                snake.sDraw();
                break;
              }
              case 40: {
                snake.preDirection = snake.direction;

                if(snake.direction == 3){break}
                else if (snake.preDirection !== 1) {
                  snake.direction = 3;
                }
                snake.move();
                snake.sDraw();
                break;
              }
            }
          };
        }
      }
    
</script>
  </body>
</html>



分类:

前端

标签:

JavaScript

作者介绍

Shinkai005
V1

公众号:深海笔记Shinkai