基于svg写了一个涂鸦组件,说项目之前先附上几张效果图:

项目地址:SVGraffiti

由于篇幅问题,本文先总体介绍一下项目的大概情况,重点介绍一下组件间的通信方式。

一、项目说明

该项目是基于webpack@3.x.x构建的多页应用,使用ES6开发,以组件的方式组织代码。
git clone项目后(文末附上该项目github仓库地址),npm i安装相关依赖,npm run dev运行项目,默认会打开应用的首页,也就是上面的效果预览对应的界面。开发过程会单独地为一些功能编写一些测试代码,所以该项目提供了不同的页面对应于不同的功能,比如:

color picker组件测试页:

组件消息通信框架测试页:

svg底层绘制api测试页:

二、组件间通信

1、组件间为了实现最大程度的封装与解耦,不直接进行互相通信,而是通过“消息订阅/发布管理中心”(以下简称“消息中心”)进行间接通信。组件通过声明自己为不同的角色从而拥有对应的通信能力:

  • 组件声明为订阅者(Subscriber)并通过@Topics注解的形式从“消息中心”订阅自己感兴趣的主题消息,对应的消息会通过notify接口告知组件;
  • 组件声明为发布者(Publisher),可以通过Publisher角色注入的publish方法发布主题消息;
  • 组件声明为发布/订阅者(SubScatterer),同时拥有订阅者和发布者的通信能力。

这里以项目中的中间区域的画板组件为例,因为画板组件只是接收Toolbar组件发来的切换绘制能力、清空绘制内容以及Settings组件发来的设置绘制参数信息,所以该组件只是一个消息订阅者角色,编码设计如下:

首先导入对应的角色类:

import Subscriber from '../../supports/pubsub/base/subscriber';
import Topics from '../../supports/pubsub/base/topics';

编写对应的组件:

// 通过@Topics的形式订阅感兴趣的消息类型
@Topics(['function', 'resident_function', 'set_preference'])
export default class Sketchpad extends Subscriber {
// 构造器
constructor(sketchpad) {
super();
this.sketchpad = sketchpad;
// ...
} /**
* 该接口由【PubSub消息管理中心】负责调用,画板组件在此接口处理接收到的消息类型
* 1、处理Toolbar组件发送的 “切换画板绘制状态” ,对应的消息类型为:“function”
* 2、处理Toolbar组件发送的 “清空绘制内容” ,对应的消息类型为:“resident_function”
* 3、处理Settings组件发送的 “设置画板绘制参数” ,对应的消息类型为:“set_preference”
* @param {String} topic 消息主题标识
* @param {Object} entity 消息实体对象
*/
notify(topic, entity) {
// 在此处理接收到的消息
}
}

注:@Topics是静态的,若有些主题是需要运行时订阅也可以调用Subscriber角色提供的subscribe方法动态订阅消息。

2、PubSub(消息订阅/发布管理中心)的实现
既然是底层通用能力就一定要实现的不带任何具体的业务,无论是在命名规范还是编码实现上都要保证它是一个通用模块

PubSub的实现:

