基于 Canvas 的简单画图组件,让你用类似于 DOM 的方式,在 Canvas 上画图。
- 🎨 简单易用 - 类似 DOM 的 API 设计,学习成本低
- 🚀 高性能 - 基于 Canvas 原生渲染,支持大量图形
- 📱 跨平台 - 支持浏览器、Node.js 和微信小程序
- 🎯 丰富图形 - 内置矩形、圆形、线条、箭头、贝塞尔曲线等常用图形
- 🎭 事件系统 - 完整的鼠标和触摸事件支持
- 🔧 可扩展 - 支持自定义图形控件
- 🌈 样式丰富 - 支持渐变、阴影、透明度、滤镜、虚线、混合模式等样式
- 🖼️ 边框系统 - 完整的 border 支持(宽度/样式/颜色),四角独立圆角
- ✂️ 裁剪遮罩 - 支持 clipPath 裁剪路径和 mask 遮罩效果
- 📐 图层管理 - 支持多图层操作,包括创建、切换、删除图层
- 🔍 缩放平移 - 支持画布缩放和平移操作
- 📤 导出功能 - 支持导出为 PNG、JPEG 和 SVG 格式
- 📝 文本换行 - 支持文本自动换行显示
npm install jmgraphyarn add jmgraph直接下载 dist/jmgraph.min.js 并在 HTML 中引用:
<scripttype="text/javascript" src="../dist/jmgraph.min.js"></script>注意:
g.createShape()创建图形后会自动添加到当前活动图层,无需手动调用g.children.add()。若设置了autoRefresh: true(默认值),画布会自动刷新,也无需手动调用g.redraw()。
<scripttype="module">importjmGraphfrom"jmgraph";constcontainer=document.getElementById('mycanvas_container');constg=jmGraph(container,{width: 800,height: 600,autoRefresh: true,style: {fill: '#000'}});</script>constjmGraph=require('jmgraph');constg=jmGraph.create('mycanvas_container',{width: 800,height: 600,style: {fill: '#000'}});conststyle={stroke: '#46BF86',lineWidth: 2,shadow: '0,0,10,#fff'};constrect=g.createShape('rect',{style: style,position: {x: 100,y: 100},width: 100,height: 100});// createShape 会自动将图形添加到当前活动图层,无需手动 g.children.add()// 如果 autoRefresh 为 true(默认),也不需要手动调用 g.redraw()jmGraph 支持简化的样式名称和原生 Canvas 样式:
| 简化名称 | 原生名称 | 说明 |
|---|---|---|
| fill | fillStyle | 填充颜色、渐变或模式 |
| stroke | strokeStyle | 描边颜色、渐变或模式 |
| shadow | - | 阴影,格式:'0,0,10,#fff' |
| shadow.blur | shadowBlur | 阴影模糊级别 |
| shadow.x | shadowOffsetX | 阴影水平偏移 |
| shadow.y | shadowOffsetY | 阴影垂直偏移 |
| shadow.color | shadowColor | 阴影颜色 |
| lineWidth | lineWidth | 线条宽度 |
| miterLimit | miterLimit | 最大斜接长度 |
| font | font | 字体 |
| fontSize | font | 字体大小 |
| fontFamily | font | 字体名称 |
| opacity | globalAlpha | 透明度 |
| textAlign | textAlign | 文本水平对齐 |
| textBaseline | textBaseline | 文本垂直对齐 |
| lineJoin | lineJoin | 线条连接样式 |
| lineCap | lineCap | 线条端点样式 |
| maxWidth | maxWidth | 文本最大宽度(用于自动换行) |
| lineDash | - | 自定义虚线模式,数组或字符串格式,如 [10, 5] 或 '10,5' |
| lineDashOffset | lineDashOffset | 虚线偏移量 |
| filter | filter | CSS 滤镜效果,如 'blur(3px) grayscale(50%)' 或对象 { blur: 3, brightness: 1.2 } |
| globalCompositeOperation | globalCompositeOperation | 混合模式,如 multiply、screen、overlay |
| border | - | 边框系统,对象 { width, style, color } 或字符串 '2px solid #ff0000' |
| clipPath | - | 裁剪路径,传入图形控件实例 |
| mask | - | 遮罩效果,传入图形控件实例 |
jmGraph 支持完整的 CSS 渐变语法,通过 jmGradient 类实现。支持线性渐变和径向渐变。
// 1. 角度格式(deg/rad/grad/turn)'linear-gradient(180deg, #8b5cf6 0%, #6366f1 50%, #4f46e5 100%)''linear-gradient(0.5turn, #10b981, #3b82f6)''linear-gradient(3.14159rad, #f59e0b, #ef4444)'// 2. 方向关键词'linear-gradient(to top, #e94560, #00d4ff)''linear-gradient(to right, #ffd93d, #e94560)''linear-gradient(to top right, #8b5cf6, #f59e0b)'// 3. 坐标格式(x1 y1 x2 y2)—— 注意:坐标之间用空格分隔,不要用逗号'linear-gradient(50% 0 50% 100%, rgba(36,159,218,0) 1, rgba(36,159,218,0.8) 0)'// 4. 径向渐变'radial-gradient(circle, #e94560, #8b5cf6)''radial-gradient(ellipse at top, #06b6d4, #8b5cf6)''radial-gradient(50% 50% 100% 50% 50% 0%, #ffd93d 0%, #f59e0b 100%)'// 方式一:直接使用字符串(最简单)constrect=g.createShape('rect',{position: {x: 100,y: 100},width: 200,height: 100,style: {fill: 'linear-gradient(180deg, #e94560 0%, #00d4ff 100%)'}});// 方式二:使用 jmGradient 对象import{jmGradient}from'jmgraph';constgradient=newjmGradient({type: 'linear',x1: '50%',y1: '0%',x2: '50%',y2: '100%',stops: [{offset: 0,color: 'rgba(36,159,218,0)'},{offset: 1,color: 'rgba(36,159,218,0.8)'}]});constrect2=g.createShape('rect',{position: {x: 100,y: 220},width: 200,height: 100,style: {fill: gradient}});// 方式三:使用 createLinearGradient / createRadialGradient 便捷方法constlinearGradient=g.createLinearGradient(0,0,0,100);linearGradient.addStop(0,'#e94560');linearGradient.addStop(1,'#00d4ff');constradialGradient=g.createRadialGradient(100,100,0,100,100,50);radialGradient.addStop(0,'#ffd93d');radialGradient.addStop(1,'#f59e0b');偏移量(offset)表示颜色在渐变中的位置,支持三种写法:
// 小数(0~1)—— 0 表示渐变起点,1 表示渐变终点'linear-gradient(180deg, #e94560 0, #00d4ff 0.5, #8b5cf6 1)'// 百分比(0%~100%)'linear-gradient(180deg, #e94560 0%, #00d4ff 50%, #8b5cf6 100%)'// 省略偏移量(首尾自动为 0 和 1,中间均匀分布)'linear-gradient(180deg, #e94560, #00d4ff, #8b5cf6)'// 等价于 'linear-gradient(180deg, #e94560 0, #00d4ff 0.5, #8b5cf6 1)'注意:偏移量
1和100%是等价的,都表示渐变终点。偏移量会自动归一化到 0~1 范围。
支持多种颜色格式:
// hex'linear-gradient(180deg, #e94560 0%, #00d4ff 100%)'// rgba —— 逗号后有无空格均可'linear-gradient(180deg, rgba(233,69,96,0.8) 0%, rgba(0,212,255,0.8) 100%)''linear-gradient(180deg, rgba(233, 69, 96, 0.8) 0%, rgba(0, 212, 255, 0.8) 100%)'// hsl/hsla'linear-gradient(180deg, hsl(345, 82%, 62%) 0%, hsl(191, 100%, 50%) 100%)'// 命名颜色'linear-gradient(to top, red, blue, green)'// transparent'linear-gradient(180deg, transparent, rgba(233,69,96,0.7))'坐标参数支持多种形式:
// 百分比(推荐)—— 相对于控件边界尺寸计算
x1: '50%'// 控件宽度的一半// 小数(0~1)—— 自动乘以控件尺寸,效果等同于百分比
x1: 0.5// 同 '50%'// 绝对像素值
x1: 100// 固定 100 像素位置点击展开常见问题
- 坐标分隔符错误:坐标格式中坐标之间用空格分隔,不要用逗号
// 正确'linear-gradient(50% 0 50% 100%, rgba(36,159,218,0) 1, rgba(36,159,218,0.8) 0)'// 错误 - 坐标之间不能有逗号'linear-gradient(50%, 0, 50%, 100%, rgba(36,159,218,0) 1, ...)'- 颜色停止点之间必须有逗号:颜色停止点用逗号分隔
// 正确'linear-gradient(180deg, #e94560 0%, #00d4ff 100%)'// 错误 - 颜色停止点之间缺少逗号'linear-gradient(180deg #e94560 0% #00d4ff 100%)'- rgba 透明度为 0 时必须写完整:不能只写
rgba(r,g,b, 0)而不写颜色值
// 正确'linear-gradient(50% 0 50% 100%, rgba(36,159,218,0) 1, rgba(36,159,218,0.8) 0)'// rgba 中透明度参数是最后一个值,逗号后可以有空格'rgba(36,159,218, 0)'// 正确'rgba(36,159,218,0)'// 正确'rgba(36, 159, 218, 0)'// 正确- 渐变至少需要 2 个颜色停止点
// 正确 - 至少 2 个颜色'linear-gradient(180deg, #e94560 0%, #00d4ff 100%)'// 错误 - 只有 1 个颜色,无法产生渐变效果'linear-gradient(180deg, #e94560)'- 多行渐变字符串:支持换行符
// 正确 - 多行字符串也能正常解析`linear-gradient(50% 0 50% 100%, rgba(36,159,218,0) 1, rgba(36,159,218,0.8) 0)`- addStop 偏移量范围:使用
addStop()方法时,偏移量必须在 0~1 之间
// 正确gradient.addStop(0,'#e94560');gradient.addStop(0.5,'#00d4ff');gradient.addStop(1,'#8b5cf6');// 错误 - 偏移量超出 0~1 范围会被自动裁剪并输出警告gradient.addStop(1.5,'#e94560');// 会被调整为 1gradient.addStop(-0.5,'#e94560');// 会被调整为 0支持 CSS 标准滤镜,可用值包括:
| 滤镜 | 说明 | 示例 |
|---|---|---|
| blur | 模糊 | 'blur(3px)' |
| grayscale | 灰度 (0-1) | 'grayscale(100%)' |
| sepia | 怀旧 (0-1) | 'sepia(80%)' |
| brightness | 亮度 (数值) | 'brightness(1.5)' |
| contrast | 对比度 (数值) | 'contrast(2)' |
| saturate | 饱和度 (数值) | 'saturate(1.5)' |
| hue-rotate | 色相旋转 (deg) | 'hue-rotate(90deg)' |
| invert | 反转 (0-1) | 'invert(100%)' |
| opacity | 不透明度 (0-1) | 'opacity(0.5)' |
支持字符串格式、对象格式或 jmFilter 实例:
// 字符串格式(多个滤镜组合)
style: {fill: '#e94560',filter: 'blur(1px) brightness(1.2) saturate(1.5)'}// 对象格式
style: {fill: '#00d4ff',filter: {blur: 3,grayscale: 0.5}}// 使用 jmFilter 类import{jmFilter}from'jmgraph';constf=newjmFilter({blur: 2,brightness: 1.3});
style: {fill: '#ffd93d',filter: f}通过 lineDash 定义自定义虚线模式,替代原有的 lineType: 'dotted':
// 等间距虚线
style: {stroke: '#00d4ff',lineWidth: 2,lineDash: [10,5]}// 字符串格式
style: {stroke: '#ff6b6b',lineWidth: 2,lineDash: '10, 5, 2, 5'}// 带偏移量
style: {stroke: '#ffd93d',lineWidth: 2,lineDash: [10,10],lineDashOffset: 5}radius 属性支持数字(四角相同)和对象格式(四角独立):
// 统一圆角(向后兼容)g.createShape('rect',{position: {x: 20,y: 20},width: 200,height: 80,radius: 20,style: {fill: '#e94560'}});// 四角独立圆角g.createShape('rect',{position: {x: 20,y: 130},width: 200,height: 80,radius: {topLeft: 30,topRight: 5,bottomRight: 30,bottomLeft: 5},style: {fill: '#00d4ff'}});// 通过 style.borderRadius 设置g.createShape('rect',{position: {x: 20,y: 240},width: 200,height: 80,style: {fill: '#00ff88',borderRadius: {topLeft: 40,topRight: 0,bottomRight: 0,bottomLeft: 40}}});支持 Canvas 标准混合模式:
// multiply 混合g.createShape('circle',{center: {x: 120,y: 120},radius: 60,style: {fill: '#e94560'}});g.createShape('circle',{center: {x: 170,y: 120},radius: 60,style: {fill: '#00d4ff',globalCompositeOperation: 'multiply'}});传入一个图形控件实例作为裁剪区域:
// 创建裁剪区域(圆形)constclipCircle=g.createShape('circle',{center: {x: 300,y: 200},radius: 80,style: {close: true}});clipCircle.initPoints();// 被裁剪的矩形,只在圆形区域内可见g.createShape('rect',{position: {x: 180,y: 120},width: 240,height: 160,radius: 12,style: {fill: 'linear-gradient(0 0 240 160, #e94560 0, #00d4ff 1)',clipPath: clipCircle}});constrect=g.createShape('rect',{style: style,position: {x: 100,y: 100},width: 100,height: 100});constarc=g.createShape('arc',{style: style,center: {x: 100,y: 150},width: 120,height: 80});constline=g.createLine({x: 10,y: 200},{x: 80,y: 120},style);constarrow=g.createShape('arrow',{style: style,start: {x: 150,y: 120},end: {x: 160,y: 150}});constbezier=g.createShape('bezier',{style: style,points: [p0,p1,p2,p3,p4]});constimg=g.createShape('image',{style: {src: 'image.png'},position: {x: 100,y: 100}});img.canMove(true);constlabel=g.createShape('label',{style: {stroke: '#effaaa',fill: '#fff',textAlign: 'center',textBaseline: 'middle',fontSize: 24,fontFamily: 'Arial',maxWidth: 200// 文本最大宽度,超过会自动换行},position: {x: 200,y: 150},text: '这是一段测试文本,展示文本换行功能',width: 200,height: 100});constellipse=g.createShape('ellipse',{style: style,center: {x: 100,y: 150},width: 120,height: 80});constpolygon=g.createShape('polygon',{style: style,center: {x: 100,y: 150},sides: 6,// 边数radius: 50// 半径});conststar=g.createShape('star',{style: style,center: {x: 100,y: 150},points: 5,// 顶点数radius: 50,// 外半径innerRadius: 25// 内半径});constshape=g.createShape('rect',{...});shape.bind('mouseover',function(evt){this.style.stroke='rgba(39,72,188,0.5)';this.cursor('pointer');this.needUpdate=true;});| 事件名称 | 说明 | 回调参数 |
|---|---|---|
| mousedown | 鼠标按下 | - |
| mousemove | 鼠标移动 | {target, position} |
| mouseover | 鼠标移入 | {target} |
| mouseleave | 鼠标移出 | {target} |
| mouseup | 鼠标松开 | - |
| click | 鼠标点击 | - |
| dblclick | 鼠标双击 | - |
| touchstart | 触摸开始 | {position} |
| touchmove | 触摸移动 | {position} |
| touchend | 触摸结束 | {position} |
大多数控件继承 jmPath 即可,通过实现 initPoints 方法来绘制自定义图形:
import{jmPath}from"jmgraph";classCustomShapeextendsjmPath{constructor(params){super(params);this.center=params.center||{x: 0,y: 0};this.radius=params.radius||0;}initPoints(){constlocation=this.getLocation();constcx=location.center.x;constcy=location.center.y;this.points=[];this.points.push({x: cx-this.radius,y: cy-this.radius});this.points.push({x: cx+this.radius,y: cy+this.radius});returnthis.points;}}jmGraph 支持微信小程序,详情请参考 mini-jmchart。
constjmGraph=require('../../utils/jmgraph');constg=jmGraph.create('mycanvas',{style: {fill: '#000'},width: 400,height: 600});this.canvastouchstart=function(...arg){returng.eventHandler.touchStart(...arg);}this.canvastouchmove=function(...arg){returng.eventHandler.touchMove(...arg);}this.canvastouchend=function(...arg){returng.eventHandler.touchEnd(...arg);}// 设置缩放因子,以指定点为中心// 缩放因子,1为原始大小// x, y 缩放中心坐标g.setZoom(1.5,400,300);// 平移画布// dx, dy 平移距离g.pan(100,50);// 重置缩放和平移g.resetTransform();// 创建新图层// name 图层名称// options 图层选项constlayer=g.createLayer('My Layer',{visible: true,locked: false});// 切换到指定图层// layer 图层名称或图层对象g.setActiveLayer('My Layer');// 获取所有图层constlayers=g.getLayers();// 获取指定名称的图层constlayer=g.getLayer('My Layer');// 获取当前活动图层constactiveLayer=g.getActiveLayer();// 移除指定图层// layer 图层名称或图层对象constsuccess=g.removeLayer('My Layer');// 将形状添加到指定图层// shape 形状对象// layer 图层名称或图层对象,默认为当前活动图层g.addShapeToLayer(shape,'My Layer');// 从图层中移除形状// shape 形状对象g.removeShapeFromLayer(shape);// 导出为 PNG 图片// fileName 文件名// format 图片格式,默认为 image/png// quality 图片质量,0-1之间g.exportToPNG('my-graph','image/png',0.9);// 导出为 JPEG 图片// fileName 文件名// quality 图片质量,0-1之间g.exportToJPEG('my-graph',0.8);// 导出为 SVG// fileName 文件名g.exportToSVG('my-graph');当不再需要 jmGraph 实例时,调用 destroy() 释放资源(如事件监听、动画帧等):
constg=jmGraph('mycanvas',{ ... });// 使用完毕后销毁g.destroy();// 销毁后可通过 destroyed 标志判断状态if(g.destroyed){console.log('实例已销毁');}
destroy()会内部调用eventHandler.destroy()清除所有事件绑定,并设置destroyed = true标记。调用后不应再使用该实例。
当文本长度超过 maxWidth 时,会自动换行显示:
constlabel=g.createShape('label',{style: {fill: '#333',fontSize: 14,fontFamily: 'Arial',textAlign: 'center',maxWidth: 200// 文本最大宽度,超过会自动换行},position: {x: 200,y: 150},text: '这是一段测试文本,当文本长度超过最大宽度时,会自动换行显示。',width: 200,height: 100});npm run buildnpm run dev欢迎贡献代码!请遵循以下步骤:
- Fork 本仓库
- 创建特性分支 (
git checkout -b feature/AmazingFeature) - 提交更改 (
git commit -m 'Add some AmazingFeature') - 推送到分支 (
git push origin feature/AmazingFeature) - 开启 Pull Request
本项目采用 MIT 许可证。
- GitHub Issues - 报告 Bug 和功能请求
- GitHub Discussions - 问题和讨论
感谢所有为本项目做出贡献的开发者!
- jmChart - 基于 jmGraph 的图表库
- mini-jmchart - 微信小程序图表库
- 作者: jiamao
- 邮箱: haofefe@163.com
- 主页: https://fefeding.github.io/jmgraph/
如果这个项目对你有帮助,请给个 ⭐️ Star!