可扩大面向对象的canvas绘图顺序

面向对象的canvas画图顺序

项目简介

全部项目分为两大部份

  1. 场景
    场景担任canvas掌握,事宜监听,动画处置惩罚
  2. 精灵
    精灵则指的是每一种能够绘制的canvas元素

Demo演示地点
Demo为最新代码

项目特性

可扩展性强

sprite精灵完成

父类

class Element {
  constructor(options = {
    fillStyle: 'rgba(0,0,0,0)',
    lineWidth: 1,
    strokeStyle: 'rgba(0,0,0,255)'
  }) {
    this.options = options
  }
  setStyle(options){
    this.options =  Object.assign(this.options. options)
  }
}
  1. 属性:
  • options中存储了一切的画图属性

    • fillStyle:设置或返回用于添补绘画的色彩、渐变或形式
    • strokeStyle:设置或返回用于笔触的色彩、渐变或形式
    • lineWidth:设置或返回当前的线条宽度
    • 运用的都是getContext(“2d”)对象的原生属性,此处只列出了这三种属性,须要的话还能够继续扩大。
  • 有须要能够继续扩大
  1. 要领:
  • setStyle要领用于从新设置当前精灵的属性
  • 有须要能够继续扩大

一切的精灵都继续Element类。

子类

子类就是每一种精灵元素的详细完成,这里我们引见一遍Circle元素的完成

class Circle extends Element {
  // 定位点的坐标(这块就是圆心),半径,设置对象
  constructor(x, y, r = 0, options) {
    // 挪用父类的组织函数
    super(options)
    this.x = x
    this.y = y
    this.r = r
  }
  // 转变元素大小
  resize(x, y) {
    this.r = Math.sqrt((this.x - x) ** 2 + (this.y - y) ** 2)
  }
  // 挪动元素到新位置,吸收两个参数,新的元素位置
  moveTo(x, y) {
    this.x = x
    this.y = y
  }
  // 推断点是不是在元素中,吸收两个参数,点的坐标
  choose(x, y) {
    return ((x - this.x) ** 2 + (y - this.y) ** 2) < (this.r ** 2)
  }
  // 偏移,盘算点和元素定位点的相对偏移量(ofsetX, offsetY)
  getOffset(x, y) {
    return {
      x: x - this.x,
      y: y - this.y
    }
  }
  // 绘制元素完成,吸收一个ctx对象,将当前元素绘制到指定画布上
  draw(ctx) {
    // 取到绘制所需属性
    let {
      fillStyle,
      strokeStyle,
      lineWidth
    } = this.options
    // 最先绘制beginPath() 要领最先一条途径,或重置当前的途径
    ctx.beginPath()
    // 设置属性
    ctx.fillStyle = fillStyle
    ctx.strokeStyle = strokeStyle
    ctx.lineWidth = lineWidth
    // 画圆
    ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI)
    // 添补色彩
    ctx.stroke()
    ctx.fill()
    // 绘制完成
  }
  // 考证函数,推断当前元素是不是满足指定前提,此处用来磨练是不是将元素添加到场景中。
  validate() {
    return this.r >= 3
  }
}

arc() 要领建立弧/曲线(用于建立圆或部份圆)

  • x 圆的中间的 x 坐标。
  • y 圆的中间的 y 坐标。
  • r 圆的半径。
  • sAngle 肇端角,以弧度计。(弧的圆形的三点钟位置是 0 度)。
  • eAngle 完毕角,以弧度计。
  • counterclockwise 可选。划定应当逆时针照样顺时针画图。False = 顺时针,true = 逆时针。

注意事项:

  • 组织函数的形参只要两个是必需的,就是定位点的坐标。
  • 别的的形参都必需有默认值。

一切要领的挪用机遇

  • 我们在画布上绘制元素的时刻回挪用resize要领。
  • 挪动元素的时刻挪用moveTo要领。
  • choose会在鼠标按下时挪用,推断当前元素是不是被选中。
  • getOffset选中元素时挪用,推断选中位置。
  • draw绘制函数,绘制元素到场景上时挪用。

scene场景的完成

  1. 属性引见
class Sence {
  constructor(id, options = {
    width: 600,
    height: 400
  }) {
    // 画布属性
    this.canvas = document.querySelector('#' + id)
    this.canvas.width = options.width
    this.canvas.height = options.height
    this.width = options.width
    this.height = options.height
    // 画图的对象
    this.ctx = this.canvas.getContext('2d')
    // 离屏canvas
    this.outCanvas = document.createElement('canvas')
    this.outCanvas.width = this.width
    this.outCanvas.height = this.height
    this.outCtx = this.outCanvas.getContext('2d')
    // 画布状况
    this.stateList = {
      drawing: 'drawing',
      moving: 'moving'
    }
    this.state = this.stateList.drawing
    // 鼠标状况
    this.mouseState = {
    // 纪录鼠标按下时的偏移量
      offsetX: 0,
      offsetY: 0,
      down: false, //纪录鼠标当前状况是不是按下
      target: null //当前操纵的目的元素
    }
    // 当前选中的精灵组织器
    this.currentSpriteConstructor = null
    // 存储精灵
    let sprites = []
    this.sprites = sprites
    /* .... */
  }
}
  1. 事宜逻辑
