JS实战系列之Canvas如何用面向对象的方式实现一个七巧板?

435 阅读6分钟

前面我们学了js基础知识,只能停留在基本使用阶段,必须实战才能更好的掌握,本文涉及的知识点如果有不会的可以参考三万字js基础总结 或者 看到不会的api可以搜关键词 + mdn

  • 需求1: es6面向对象的方式实现一个七巧板
  • 需求2: 颜色可以随机改变而不是写死
  • 需求3: 颜色可以随机改变的同时且不重复

canvas基础知识点介绍

Canvas Api:提供js和HTML的<canvas>元素来绘制图形的方式,可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面

如何创建?

可以通过js方式 或者通过HTML的canvas标签来创建

  • js方式
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
 
  • HTML方式
<canvas id="canvas"
    width="1024"
    height="1024"
    style="border:1px solid #aaa;
            display:block;margin:0 auto"
>
浏览器不支持canvas 标签内部判断是否支持 不支持就会显示这段文字
支持的化不会显示这里面的文字
</canvas>

canvas对象上的属性和方法

  • canvas.width
  • canvas.height
  • canvas.getContext('2d')

canvas画一条线

画线: 起点是哪里,然后连线,最后填充

let ctx = document.createElement('canvas').getContext('2d')

//draw a line 
ctx.moveTo(0,0)//左上角
ctx.lineTo(500,500) //画线
ctx.stroke()//填充

给线添加样式:

//draw a line with style
ctx.moveTo(0,0)//左上角
ctx.lineTo(500,500) //画线

//修改样式
ctx.lineWidth = 5
ctx.strokeStyle = "#005588"

ctx.stroke()//画线条

画多边形:只要知道顶点的坐标,调用lineTo 再画线 stoke即可

//画一个三角形
ctx.moveTo(100,100)
ctx.lineTo(500,500) //链接
ctx.lineTo(100,500)
ctx.lineTo(100,100)
//修改样式
ctx.lineWidth = 5
ctx.strokeStyle = "#005588"

ctx.stroke()//画线条

给三角形填充颜色并加上外边框

//给三角形填充
ctx.moveTo(100,100)
ctx.lineTo(500,500) //链接
ctx.lineTo(100,500)
ctx.lineTo(100,100)


ctx.fillStyle = "rgb(2,100,30)"//设置填充颜色
ctx.fill()//填充

//再给边框 但是先后顺序有影响
ctx.lineWidth = 5
ctx.strokeStyle = "red"
ctx.stroke()

绘制一个三角形 + 一条新的线

//绘制多个图形 三角形+ 另外一条线 
ctx.moveTo(100,100)
ctx.lineTo(500,500) //链接
ctx.lineTo(100,500)
ctx.lineTo(100,100)

ctx.lineWidth = 5
ctx.strokeStyle = "red"
ctx.stroke()

ctx.moveTo(200,100)
ctx.lineTo(700,600)
// ctx.strokeStyle = 'black'

ctx.stroke()
//canvas基于状态绘制的 后面的同名属性会覆盖前面的

如何在上面的基础上 让三角形颜色和新的线的颜色不同? ctx.beginPath ctx.closePath

 //绘制多个图形 三角形+ 另外一条线 颜色不同
ctx.beginPath() 
ctx.moveTo(100,100)
ctx.lineTo(500,500) //链接
ctx.lineTo(100,500)
ctx.lineTo(100,100)
ctx.closePath()

ctx.lineWidth = 5
ctx.strokeStyle = "red"
ctx.stroke()


ctx.beginPath() 
ctx.moveTo(200,100)
ctx.lineTo(700,600)
ctx.closePath()

ctx.strokeStyle = 'black'
ctx.stroke()
  
                

小结

draw 定义一个路径

  • ctx.moveTo(x,y)
  • ctx.lineTo(x,y)

多个路径分开处理用:

  • ctx.beginPath()
  • ctx.closePath()

定义线条的宽度: ctx.fillStyle = '#ccc' 和颜色: ctx.strokeStyle = 'red' 填充的颜色: ctx.fillStyle = 'rab(3,100,200)'

绘制线条

  • ctx.stroke()

绘制一个填充的颜色块儿

  • ctx.fill

