面向对象的canvas画图顺序
项目简介
全部项目分为两大部份
- 场景
场景担任canvas掌握,事宜监听,动画处置惩罚 - 精灵
精灵则指的是每一种能够绘制的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)
}
}
- 属性:
options中存储了一切的画图属性
- fillStyle:设置或返回用于添补绘画的色彩、渐变或形式
- strokeStyle:设置或返回用于笔触的色彩、渐变或形式
- lineWidth:设置或返回当前的线条宽度
- 运用的都是getContext(“2d”)对象的原生属性,此处只列出了这三种属性,须要的话还能够继续扩大。
- 有须要能够继续扩大
- 要领:
- 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场景的完成
- 属性引见
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
/* .... */
}
}
- 事宜逻辑
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) {
// 什么都不做
}
}
})
}
}
- 要领引见
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
}
}