Vibe Coding 第二弹:做一个 Canvas K线图

写在开始

有了第一次的vibe coding 实践Vibe Coding 初体验:从对话模型到 RAG,不得不承认,现在的AI编程能力确实已经非常强大了。对比一年前,AI写出来的代码,还是会有报错,需要手动去修bug等情况,但现在已经几乎不需要人为干预了。

那无需多言,继续vibe coding之路。刚好想起多年前写的一个demo项目(回顾: HTML5 Canvas 实现K线图),当时只实现了非常简单基础的一版demo,在架构、功能、性能以及兼容等方面,还有很多未实现的想法,正好可以拿来做vibe coding实验。

这次的思路还是让AI先出规划文档,再实现代码逻辑。本次规划分为短期和长期两部分,本文先实现短期目标。

本文中涉及到的更新,对应github仓库地址 commit

项目源码地址: 传送门

效果图

出发点:为什么要拆「短期目标」

手里的 render-kline 最初是典型的「单 Canvas + 全量重绘」演示代码:能画蜡烛、能拖、能滚轮缩放,但若往上叠成交量、均线、十字线高频刷新,很快会遇到三件烦心事------

  1. 数据形态不统一 :接口常返回字符串 OHLC、字段名各异,渲染层到处 [open_price],一改接口就崩一半。
  2. 渲染与交互耦合:鼠标一动就整屏重画,十字线和 K 线抢同一块画布,性能和代码结构都不讨好。
  3. 移动端缺席 :没有统一的指针模型,touch-action 不管好,页面滚动还会和拖拽打架。

短期文档把它拆成 A~F 六个阶段 ,顺序刻意设计成:先数据与分层,再业务功能(量、均线),再多端,最后生命周期。下面按真实迭代顺序回顾------它和文档条目基本一致,中间也踩过几个「工程向」的坑。


阶段 A:先把内存里的 K 线「洗」干净

做了什么 :新增 src/model/normalize-bar.js,约定内部结构 { time, open, high, low, close, volume } 全为 number,时间在 normalize 里统一到 Unix 秒 ,成交量兼容 vol / volumeupdateHistoryQuote、实时合并 pushQuoteInData 入口处只做一件事:入库前必须规范化

顺手收益:后续副图成交量、MA 计算都只面对数值,不会再出现字符串比较大小时的边角 bug。

小结:这一步几乎是强制前置------哪怕暂不拆 Canvas,也 worth 做。


阶段 B:分层 Canvas + 调度器,十字线终于不必拖累主图

做了什么

  • index.js 里用 .kline-root 包四层:grid / main / sub / overlay,顶层 overlay 负责命中与十字线。
  • src/layers/resize-layers.jsResizeObserver + 防抖 ,统一按容器与 sharpness 改缓冲区分辨率;避免「每次 mousemove 改 canvas.width」那种隐性清空。
  • src/scheduler.jsmarkDirty(FULL | OVERLAY) + requestAnimationFrame 合并;mousemove 只刷 overlay。
  • core.jsdrawGridLayer / drawMainLayer / drawSubLayer(起初副图为占位)等。

副图那一截白条是什么意思 :当时先在布局上切出 SUB_CHART_HEIGHT_RATIO(默认 20%)给「将来的副图」,主图price 映射改用 mainPlotHeight。文档里专门写过对照说明------避免同事误以为「分层做完了成交量就一定有了」。


阶段 C:成交量------验证「分层 + 数据」是否真的对齐

做了什么

  • draw-tools.js 增加 computeVolumeScale :与价格可视切片同一套索引区间,取 maxVolumeVisible ,把柱高映射到 subPlotHeight(留少量底边距)。
  • core.jsdrawSubLayer :与蜡烛相同的 margin 循环与可视剔除,涨跌色默认跟 buyColor/sellColor,也可配 volumeUpColor / volumeDownColorshowVolume 可关。

验收感 :拖动画布、缩放后,若柱子和蜡烛在 x 上「一起晃」且不漂移,说明 movingRange 与坐标变换只在一条链路上维护------这点比「柱图画出来」更重要。


阶段 D:均线与注册表------给后面 MACD 留挂钩

做了什么

  • src/indicators/ma.jscomputeMA(index 0 为最新一根时的滑动窗口语义)+ drawMAOnMain
  • src/indicators/registry.jsregisterIndicator / runComputeIndicators / runDrawMainIndicators ,当前内置 madrawSub 预留给以后副图指标。
  • 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 的 spanlayerRoot
  • addWheelListener 改为返回卸载函数,与指针清理一并收回。
  • README 补全 options、演示说明;npm run dev 提供 rollup watch。

到这里,短期路线图里的主干能力才算「可交付」:不是只能 demo,而是能想象它被挂进页面、再被拆掉


回过头看:若重来一遍会坚持的三件事

  1. 数据先入轨(阶段 A):晚做一天,后面副图和指标都可能返工。
  2. 分层与调度先于复杂指标:否则 MA、成交量会在「一张画布」里越缠越乱。
  3. 交互一开始就想清卸载 :Pointer + wheel + ResizeObserver,若不集中注册、不集中 teardown,destroy 一定会漏。

还未做的「增强项」(文档里也已点名)

  • 主图平移时的 位图块平移 + 补边(极致性能向)。
  • 副图 纵轴刻度 ,以及十字线在副图区 联动显示的成交量读数(柱状图已落地;读数与刻度仍可增强)。
  • 单元测试(normalize、实时合并、视口切片)。

它们不影响我们已经兑现的短期主线:分层、局部刷新、量、均线、触摸、destroy、文档与开发体验。


结语

这一轮迭代的本质,并不是「多画了几样东西」,而是把渲染拆成 数据 → 调度 → 分层绘制 → 可卸载交互 四条清晰边界。render-kline 仍离商用终端很远,但作为 自研 K 线内核的 1.x 脚手架,它已经足够支撑下一阶段:更多副图指标、自定义脚本、或 WebGL 升级选型------而那些决策,最好建立在当前这套边界之上,而不是回到单 Canvas 里继续堆逻辑。

再次感叹,AI确实太强大了,几年前的一个小demo,由于是初学canvas,花了得有一周多的时间,感觉拿到现在用AI编程来做,最多半天,即可实现一个规划更清晰、性能更好、功能更丰富的产品,关键是可以不会canvas,甚至都不用会编程,气不气人。

vive coding 好啊!得学啊!

相关推荐
卷帘依旧1 小时前
Vue 响应式原理:Object.defineProperty vs Proxy 深度对比
前端·vue.js
yqcoder2 小时前
原生 AJAX 揭秘:如何使用 XHR 发起请求
前端·ajax·okhttp
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_34:(深入XML 中的 CDATASection 接口)
xml·前端·html·html5·媒体
之歆2 小时前
DAY_20JavaScript 条件语句与循环结构深度学习(二)
前端·javascript
山北雨夜漫步2 小时前
LangGraph
java·前端·算法
漓漾li2 小时前
每日面试题-前端
前端·react.js·面试
布局呆星2 小时前
Vue3 路由守卫详解:全局守卫、路由独享守卫、组件内守卫
前端·javascript·vue.js
小李子呢02112 小时前
前端八股Vue---ref操作 DOM 元素或组件,调用子组件方法
前端·javascript·vue.js
Yoram2 小时前
Vue3 响应性:跨上下文的传递、转换与作用域控制
前端·vue.js