class Sence {
  constructor(id, options = {
    width: 600,
    height: 400
  }) {
  /* ... */
  // 监听事宜
    this.canvas.addEventListener('contextmenu', (e) => {
      console.log(e)
    })
    // 鼠标按下时的处置惩罚逻辑
    this.canvas.addEventListener('mousedown', (e) => {
    // 只要左键按下时才会处置惩罚鼠标事宜
      if (e.button === 0) {
      // 鼠标的位置
        let x = e.offsetX
        let y = e.offsetY
        // 纪录鼠标是不是按下
        this.mouseState.down = true
        // 建立一个暂时target
        // 纪录目的元素
        let target = null
        if (this.state === this.stateList.drawing) {
        // 推断当前有无精灵组织器,有的话就组织一个对应的精灵元素
          if (this.currentSpriteConstructor) {
            target = new this.currentSpriteConstructor(x, y)
          }
        } else if (this.state === this.stateList.moving) {
          let sprites = this.sprites
          // 遍历一切的精灵,挪用他们的choose要领,推断有无被选中
          for (let i = sprites.length - 1; i >= 0; i--) {
            if (sprites[i].choose(x, y)) {
              target = sprites[i]
              break;
            }
          }
          
          // 假如选中的话就挪用target的getOffset要领,猎取偏移量
          if (target) {
            let offset = target.getOffset(x, y)
            this.mouseState.offsetX = offset.x
            this.mouseState.offsetY = offset.y
          }
        }
        // 存储当前目的元素
        this.mouseState.target = target
        // 在离屏canvas保留除目的元素外的一切元素
        let ctx = this.outCtx
        // 清空离屏canvas
        ctx.clearRect(0, 0, this.width, this.height)
        // 将目的元素外的一切的元素绘制到离屏canvas中
        this.sprites.forEach(item => {
          if (item !== target) {
            item.draw(ctx)
          }
        })
        if(target){
            // 最先动画
            this.anmite()
        }
      }
    })
    this.canvas.addEventListener('mousemove', (e) => {
    //  假如鼠标按下且有目的元素,才实行下面的代码
      if (this.mouseState.down && this.mouseState.target) {
        let x = e.offsetX
        let y = e.offsetY
        if (this.state === this.stateList.drawing) {
        // 挪用当前target的resize要领,转变大小
          this.mouseState.target.resize(x, y)
        } else if (this.state === this.stateList.moving) {
        // 取到存储的偏移量
          let {
            offsetX, offsetY
          } = this.mouseState
          // 挪用moveTo要领将target挪动到新的位置
          this.mouseState.target.moveTo(x - offsetX, y - offsetY)
        }
      }
    })
    document.body.addEventListener('mouseup', (e) => {
      if (this.mouseState.down) {
      // 将鼠标按下状况纪录为false
        this.mouseState.down = false
        if (this.state === this.stateList.drawing) {
        // 挪用target的validate要领。推断他要不要被加到场景去呢
          if (this.mouseState.target.validate()) {
            this.sprites.push(this.mouseState.target)
          }
        } else if (this.state === this.stateList.moving) {
          // 什么都不做
        }
      }
    })
  }
}
  1. 要领引见
class Sence {
// 动画
  anmite() {
    requestAnimationFrame(() => {
      // 消灭画布
      this.clear()
      // 将离屏canvas绘制到当前canvas上
      this.paint(this.outCanvas)
      // 绘制target
      this.mouseState.target.draw(this.ctx)
      // 鼠标是按下状况就继续实行下一帧动画
      if (this.mouseState.down) {
        this.anmite()
      }
    })
  }
  // 能够将手动的建立的精灵添加到画布中
  append(sprite) {
    this.sprites.push(sprite)
    sprite.draw(this.ctx)
  }
  // 依据ID值,从场景中删除对应元素
  remove(id) {
    this.sprites.splice(id, 1)
  }
  // clearRect消灭指定地区的画布内容
  clear() {
    this.ctx.clearRect(0, 0, this.width, this.height)
  }
  // 重绘全部画布的内容
  reset() {
    this.clear()
    this.sprites.forEach(element => {
      element.draw(this.ctx)
    })
  }
  // 将离屏canvas绘制到页面的canvas画布上
  paint(canvas, x = 0, y = 0) {
    this.ctx.drawImage(canvas, x, y, this.width, this.height)
  }
  // 设置当前选中的精灵组织器
  setCurrentSprite(Element) {
    this.currentSpriteConstructor = Element
  }
}

Demo演示地点

    原文作者:bello
    原文地址: https://segmentfault.com/a/1190000015832503
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