前言
各种插件/库和一些常规的业务代码,最大的区别就在于编程的思路与方法。
比如我们现在想写一段业务代码,使用js实现一个矩形,那很简单,几行代码就可以了
const canvas = document.getElementById('canvas')
const mode = canvas.getContext('2d')
mode.rect(200,200,200,200)
mode.fillStyle = "red"
mode.fill()
因为功能非常简单,我们就按照功能的思路很快实现
但是,如果我们需要去实现一个插件库,你这么写代码,可就不行了。
别人要使用你的插件,肯定是不需要从头看你的源码,你需要将自己封装好的api暴露给用户。
现在用户希望迅速创建一个矩形,他肯定不会像原生js一样去写
理想状态应该是这样子的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义封装canvas插件</title>
</head>
<body>
<canvas id="mycanvas"></canvas>
<script src="../myCanvasPlugins.js"></script>
<script>
const myCanvas = new CanvasPlugin().instance('#mycanvas',{width:800,height:600})
let canvasDataList = [
{
type:'rect',
nodeName:'rect1',
x:100,
y:100,
width:100,
height:100,
styleType:'fill',
strokeStyle:null,
fillStyle:'red',
event:{
click:()=>{
console.log('点击了rect1元素')
}
}
},
{
type:'arc',
nodeName:'arc1',
x:300,
y:300,
radius:100,
start:0,
end:360,
direction:true,
styleType:'stroke',
strokeStyle:'blue',
fillStyle:null,
event:{
click:()=>{
console.log('点击了arc1元素')
}
}
}
]
myCanvas.draw(canvasDataList)
</script>
</body>
</html>
所以通过分析,我们的myCanvasPlugin是需要完成一个公共的方法可以供用户使用
所以现在我们需要去实现一个js的插件,他可以让用户直接简单的去使用
思路
1.创建实例,使用的是new,所以这里我们最外层需要暴露的肯定是一个构造函数或者类
2.new之后的实例对象调用instance方法需要用户传入一个id号和元素的宽高,所以我们肯定需要在内部对dom进行一个封装
3.我们需要设置固定的属性配置,可以让用户去根据配置项,配置生成理想的数据
4.需要在实例上设置draw方法,将用户配置好的数据传入,完成内部的渲染
思路捋出来的,后续的函数设计就很明白了
而且为了批量化的操作,我们需要大量使用面向对象编程思想
实现
代码如下
/** 生成canvas的类 */
class CanvasPlugin{
constructor(){
this.instance = (id,nodeInfo)=>{
const node = document.querySelector(id)
node.setAttribute('width',nodeInfo.width)
node.setAttribute('height',nodeInfo.height)
node.draw = this.draw
return node
}
}
draw(list){
let useDataList= []
list.forEach(item=>{
let node = null
switch(item.type){
case 'rect':
node = new Rect(item.nodeName,item.x,item.y,item.width,item.height,item.styleType,item.strokeStyle,item.fillStyle,item.event)
break
case 'arc':
node = new Arc(item.nodeName,item.x,item.y,item.radius,item.start,item.end,item.direction,item.styleType,item.strokeStyle,item.fillStyle,item.event)
}
useDataList.push(node)
})
console.log(this,'???this---')
useDataList.forEach(item=>{
item.drawFun(this)
})
}
}
/** 生成矩形的类 */
class Rect{
constructor(nodeName,x,y,width,height,styleType,strokeStyle,fillStyle,event) {
this.type = 'rect'
this.nodeName = nodeName
this.x = x
this.y = y
this.width = width
this.height = height
this.styleType = styleType
this.strokeStyle = strokeStyle
this.fillStyle = fillStyle
this.event = event
}
drawFun(canvasNode){
const ctx = canvasNode.getContext('2d')
let eventKeyList = Object.keys(this.event)
let fn = (ctx,canvasPosition,$event,event)=>{
ctx.beginPath()
ctx[this.type](this.x,this.y,this.width,this.height)
ctx[`${this.styleType}Style`] = this[[`${this.styleType}Style`]]
ctx[`${this.styleType}`]()
ctx.closePath()
if(canvasPosition){
const isBeTrigger = ctx.isPointInPath(canvasPosition.x, canvasPosition.y)
if (isBeTrigger === true) {
event()
}
}
}
fn(ctx)
commonEventHandler(ctx,eventKeyList,canvasNode,fn,this.event)
}
}
/** 生成圆形的类*/
class Arc{
constructor(nodeName,x,y,radius,start,end,direction,styleType,strokeStyle,fillStyle,event){
this.type = 'arc'
this.nodeName = nodeName
this.x = x
this.y = y
this.radius = radius
this.start = start
this.end = end
this.direction = direction
this.styleType = styleType
this.strokeStyle = strokeStyle
this.fillStyle = fillStyle
this.event = event
}
drawFun(canvasNode){
const ctx = canvasNode.getContext('2d')
let eventKeyList = Object.keys(this.event)
let fn = (ctx,canvasPosition,$event,event)=>{
ctx.beginPath()
ctx[this.type](this.x,this.y,this.radius,[Math.PI/180]*this.start,[Math.PI/180]*this.end,this.direction)
ctx[`${this.styleType}Style`] = this[[`${this.styleType}Style`]]
ctx[`${this.styleType}`]()
ctx.closePath()
if(canvasPosition){
const isBeTrigger = ctx.isPointInPath(canvasPosition.x, canvasPosition.y)
if (isBeTrigger === true) {
event()
}
}
}
fn(ctx)
commonEventHandler(ctx,eventKeyList,canvasNode,fn,this.event)
}
}
/**
* @function 公共方法
* @description 给画布添加dom事件监听,并且会绘制该节点。canvas中元素的事件绑定和触发不同于常规的dom
* @author 王惊涛
* @param ctx 可操作做的画布实例
* @param eventKeyList 事件属性列表:例如['click','mouseover']
* @param canvasNode canvas的dom元素,这里主要用于判定canvas节点的具体位置
* @param fn 当前节点的绘制函数,定义在类中
* @param event 用户根据当前目标自定义的事件
*/
function commonEventHandler(ctx,eventKeyList,canvasNode,fn,event){
eventKeyList.forEach(item=>{
canvasNode.addEventListener(item,($event)=>{
let position = canvasNode.getBoundingClientRect()
let canvasPosition = {
x: $event.clientX - position.left,
y: $event.clientY - position.top
}
fn(ctx,canvasPosition,$event,event[item])
})
})
}
这里对于有些读者可能会感觉到比较绕一些,但是,听我一言。
如果你真的需要去封装一些什么东西,并且想做一些自己的插件这类的,这种操作是你必须经历的。而且一定要认真地去阅读代码,并且要多复盘几次!!!