可行,需自行处理时间刻度映射、任务条dom定位和拖拽实时校准;推荐固定单位像素映射(如1小时=40px),用date统一管理时间,flex渲染时间轴,拖拽时记录鼠标偏移并禁用文本选中,拖拽后立即更新数据并校验同资源时间冲突。

用原生 HTML + JavaScript 实现可拖拽甘特图,关键在时间轴对齐和 DOM 重绘控制
纯前端实现可编辑甘特图,不依赖 Chart.js 或 anychart 等重型库是可行的,但必须自己处理三个核心:时间刻度映射、任务条 DOM 定位、拖拽过程中的实时位置校准。直接用 position: absolute + 百分比宽度会因四舍五入导致任务条错位,尤其在缩放或滚动时明显。
推荐做法是:固定时间轴单位(如 1 小时 = 40px),所有任务条用 left 和 width 以像素为单位计算,避免百分比漂移。时间范围建议用 Date 对象统一管理起止,不要用字符串拼接或 moment 格式化后再解析——容易引入时区偏差。
- 时间轴容器设
overflow-x: auto,内部用display: flex渲染日期列,每列宽固定(如min-width: 120px) - 每个任务条用
div,class="gantt-bar",初始left和width由startDate和endDate按像素比例算出 - 拖拽开始时记录鼠标相对于任务条左边缘的偏移(
clientX - bar.getBoundingClientRect().left),而非直接绑定bar.style.left - 拖拽中禁用默认文本选中:
document.onselectstart = () => false;拖拽结束立即恢复
任务条拖拽更新后,如何同步修改 underlying 数据并防止时间冲突
用户拖动一个任务条,不只是视觉位移,必须立刻更新它对应的 task.startDate 和 task.endDate,否则保存时数据还是旧的。更关键的是:不能让用户把任务拖到资源已被占用的时间段里——这需要做排期校验,而不是等点击“保存”才报错。
简单有效的校验策略是:拖拽结束瞬间,遍历其他同资源(如 task.resourceId === 'dev-1')的任务,检查时间区间是否重叠。注意重叠判断要包含边界相接(如 A 结束于 10:00,B 开始于 10:00 应视为允许;但若要求严格间隔,则需加 1 分钟缓冲)。
- 用
Math.max(aStart, bStart) 判断重叠(返回 <code>true表示冲突) - 冲突时,立即将任务条
style.transform = 'scale(0.95)'并加border: 2px dashed #f33'提示,同时还原startDate/endDate到拖拽前值 - 不要在拖拽过程中实时校验——性能差且 UI 卡顿;只在校准后的最终位置校验一次
- 如果支持跨天任务,注意
Date对象比较前确保时区一致,建议全部转为 UTC 时间戳再运算
缩放时间粒度(日/小时/半小时)时,DOM 重绘卡顿?用虚拟滚动+节流渲染
当时间轴从「按天」切到「按半小时」,列数可能从 30 激增到 720,一次性生成 DOM 会导致页面冻结。这不是 CSS 能解决的问题,得从渲染策略下手。
真实项目中应启用「可视区域渲染」:只生成当前滚动窗口左右各 2–3 天(或 10 个时间单元)的列,其余用占位 div 维持高度。监听 scroll 和 resize 事件,用 requestAnimationFrame 节流触发重绘,避免连续触发。
- 时间轴列容器设
height: 40px(固定行高),所有列用flex-shrink: 0防止压缩 - 用
getBoundingClientRect()获取可视区域top/bottom,结合每列像素高度反推起始列索引 - 任务条也按同样逻辑做懒加载:只渲染在可视时间范围内(
task.endDate > visibleStart && task.startDate )的任务 - 缩放切换时,先清空 DOM 再批量插入,不要逐个
appendChild;用DocumentFragment收集后一次性挂载
导出为 PNG 或 PDF 时,甘特图样式丢失?用 html2canvas + 自定义 canvas 绘制逻辑
html2canvas 对绝对定位、transform、CSS 变量支持不稳定,直接截图常出现任务条错位、文字模糊或时间轴断裂。与其强依赖它,不如在导出时切换为 canvas 绘制模式。
思路是:保留原始任务数据,导出触发时新建 canvas,用 ctx.fillRect 画任务条,ctx.fillText 写文字,所有坐标按相同像素比例换算。这样可控性强,还能加水印、调整 DPI。
- 导出前先调用
window.print()的替代方案不可取——打印样式难控制,且无法自动下载 - canvas 宽度按时间轴总像素计算:
totalHours * pixelPerHour + leftMargin,高度 = 行高 × 任务数 + 表头高 - 文字渲染用
ctx.font = '12px sans-serif',避免系统字体缺失;中文需提前检测ctx.measureText是否返回 0 - 导出 PDF 推荐用
jsPDF+addImage,传入 canvas 的toDataURL('image/png'),不要用html()方法
时间轴刻度对齐、拖拽精度、缩放重绘、导出一致性——这四个点任一没控好,用户就会觉得“像是能用,但总差点意思”。尤其是多任务并发拖拽时的冲突判定逻辑,很容易被当成 UI 问题掩盖掉底层数据不同步的事实。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











