写在开始
有了第一次的vibe coding 实践Vibe Coding 初体验:从对话模型到 RAG,不得不承认,现在的AI编程能力确实已经非常强大了。对比一年前,AI写出来的代码,还是会有报错,需要手动去修bug等情况,但现在已经几乎不需要人为干预了。
那无需多言,继续vibe coding之路。刚好想起多年前写的一个demo项目(回顾: HTML5 Canvas 实现K线图),当时只实现了非常简单基础的一版demo,在架构、功能、性能以及兼容等方面,还有很多未实现的想法,正好可以拿来做vibe coding实验。
这次的思路还是让AI先出规划文档,再实现代码逻辑。本次规划分为短期和长期两部分,本文先实现短期目标。
本文中涉及到的更新,对应github仓库地址
commit。
项目源码地址: 传送门
效果图
出发点:为什么要拆「短期目标」
手里的 render-kline 最初是典型的「单 Canvas + 全量重绘」演示代码:能画蜡烛、能拖、能滚轮缩放,但若往上叠成交量、均线、十字线高频刷新,很快会遇到三件烦心事------
- 数据形态不统一 :接口常返回字符串 OHLC、字段名各异,渲染层到处
[open_price],一改接口就崩一半。 - 渲染与交互耦合:鼠标一动就整屏重画,十字线和 K 线抢同一块画布,性能和代码结构都不讨好。
- 移动端缺席 :没有统一的指针模型,
touch-action不管好,页面滚动还会和拖拽打架。
短期文档把它拆成 A~F 六个阶段 ,顺序刻意设计成:先数据与分层,再业务功能(量、均线),再多端,最后生命周期。下面按真实迭代顺序回顾------它和文档条目基本一致,中间也踩过几个「工程向」的坑。
阶段 A:先把内存里的 K 线「洗」干净
做了什么 :新增 src/model/normalize-bar.js,约定内部结构 { time, open, high, low, close, volume } 全为 number,时间在 normalize 里统一到 Unix 秒 ,成交量兼容 vol / volume。updateHistoryQuote、实时合并 pushQuoteInData 入口处只做一件事:入库前必须规范化。
顺手收益:后续副图成交量、MA 计算都只面对数值,不会再出现字符串比较大小时的边角 bug。
小结:这一步几乎是强制前置------哪怕暂不拆 Canvas,也 worth 做。
阶段 B:分层 Canvas + 调度器,十字线终于不必拖累主图
做了什么:
index.js里用.kline-root包四层:grid / main / sub / overlay,顶层 overlay 负责命中与十字线。src/layers/resize-layers.js:ResizeObserver + 防抖 ,统一按容器与sharpness改缓冲区分辨率;避免「每次 mousemove 改canvas.width」那种隐性清空。src/scheduler.js:markDirty(FULL | OVERLAY)+requestAnimationFrame合并;mousemove 只刷 overlay。core.js拆drawGridLayer/drawMainLayer/drawSubLayer(起初副图为占位)等。
副图那一截白条是什么意思 :当时先在布局上切出 SUB_CHART_HEIGHT_RATIO(默认 20%)给「将来的副图」,主图price 映射改用 mainPlotHeight。文档里专门写过对照说明------避免同事误以为「分层做完了成交量就一定有了」。
阶段 C:成交量------验证「分层 + 数据」是否真的对齐
做了什么:
draw-tools.js增加computeVolumeScale:与价格可视切片同一套索引区间,取maxVolumeVisible,把柱高映射到subPlotHeight(留少量底边距)。core.js的drawSubLayer:与蜡烛相同的margin循环与可视剔除,涨跌色默认跟buyColor/sellColor,也可配volumeUpColor/volumeDownColor,showVolume可关。
验收感 :拖动画布、缩放后,若柱子和蜡烛在 x 上「一起晃」且不漂移,说明 movingRange 与坐标变换只在一条链路上维护------这点比「柱图画出来」更重要。
阶段 D:均线与注册表------给后面 MACD 留挂钩
做了什么:
src/indicators/ma.js:computeMA(index 0 为最新一根时的滑动窗口语义)+drawMAOnMain。src/indicators/registry.js:registerIndicator/runComputeIndicators/runDrawMainIndicators,当前内置ma,drawSub预留给以后副图指标。drawFull里:computeSpaceY之后跑 compute,主图蜡烛之后跑 draw。
取舍 :首版均线轴与主图共用 min/max,不单独做「均线专用纵轴」------文档里也写明这是刻意的简单策略,后面要再上开关。
阶段 E:Pointer Events 一统桌面与触摸
做了什么 :用 attachChartInteractions 替换原先分散的 mouse 监听;现代浏览器走 Pointer Events,touch-action: none + user-select: none 铺在命中层;单指拖拽、scaleKLine 离散步进的双指 pinch、滚轮逻辑保留;老环境降级 mouse + touch。
真实踩坑 :早期若静态站根目录设错,demo-data.js 的路径会 404;以及 浏览器原生 ES Module 要求 import 路径带 .js 后缀,Rollup 时代养成的省略扩展名习惯要改一改。
阶段 F:能卸载,才敢嵌进 SPA
做了什么:
destroy():disconnectResizeObserver、取消待执行 rAF(cancelScheduledDraw)、detachChartInteractions(滚轮与各指针监听可对称移除)、拆掉 loading 的span与layerRoot。addWheelListener改为返回卸载函数,与指针清理一并收回。- README 补全 options、演示说明;
npm run dev提供 rollup watch。
到这里,短期路线图里的主干能力才算「可交付」:不是只能 demo,而是能想象它被挂进页面、再被拆掉。
回过头看:若重来一遍会坚持的三件事
- 数据先入轨(阶段 A):晚做一天,后面副图和指标都可能返工。
- 分层与调度先于复杂指标:否则 MA、成交量会在「一张画布」里越缠越乱。
- 交互一开始就想清卸载 :Pointer + wheel + ResizeObserver,若不集中注册、不集中 teardown,
destroy一定会漏。
还未做的「增强项」(文档里也已点名)
- 主图平移时的 位图块平移 + 补边(极致性能向)。
- 副图 纵轴刻度 ,以及十字线在副图区 联动显示的成交量读数(柱状图已落地;读数与刻度仍可增强)。
- 单元测试(normalize、实时合并、视口切片)。
它们不影响我们已经兑现的短期主线:分层、局部刷新、量、均线、触摸、destroy、文档与开发体验。
结语
这一轮迭代的本质,并不是「多画了几样东西」,而是把渲染拆成 数据 → 调度 → 分层绘制 → 可卸载交互 四条清晰边界。render-kline 仍离商用终端很远,但作为 自研 K 线内核的 1.x 脚手架,它已经足够支撑下一阶段:更多副图指标、自定义脚本、或 WebGL 升级选型------而那些决策,最好建立在当前这套边界之上,而不是回到单 Canvas 里继续堆逻辑。
再次感叹,AI确实太强大了,几年前的一个小demo,由于是初学canvas,花了得有一周多的时间,感觉拿到现在用AI编程来做,最多半天,即可实现一个规划更清晰、性能更好、功能更丰富的产品,关键是可以不会canvas,甚至都不用会编程,气不气人。
vive coding 好啊!得学啊!