/**
* 主题订阅发布中心
*/
export default class PubSub { // 缓存主题和主题的订阅者列表
static topics = {}; /**
* 发布主题消息
* @param {String} topic 主题
* @param {*} entity 消息体
*/
static publish(topic, entity) {
if (!PubSub.topics[topic]) return; // 获取该主题的订阅者列表
const subscribers = PubSub.topics[topic]; // 向所有该主题的订阅者发送主题消息
for (let subscriber of subscribers) {
subscriber.notify && subscriber.notify(topic, entity);
}
} /**
* 一次登记一个主题
* @param {String} topic
*/
static registerTopic(topic) {
const topics = PubSub['topics'];
!topics[topic] && (topics[topic] = []);
} /**
* 同时登记多个主题
* @param {Array} topics
*/
static registerTopics(topics = []) {
topics.forEach(topic => {
this.registerTopic(topic);
});
} /**
* 添加主题订阅者
* @param {String} topic 主题
* @param {Object} subscriber 实现了notify接口的订阅者
*/
static addSubscriber(topic, subscriber) {
const topics = PubSub['topics'];
!topics[topic] && (topics[topic] = []); // 将该主题的订阅者登记到对应的主题
topics[topic].push(subscriber);
} /**
* 删除对应的订阅者
* @param subscriber
*/
static removeSubscriber(subscriber) {
const subs = [];
// 遍历所有主题下的订阅者列表,将对应订阅者删除
const topics = PubSub.topics;
Object.keys(topics).forEach(topicName => {
const topic = topics[topicName];
for (let i = 0; i < topic.length; ++i) {
if (topic[i] === subscriber) {
subs.push(topics[topic].splice(i, 1));
break;
}
}
});
return subs;
}
}

Subscriber的实现:

import PubSub from '../pubsub';

const addSubscribe = (topics = [], context) => {
topics.forEach(topic => {
PubSub.addSubscriber(topic, context);
});
} /**
* 主题订阅者
*/
export default class Subscriber {
constructor() {
addSubscribe(this.__proto__.constructor.topics, this);
} subscribe(topic) {
PubSub.addSubscriber(topic, this);
}
}

为了方便订阅主题,再提供一个@Topics注解:

import PubSub from '../pubsub';

/**
* 订阅者主题装饰器
* @param {Array} topics
*/
export default function Topics(topics) {
return target => {
target.topics = topics;
PubSub.registerTopics(topics);
}
}

Publisher的实现:

import PubSub from '../pubsub';

/**
* 主题消息发布者
*/
export default class Publisher {
publish(topic, entity) {
PubSub.publish(topic, entity);
}
}

SubScatterer的实现:

import PubSub from '../pubsub';
import Subscriber from './subscriber'; /**
* 主题订阅者 and 主题消息发布者
*/
export default class SubScatterer extends Subscriber {
publish(topic, entity) {
PubSub.publish(topic, entity);
}
}

本篇介绍了项目的大概情况,重点分析了如何以发布/订阅的形式实现组件间的通信,接下来还会抽时间写几个篇分别介绍“svg底层绘制能力的封装”、“画板不同绘制状态的实现与管理”、“如何开发一个通用的ColorPicker”等等与本项目相关的文章,写得不好求亲喷。

项目github地址:SVGraffiti

感兴趣的同学们欢迎star一起交流。

设计一个基于svg的涂鸦组件(一)的更多相关文章

  1. Android 设计一个菱形形状的Imageview组件.

    网上没有资料,特来请教下大神 Android 设计一个菱形形状的Imageview组件. >> android这个答案描述的挺清楚的:http://www.goodpm.net/postr ...

  2. 一个基于swoole的作业调度组件,已经实现了redis和rabitmq队列消息存储。

    https://github.com/kcloze/swoole-jobs 一个基于swoole的作业调度组件,已经实现了redis和rabitmq队列消息存储.参考资料:swoole https:/ ...

  3. 一个基于MVVM的TableView组件化实现方案

    AITableView https://github.com/chentoo/AITableView cocoapods: pod ‘AITableView’ 做什么用? 这是一个简化UITableV ...

  4. 如何设计一个基于Node.js和Express的网站架构?

    前言 今年七月份,我和几个小伙伴们合伙建立了一个开发团队.业务开展如火如荼的同时,团队宣传就提上了日程,所以迫切需要搭建公司网站出来.确定目标后我们就开始考虑如果构建一个企业网站.先是进行业内调查,看 ...

  5. 如何设计一个基于mysql的消息系统

    https://segmentfault.com/a/1190000012255186

  6. 使用webpack4搭建一个基于Vue的组件库

    组内负责的几个项目都有一些一样的公共组件,所以就着手搭建了个公共组件开发脚手架,第一次开发 library,所以是参考着 iview 的配置来搭建的.记录如何使用webpack4搭建一个library ...

  7. RSuite 一个基于 React.js 的 Web 组件库

    RSuite http://rsuite.github.io RSuite 是一个基于 React.js 开发的 Web 组件库,参考 Bootstrap 设计,提供其中常用组件,支持响应式布局. 我 ...

  8. Go/Python/Erlang编程语言对比分析及示例 基于RabbitMQ.Client组件实现RabbitMQ可复用的 ConnectionPool(连接池) 封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil 分享基于MemoryCache(内存缓存)的缓存工具类,C# B/S 、C/S项目均可以使用!

    Go/Python/Erlang编程语言对比分析及示例   本文主要是介绍Go,从语言对比分析的角度切入.之所以选择与Python.Erlang对比,是因为做为高级语言,它们语言特性上有较大的相似性, ...

  9. artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口

    artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口 自适应内容 artDialog的特殊UI框架能够适应内容变化,甚至连外部程序动态插入的内容它仍然能自适应 ...

