Skip to content

Repository files navigation

jmGraph

npm versionnpm downloadsLicense: MITBuild Status

基于 Canvas 的简单画图组件,让你用类似于 DOM 的方式,在 Canvas 上画图。

✨ 特性

  • 🎨 简单易用 - 类似 DOM 的 API 设计,学习成本低
  • 🚀 高性能 - 基于 Canvas 原生渲染,支持大量图形
  • 📱 跨平台 - 支持浏览器、Node.js 和微信小程序
  • 🎯 丰富图形 - 内置矩形、圆形、线条、箭头、贝塞尔曲线等常用图形
  • 🎭 事件系统 - 完整的鼠标和触摸事件支持
  • 🔧 可扩展 - 支持自定义图形控件
  • 🌈 样式丰富 - 支持渐变、阴影、透明度、滤镜、虚线、混合模式等样式
  • 🖼️ 边框系统 - 完整的 border 支持(宽度/样式/颜色),四角独立圆角
  • ✂️ 裁剪遮罩 - 支持 clipPath 裁剪路径和 mask 遮罩效果
  • 📐 图层管理 - 支持多图层操作,包括创建、切换、删除图层
  • 🔍 缩放平移 - 支持画布缩放和平移操作
  • 📤 导出功能 - 支持导出为 PNG、JPEG 和 SVG 格式
  • 📝 文本换行 - 支持文本自动换行显示

📦 安装

npm

npm install jmgraph

yarn

yarn add jmgraph

CDN

直接下载 dist/jmgraph.min.js 并在 HTML 中引用:

<scripttype="text/javascript" src="../dist/jmgraph.min.js"></script>

🚀 快速开始

注意g.createShape() 创建图形后会自动添加到当前活动图层,无需手动调用 g.children.add()。若设置了 autoRefresh: true(默认值),画布会自动刷新,也无需手动调用 g.redraw()

ES6 模块方式

<scripttype="module">importjmGraphfrom"jmgraph";constcontainer=document.getElementById('mycanvas_container');constg=jmGraph(container,{width: 800,height: 600,autoRefresh: true,style: {fill: '#000'}});</script>

CommonJS 方式

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 样式:

简化名称原生名称说明
fillfillStyle填充颜色、渐变或模式
strokestrokeStyle描边颜色、渐变或模式
shadow-阴影,格式:'0,0,10,#fff'
shadow.blurshadowBlur阴影模糊级别
shadow.xshadowOffsetX阴影水平偏移
shadow.yshadowOffsetY阴影垂直偏移
shadow.colorshadowColor阴影颜色
lineWidthlineWidth线条宽度
miterLimitmiterLimit最大斜接长度
fontfont字体
fontSizefont字体大小
fontFamilyfont字体名称
opacityglobalAlpha透明度
textAligntextAlign文本水平对齐
textBaselinetextBaseline文本垂直对齐
lineJoinlineJoin线条连接样式
lineCaplineCap线条端点样式
maxWidthmaxWidth文本最大宽度(用于自动换行)
lineDash-自定义虚线模式,数组或字符串格式,如 [10, 5]'10,5'
lineDashOffsetlineDashOffset虚线偏移量
filterfilterCSS 滤镜效果,如 'blur(3px) grayscale(50%)' 或对象 { blur: 3, brightness: 1.2 }
globalCompositeOperationglobalCompositeOperation混合模式,如 multiplyscreenoverlay
border-边框系统,对象 { width, style, color } 或字符串 '2px solid #ff0000'
clipPath-裁剪路径,传入图形控件实例
mask-遮罩效果,传入图形控件实例

渐变 (jmGradient)

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)'

注意:偏移量 1100% 是等价的,都表示渐变终点。偏移量会自动归一化到 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 像素位置

常见错误与注意事项

点击展开常见问题
  1. 坐标分隔符错误:坐标格式中坐标之间用空格分隔,不要用逗号
// 正确'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, ...)'
  1. 颜色停止点之间必须有逗号:颜色停止点用逗号分隔
// 正确'linear-gradient(180deg, #e94560 0%, #00d4ff 100%)'// 错误 - 颜色停止点之间缺少逗号'linear-gradient(180deg #e94560 0% #00d4ff 100%)'
  1. 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)'// 正确
  1. 渐变至少需要 2 个颜色停止点
// 正确 - 至少 2 个颜色'linear-gradient(180deg, #e94560 0%, #00d4ff 100%)'// 错误 - 只有 1 个颜色,无法产生渐变效果'linear-gradient(180deg, #e94560)'
  1. 多行渐变字符串:支持换行符
// 正确 - 多行字符串也能正常解析`linear-gradient(50% 0 50% 100%, rgba(36,159,218,0) 1, rgba(36,159,218,0.8) 0)`
  1. 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

filter 滤镜

支持 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 自定义虚线

通过 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}

borderRadius 四角独立圆角

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}}});

globalCompositeOperation 混合模式

支持 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'}});

clipPath 裁剪路径

传入一个图形控件实例作为裁剪区域:

// 创建裁剪区域(圆形)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}});

🎯 内置图形

矩形 (Rect)

constrect=g.createShape('rect',{style: style,position: {x: 100,y: 100},width: 100,height: 100});

圆形/椭圆 (Arc)

constarc=g.createShape('arc',{style: style,center: {x: 100,y: 150},width: 120,height: 80});

线条 (Line)

constline=g.createLine({x: 10,y: 200},{x: 80,y: 120},style);

箭头 (Arrow)

constarrow=g.createShape('arrow',{style: style,start: {x: 150,y: 120},end: {x: 160,y: 150}});

贝塞尔曲线 (Bezier)

constbezier=g.createShape('bezier',{style: style,points: [p0,p1,p2,p3,p4]});

图片 (Image)

constimg=g.createShape('image',{style: {src: 'image.png'},position: {x: 100,y: 100}});img.canMove(true);

文字 (Label)

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});

椭圆 (Ellipse)

constellipse=g.createShape('ellipse',{style: style,center: {x: 100,y: 150},width: 120,height: 80});

多边形 (Polygon)

constpolygon=g.createShape('polygon',{style: style,center: {x: 100,y: 150},sides: 6,// 边数radius: 50// 半径});

星形 (Star)

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

// 导出为 PNG 图片// fileName 文件名// format 图片格式,默认为 image/png// quality 图片质量,0-1之间g.exportToPNG('my-graph','image/png',0.9);

导出为 JPEG

// 导出为 JPEG 图片// fileName 文件名// quality 图片质量,0-1之间g.exportToJPEG('my-graph',0.8);

导出为 SVG

// 导出为 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 build

运行示例

npm run dev

🤝 贡献

欢迎贡献代码!请遵循以下步骤:

  1. Fork 本仓库
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启 Pull Request

📄 许可证

本项目采用 MIT 许可证。

💬 讨论

🙏 致谢

感谢所有为本项目做出贡献的开发者!

🔗 相关项目

📮 联系方式


如果这个项目对你有帮助,请给个 ⭐️ Star!

About

基于CANVAS的简单画图组件, 像写dom对象一样在canvas上画图

Topics

Resources

Stars

88 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages