使用面向对象思想去封装实现canvas功能

news2025/2/26 8:06:59

前言

各种插件/库和一些常规的业务代码,最大的区别就在于编程的思路与方法。

比如我们现在想写一段业务代码,使用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])
        })
    })
}

这里对于有些读者可能会感觉到比较绕一些,但是,听我一言。

如果你真的需要去封装一些什么东西,并且想做一些自己的插件这类的,这种操作是你必须经历的。而且一定要认真地去阅读代码,并且要多复盘几次!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1477490.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内网渗透-DC-9靶机渗透

攻击机&#xff1a;kali 192.168.236.137 目标机&#xff1a;dc-9 192.168.236.138 一、信息收集 1.使用arp-scan -l和nmap进行主机发现和端口信息收集 nmap -sS -T5 --min-rate 10000 192.168.236.138 -sC -p- 发现22端口被阻塞 2.whatweb收集一下cms指纹信息 what http…

无人驾驶决策规划技术理论(一)

本博客记录学习李柏老师所著的决策规划技术理论与实践中的关键知识&#xff0c;想以此作为自己学习的框架&#xff0c;方便自己日后回顾。 1 非结构化场景决策规划方法 场景&#xff1a;港口、停车场的泊车附近区域、偏僻道路、野外 特色&#xff1a;障碍物位置杂乱无章 决策…

unity使用Registry类将指定内容写入注册表

遇到一个新需求&#xff0c;在exe执行初期把指定内容写入注册表&#xff0c;Playerprefs固然可以写入&#xff0c;但是小白不知道怎么利用Playerprefs写入DWORD类型的数据&#xff0c;因此使用了Registry类 一. 对注册表中键的访问 注册表中共可分为五类 一般在操作时&#…

个人如何合法自建服务器?

随着互联网技术的不断发展&#xff0c;越来越多的人开始考虑自建服务器&#xff0c;以满足自己的需求。但是&#xff0c;在自建服务器之前&#xff0c;必须了解相关的法律法规和规定&#xff0c;以确保自己的行为合法合规。本文将介绍个人如何合法自建服务器&#xff0c;以供参…

【Oracle】玩转Oracle数据库(七):RMAN恢复管理器

前言 嘿&#xff0c;数据库大魔法师们&#xff01;准备好迎接新的技术大招了吗&#xff1f;今天我们要探索的是Oracle数据库中的神奇利器——RMAN恢复管理器&#xff01;&#x1f6e1;️&#x1f4be; 在这篇博文【Oracle】玩转Oracle数据库&#xff08;七&#xff09;&#xf…

单片机SWJ 调试端口(SW-DP和JTAG)、SWD下载电路

单片机下载接口是指用于将编写好的程序代码下载到单片机芯片中的接口。常见的单片机下载接口包括以下几种&#xff1a; 1. **串口下载接口**&#xff1a;通过串口&#xff08;如UART或RS-232接口&#xff09;与计算机或下载器相连&#xff0c;将程序代码通过串口传输到单片机内…

云里物里轻薄系列电子价签,如何革新零售?

云里物里的DS轻薄系列电子价签&#xff0c;凭借轻巧外观和强劲性能&#xff0c;为零售行业提供了更便捷的商品改价方案。这不仅是对纸质价标的替代&#xff0c;更以其安全性和可持续发展性&#xff0c;实现对零售行业的效率升级&#xff0c;让商家们轻松迎接数字化时代的挑战&a…

Java毕业设计-基于springboot开发的家乡特色推荐系统-毕业论文+答辩PPT(有源代码)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1.开发说明2.需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、管理员功能模块3、用户功能模块 四、毕设内容和源代码获取总结 Java毕业设计-基于springboot开发的家乡特色推荐系…

Vue+SpringBoot打造音乐偏好度推荐系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 音乐档案模块2.1.2 我的喜好模块2.1.3 每日推荐模块2.1.4 通知公告模块 2.2 用例图设计2.3 实体类设计2.4 数据库设计 三、系统展示3.1 登录注册3.2 音乐档案模块3.3 音乐每日推荐模块3.4 通知公告模…