随机推荐

  1. Go切片全解析

    Go切片全解析 目录结构: 数组 切片 底层结构 创建 普通声明 make方式 截取 边界问题 追加 拓展表达式 扩容机制 切片传递的坑 切片的拷贝 浅拷贝 深拷贝 数组 var n [4]int f ...

  2. 实战:Spring AOP实现多数据源动态切换

    需求背景 去年底,公司项目有一个需求中有个接口需要用到平台.算法.大数据等三个不同数据库的数据进行计算.组装以及最后的展示,当时这个需求是另一个老同事在做,我只是负责自己的部分. 直到今年回来了,这个 ...

  3. Forbidden You don't have permission to access this resource. 解决办法!

    这两天在使用hmailserver+roundcubemail 搭建邮箱时遇到的一些坑和大家分享一下,避免少踩坑. 关用httpd.conf及httpd-vhosts.conf配置我贴出来供大家参考. ...

  4. U8g2库的使用

    一.硬件介绍: 由于笔者这里只有0.96寸的OLED屏幕,那就讲讲最常用的0.96寸OLED屏幕吧. OLED介绍: OLED,即有机发光二极管( Organic Light Emitting Dio ...

  5. VuePress 博客优化之增加 Vssue 评论功能

    前言 在 <一篇带你用 VuePress + Github Pages 搭建博客>中,我们使用 VuePress 搭建了一个博客,最终的效果查看:TypeScript 中文文档. 本篇讲讲 ...

  6. Fiddler抓包常用功能

    通过上一篇文章Fiddler移动端抓包,我们知道了Fiddler抓包原理以及怎样进行移动端抓包,接下来介绍Fiddler中常用的功能. Fiddler中常用的功能如下: 停止抓包 清空会话窗内容 过滤 ...

  7. Spring AOP调用本类方法为什么没有生效

    首先请思考一下以下代码执行的结果: LogAop.java //声明一个AOP拦截service包下的所有方法@Aspectpublic class LogAop { @Around("ex ...

  8. PCI协议 总结

    1.引脚 必要的引脚在左边,任选的引脚在右边 2.CLK in:时钟输入,为所有PCI上的接口传送提供时序.其频率也称为PCI的工作频率. 大部分信号都在CLK的上升沿有效 3.AD0~AD31 t/ ...

  9. 【freertos】002-posix模拟器设计与cortex m3异常处理

    目录 前言 posix 标准接口层设计 模拟器的系统心跳 模拟器的task底层实质 模拟器的任务切换原理 cortex M3/M4异常处理 双堆栈指针 双操作模式 栈帧 EXC_RETURN 前言 如 ...

  10. Spring Boot 2.X 有什么新特性?与 1.X 有什么区别?

    配置变更JDK 版本升级第三方类库升级响应式 Spring 编程支持HTTP/2 支持配置属性绑定更多改进与加强-