实现需求1

  • 实现一个需求的过程大概是:

      1. 需求分析,确定数据结构
      1. 确定面向过程还是面向对象?这里要求面向对象,那就面向对象
      1. 数据结构的处理(循环,赋值啥的)
      1. 事件的处理
      1. canvas基本知识的熟悉,上面已经介绍过了
      1. 基于canvas将数据和视图结合展示在页面上
  • 代码实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <canvas id="canvas"
        width="1024"
        height="1024"
        style="border:1px solid #aaa;
               display:block;margin:0 auto"
    >
    浏览器不支持canvas 请更换浏览器再试
    </canvas>

    <script>
        class Tangram{
            constructor(width =800){
                this.canvas = document.getElementById('canvas')
                this.ctx = this.canvas.getContext('2d')
                if(!this.ctx){
                    throw new Error('canvas不支持')
                }
                this.datas =  [
                    {
                        p : [ {x:0,y:0}, {x:800,y:0}, {x:400, y:400}],
                        color: "#caff67"
                    },
                    {
                        p : [ {x:0,y:0}, {x:400,y:400}, {x:0, y:800}],
                        color: "#67becf"
                    },
                    {
                        p : [ {x:800,y:0}, {x:800,y:400}, {x:600, y:600}, {x:600, y:200}],
                        color: "#ef3d61"
                    },
                    {
                        p : [ {x:600,y:200}, {x:600,y:600}, {x:400, y:400}],
                        color: "#f9f51a"
                    },
                    {
                        p : [ {x:400,y:400}, {x:600,y:600}, {x:400, y:800}, {x:200, y:600}],
                        color: "#a594c0"
                    },
                    {
                        p : [ {x:200,y:600}, {x:400,y:800}, {x:0, y:800}],
                        color: "#fa8ecc"
                    },
                    {
                        p : [ {x:800,y:400}, {x:800,y:800}, {x:400, y:800}],
                        color: "#f6ca29"
                    }
                ]
                this.WIDTH = this.HEIGHT = width

                window.onload = ()=>{
                    this.canvas.width = this.WIDTH
                    this.canvas.height =  this.HEIGHT

                    this.init()
                }
            }
            init(){
                this.datas.forEach( data => this.draw(data ,this.ctx))
            }

            draw(data ,ctx){
                ctx.beginPath()
                ctx.moveTo(data.p[0].x, data.p[0].y)
                for(let i=1; i<data.p.length; i++){
                    ctx.lineTo(data.p[i].x, data.p[i].y)
                }
                ctx.closePath()

                ctx.fillStyle = data.color
                ctx.fill()
            }

        }

        new Tangram()

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

效果:

实现需求2 用类的继承

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <canvas id="canvas"
        width="1024"
        height="1024"
        style="border:1px solid #aaa;
               display:block;margin:0 auto"
    >
    浏览器不支持canvas 请更换浏览器再试
    </canvas>

    <script>
        class Tangram{
            constructor(width =800){
                this.canvas = document.getElementById('canvas')
                this.ctx = this.canvas.getContext('2d')
                if(!this.ctx){
                    throw new Error('canvas不支持')
                }
                this.datas =  [
                    {
                        p : [ {x:0,y:0}, {x:800,y:0}, {x:400, y:400}],
                        color: "#caff67"
                    },
                    {
                        p : [ {x:0,y:0}, {x:400,y:400}, {x:0, y:800}],
                        color: "#67becf"
                    },
                    {
                        p : [ {x:800,y:0}, {x:800,y:400}, {x:600, y:600}, {x:600, y:200}],
                        color: "#ef3d61"
                    },
                    {
                        p : [ {x:600,y:200}, {x:600,y:600}, {x:400, y:400}],
                        color: "#f9f51a"
                    },
                    {
                        p : [ {x:400,y:400}, {x:600,y:600}, {x:400, y:800}, {x:200, y:600}],
                        color: "#a594c0"
                    },
                    {
                        p : [ {x:200,y:600}, {x:400,y:800}, {x:0, y:800}],
                        color: "#fa8ecc"
                    },
                    {
                        p : [ {x:800,y:400}, {x:800,y:800}, {x:400, y:800}],
                        color: "#f6ca29"
                    }
                ]
                this.WIDTH = this.HEIGHT = width

                window.onload = ()=>{
                    this.canvas.width = this.WIDTH
                    this.canvas.height =  this.HEIGHT

                    this.init()
                }
            }
            init(){
                this.datas.forEach( data => this.draw(data ,this.ctx))
            }

            draw(data ,ctx){
                ctx.beginPath()
                ctx.moveTo(data.p[0].x, data.p[0].y)
                for(let i=1; i<data.p.length; i++){
                    ctx.lineTo(data.p[i].x, data.p[i].y)
                }
                ctx.closePath()

                ctx.fillStyle = data.color
                ctx.fill()
            }

        }

        // new Tangram()







        // add Random color 

        class RandTangram extends Tangram{
            constructor(){
                super()
                this.colors = ["#caff67","67becf","#ef3d61","#f9f51a","#a594c0","#fa8ecc","#f6ca29"]
                this.datas = this.makeNewDatas()
            }

            makeNewDatas(datas){
                return this.datas.map( data=>{
                    data.color = this.randColor()
                    return data
                })
            }
            randColor(){
                let index = Math.floor(Math.random() * this.colors.length )
                return  this.colors[index]
            }
        }
        new RandTangram()



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

效果:

实现需求3 继承第二个需求的类,稍微修改即可,可以在评论区留言你的实现!!!

等你实现!!!

参考资料:

  • canvas
  • 波波老师的慕课网的课,他是面向过程实现的,我这里用es6面向对象实现的