客服办公神器·带你实现快捷回复自由

节后很多做客服的小伙伴都来找我说回复挺力不从心的&#xff0c;让我支点招。因为每个小伙伴遇到的顾客问题和回复情况都各不相同&#xff0c;我还是建议大家下载一个利于提高自己办公效率的软件&#xff0c;像我一直在用的这个“客服宝快捷回复软件”真是客服打工人之光&#…

使用maven项目引入jQuery

最近在自学 springBoot &#xff0c;期间准备搞一个前后端不分离的东西&#xff0c;于是需要在 maven 中引入jQuery 依赖&#xff0c;网上百度了很多&#xff0c;这里来做一个总结。 1、pom.xml 导入依赖 打开我们项目的 pom.xml 文件&#xff0c;输入以下坐标。这里我使用的是…

uniapp开发笔记----发布成微信小程序体验版本

HBuilderX-vue2 一 创建vue2项目并打包成微信小程序&#xff0c;发布到体验版本1. 创建项目2. 开发页面3. 打包成微信小程序4. 发布成微信小程序体验版本-上传代码成功后登陆微信公众平台[https://mp.weixin.qq.com/](https://mp.weixin.qq.com/)-找到版本可管理->开发版本-…

spring boot 整合 minio存储 【使用篇】

zi导入依赖 <!--minio--><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.0.3</version></dependency> yml配置&#xff08;默认配置&#xff09; spring:# 配置文件上传大小限制s…

vue -- watermark水印添加方法

前言 项目生成公司水印是很普遍的需求&#xff0c;下面是vue项目生产水印的方法。话不多说&#xff0c;复制粘贴就可以马上解决你的需求。 步骤1 创建watermark.js文件。目录结构 /** 水印添加方法 */let setWatermark (str1, str2) > {let id 1.23452384164.1234124…

配电房轨道式巡检机器人方案

一、应用背景 在变电站、配电房、开关站等各种室内变配电场所内&#xff0c;由于变配电设备的数量众多、可能存在各类安全隐患&#xff0c;为了保证用电的安全可靠&#xff0c;都要进行日常巡检。 但目前配电房人工巡检方式有以下主要问题&#xff1a; 巡检工作量大、成本高 …

无法导入ohos.bundle.installer错误解决方法

今天在运行一个开源项目时&#xff0c;发现编译项目时报了一个错误&#xff1a;ohos.bundle.installer。 对应的SDK版本信息如下&#xff1a; 解决方法&#xff1a; 造成错误的原因是&#xff0c;我们使用的是public-sdk&#xff0c;所以我们需要到OpenHarmony平台下载full-s…

配电房智能辅助监控系统设计

业务背景 工业企业、学校、医院、居民小区等单位有这海量的配电房&#xff0c;这些配电房内的配电设备种类多、运行环境复杂&#xff0c;存在各种各样的安全隐患。目前这些配电房主要依靠人员在场值守或巡检方式进行管理&#xff0c;但单纯的人工运维方式既成本高&#xff0c;…

第3届图像处理与媒体计算国际会议(ICIPMC 2024)即将召开!

2024年第3届图像处理与媒体计算国际会议&#xff08;ICIPMC2024&#xff09;将于2024年5月17-19日在中国合肥举行。本次大会由安徽大学、西北工业大学&#xff0c;西北大学和IEEE联合主办。ICIPMC 2024旨在汇集该领域领先的学术科学家、研究人员和学者&#xff0c;并进行交流和…

精品springboot校园失物招领系统

《[含文档PPT源码等]精品基于springboot校园失物招领系统[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#xff1a;HTML5,…

Linux磁盘如何分区?

首先需要先给虚拟机添加磁盘 sblk #查看磁盘设备 得到以下内容&#xff1a; NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 20G 0 disk ├─sda1 8:1 0 1G 0 part /boot └─sda2 8:2 0 19G 0 pa…