一、前言
五一来了,好家伙直接放5天假,今年立志要也稍微小卷一下,必须把这5天时间好好利用一下。本来打算上上课啥的,不过和我的好朋友宇深商量了一下,不如一起做个小个人项目,实践一把它不香么?
那到底做啥呢!
思前想后,我们决定做一个简单的svg编辑器,废话不多说,先看一下效果
-
可滑动的工具栏
-
拖拽想要的图形
-
自定义svg属性
-
代码生成
体验地址:wildvillage.github.io/SVG-Editor/
源码地址:github.com/wildvillage…
二、项目报告
-
项目背景
这个项目是源于我们都有一个想法,就是平常的工作中比较少接触到svg或者canvas的相关应用场景,但是我们都对svg和canvas还是很感兴趣的,因此希望做这方面的一个练手和尝试;于是经过了一个电话的讨论,确定了这样的一个方向; -
选择原因
主要有以下两个原因:- 项目不会涉及到前后段数据交互,这样在有限的时间内,我们可以把精力放在前端
- 编辑器交互逻辑还是有一定的挑战的,有一定的尝试价值
-
投入产出
项目总共代码工时应该是有30个小时左右,人均15个小时。因为我们每天花在这个上面的时间大约都是3个小时左右的时间;一起讨论出想法之后,就开始写会儿代码!然后咖啡店要关门了就回家!整个过程不算高效,但尽力了。所谓产出就是这样的一个半成品,但是我不会就这样就算了,今后依然会不断完善,把当时的想法都实现掉;但这篇博客可以先写,毕竟博客也属于当时的一个计划嘛!
三、技术细节
-
技术栈:react18 + vite + antd + ts
考虑到这样的项目本质上其实是一个只有一个路由的单页面,因此就不需要用类似umi这样的框架了,我们直接用react自带的脚手架,另外时间紧迫,不自己造UI轮子了,antd直接拿来用,ts是必须用的,这个没得说;
-
基于svg
我们最初的设想的做一个可以让用户随便画东西,然后能够生成代码的工具,我们设想过用canvas但是我们想到canvas画出来的图形不会体现在DOM上,因为DOM上就只有一个元素
<canvas></canvas>,而画出来的图形代码都在js当中。当时没想到用canvas怎么实现,但svg的元素直接能够体现在DOM上,获取代码直接获取DOM就可以,因此选择基于svg进行实现;这样会方便一些。 -
数据流
画了一张简单的类型信息,左侧的菜单和可移动的tool工具包同时给画布传递信息,这个信息是如下这样的数据结构:
const render = [
{
id:string,
type:"line" | "rect",
attrs:{
...props
}
},
...
]
画布就做一件事情那就是拿到最新的render进行渲染;render的信息保存在全局的状态管理器中
-
关于拖拽
拖拽主要是用了ahooks中的两个hook,分别是
useDrag 和 useDrop,然后拿到画布所放的位置信息,在render中push一条信息即可;详情可看ahooks官网文档示例
-
关于网格和刻度
网格是使用svg画出来的,其实本质上就是横向和竖向的等差数列line.
generateSplitLine = ( split: number, step: number, type: 'vertical' | 'horizontal' ) => { const STROKE = '#ccc5'; if (type === 'vertical') { return new Array(split).fill(null).map((item, idx) => { return { x1: idx * step, y1: 0, x2: idx * step, y2: '100%', stroke: STROKE, strokeWidth: idx % 5 ? 1 : 1.5, }; }); } else { return new Array(split).fill(null).map((item, idx) => { return { x1: 0, y1: idx * step, x2: '100%', y2: idx * step, stroke: STROKE, strokeWidth: idx % 5 ? 1 : 1.5, }; }); } }; function SplitLine({ width, height }: { width: number; height: number }) { const splitLine = useMemo(() => { if (height && width) { const vertical = Math.ceil(width / 100); const verticalLine = generateSplitLine( vertical * 5, SCALE_STEP, 'vertical' ); const horizontal = Math.ceil(height / 100); const horizontalLine = generateSplitLine( horizontal * 5, SCALE_STEP, 'horizontal' ); return [...verticalLine, ...horizontalLine]; } }, [height, width]); return ( <svg xmlns={SVG_XMLNS} className={styles.splitLine}> {splitLine?.map((line) => ( <line key={nanoid()} {...line} /> ))} </svg> ); } -
代码展示
可以看到,代码的展示属于一个beautiful版的代码,但是实际上他们就是代码字符串而已,怎么使其美化呢?用到了一个叫做
Prism的库,专门用来美化字符串代码的一个轻量的库;import Prism from 'prismjs'; import 'prismjs/themes/prism-okaidia.css'; const 美化后的code = Prism.highlight(原字符串, Prism.languages.svg, 'Markup');简单研究了一下,它的原理其实就是遍历每一个字符串,然后拦截诸如
<>、svg、line这些敏感字符串,然后将他们用<span>包装一下,加上类名使用css美化就好了;
三、项目心得
-
高效才是硬道理
无论做任何事情,其实效率高是非常重要的,做这个项目,效率还算可以,但是有的时候还是浪费了不少时间。另外节奏非常重要,应该在前两天把核心技术讨论一下攻克掉,否则后面没时间了,就水过去了。
-
让假期更有意义
通过这次的小项目,我发现,其实假期也可以和自己感兴趣的事情结合在一起去过,也是一种很不错的体验,很喜欢那种一起讨论,一起头脑风暴的感觉,瞬间动力满满,因为这样的东西,不是为老板而坐,更不是为了别人想要的效果而做,而是朝着自己希望的方向去做;所以无论多么花费时间也觉得值得;
-
1 + 1 > 2
可能如果是自己一个人弄的话,假期就玩过去了,但是有一到两个小伙伴一起弄的话,会相互监督,相互提意见,把这件事情尽可能做的好一点,我觉得这个是展现出了1 + 1 > 2的感觉,因为它会倒逼我们进步;同时一起合作的时候,也会学到对方身上优秀的点,不知道的东西又少了一些;很nice;
四、关于未来
-
维护
这个项目会一直维护下去,因为这个项目,目前还是一个半成品,我还是比较希望能够做成当时设想的那个样子,目前是还有图形操作上的内容没有完成,数据流部分还不是很完善,接下来会逐渐改善这个部分;
-
拓展
接下来,我能想到的是拓展这样的一个功能,可实现图形的拖拽移动、旋转、放大、缩小。目前支持直线和矩形,接下来会支持圆形、箭头、椭圆、多边形等,UI相关的样式也会重新进行一个设计。尤其是图标部分,需要统一和美观。
五、结语
感谢各位的阅读,我的base在杭州,如果有希望一起进步的同学欢迎一起学习前端知识,偶尔一起做个前端项目也挺棒的哦!😁
自荐其他文章阅读:
《typescript内置类型》
《typescript自测清单》
《时间切片了解一下》
《commonjs和es6的区别》
《设计一个Scrollbar的终极解决方案》