[{"data":1,"prerenderedAt":402},["ShallowReactive",2],{"blog-canvas-random-tree":3},{"id":4,"title":5,"body":6,"category":387,"date":388,"description":389,"draft":390,"extension":391,"meta":392,"navigation":393,"path":394,"seo":395,"stem":396,"tags":397,"__hash__":401},"blog\u002Fblog\u002Fcanvas-random-tree.md","Canvas 递归绘制随机分形树",{"type":7,"value":8,"toc":377},"minimark",[9,13,17,21,26,33,60,68,72,87,238,242,256,325,329,344,347,356,359,362,373],[10,11,5],"h1",{"id":12},"canvas-递归绘制随机分形树",[14,15,16],"p",{},"分形树是 Canvas 入门的经典练习：从树干出发，每画一节枝条就递归分叉，直到深度耗尽。加上伪随机种子，每次点击都能生成形态各异但风格统一的树。下面 demo 支持点击换树、窗口缩放高清重绘。",[18,19,20],"h2",{"id":20},"核心链路",[22,23,25],"h3",{"id":24},"_1-递归分形depth-驱动一切","1. 递归分形：depth 驱动一切",[14,27,28,32],{},[29,30,31],"code",{},"drawBranch(x, y, len, angle, depth, maxDepth)"," 是核心函数：",[34,35,36,44,47,54],"ol",{},[37,38,39,40,43],"li",{},"画当前枝段（线宽由 ",[29,41,42],{},"depth \u002F maxDepth"," 的平方决定，主干粗、末梢细）",[37,45,46],{},"按深度决定分叉数量：主干区 2～3 叉，末梢 0～1 叉",[37,48,49,50,53],{},"子枝长度乘以 ",[29,51,52],{},"0.63～0.78"," 的随机缩放，逐层缩短",[37,55,56,59],{},[29,57,58],{},"depth \u003C= 2"," 时在末梢稀疏开花",[14,61,62,63,67],{},"较粗的枝段还有概率从",[64,65,66],"strong",{},"线段中途","再分叉，让树形更自然。",[22,69,71],{"id":70},"_2-角度约束树枝只朝上半平面","2. 角度约束：树枝只朝上半平面",[14,73,74,75,78,79,82,83,86],{},"Canvas 的 Y 轴向下，所以 ",[29,76,77],{},"sin(angle) \u003C= 0"," 表示朝上。",[29,80,81],{},"clampAngleUpward"," 把角度限制在 ",[29,84,85],{},"[-π, 0]","，避免枝条插进地面：",[88,89,94],"pre",{"className":90,"code":91,"language":92,"meta":93,"style":93},"language-javascript shiki shiki-themes github-light github-dark","function clampAngleUpward(angle) {\n  let a = angle\n  while (a \u003C= -Math.PI) a += Math.PI * 2\n  while (a > Math.PI) a -= Math.PI * 2\n  if (a > 0) a = -a\n  return a\n}\n","javascript","",[29,95,96,120,135,174,201,223,232],{"__ignoreMap":93},[97,98,101,105,109,113,117],"span",{"class":99,"line":100},"line",1,[97,102,104],{"class":103},"szBVR","function",[97,106,108],{"class":107},"sScJk"," clampAngleUpward",[97,110,112],{"class":111},"sVt8B","(",[97,114,116],{"class":115},"s4XuR","angle",[97,118,119],{"class":111},") {\n",[97,121,123,126,129,132],{"class":99,"line":122},2,[97,124,125],{"class":103},"  let",[97,127,128],{"class":111}," a ",[97,130,131],{"class":103},"=",[97,133,134],{"class":111}," angle\n",[97,136,138,141,144,147,150,153,157,160,163,166,168,171],{"class":99,"line":137},3,[97,139,140],{"class":103},"  while",[97,142,143],{"class":111}," (a ",[97,145,146],{"class":103},"\u003C=",[97,148,149],{"class":103}," -",[97,151,152],{"class":111},"Math.",[97,154,156],{"class":155},"sj4cs","PI",[97,158,159],{"class":111},") a ",[97,161,162],{"class":103},"+=",[97,164,165],{"class":111}," Math.",[97,167,156],{"class":155},[97,169,170],{"class":103}," *",[97,172,173],{"class":155}," 2\n",[97,175,177,179,181,184,186,188,190,193,195,197,199],{"class":99,"line":176},4,[97,178,140],{"class":103},[97,180,143],{"class":111},[97,182,183],{"class":103},">",[97,185,165],{"class":111},[97,187,156],{"class":155},[97,189,159],{"class":111},[97,191,192],{"class":103},"-=",[97,194,165],{"class":111},[97,196,156],{"class":155},[97,198,170],{"class":103},[97,200,173],{"class":155},[97,202,204,207,209,211,214,216,218,220],{"class":99,"line":203},5,[97,205,206],{"class":103},"  if",[97,208,143],{"class":111},[97,210,183],{"class":103},[97,212,213],{"class":155}," 0",[97,215,159],{"class":111},[97,217,131],{"class":103},[97,219,149],{"class":103},[97,221,222],{"class":111},"a\n",[97,224,226,229],{"class":99,"line":225},6,[97,227,228],{"class":103},"  return",[97,230,231],{"class":111}," a\n",[97,233,235],{"class":99,"line":234},7,[97,236,237],{"class":111},"}\n",[22,239,241],{"id":240},"_3-lcg-伪随机可复现的随机","3. LCG 伪随机：可复现的「随机」",[14,243,244,245,248,249,252,253,255],{},"用线性同余生成器（LCG）代替 ",[29,246,247],{},"Math.random()","，同一 ",[29,250,251],{},"seed"," 下每次绘制结果一致；点击画布时重置 ",[29,254,251],{},"，得到新树：",[88,257,259],{"className":90,"code":258,"language":92,"meta":93,"style":93},"function rand() {\n  seed = (seed * 16807 + 0) % 2147483647\n  return (seed - 1) \u002F 2147483646\n}\n",[29,260,261,271,301,321],{"__ignoreMap":93},[97,262,263,265,268],{"class":99,"line":100},[97,264,104],{"class":103},[97,266,267],{"class":107}," rand",[97,269,270],{"class":111},"() {\n",[97,272,273,276,278,281,284,287,290,292,295,298],{"class":99,"line":122},[97,274,275],{"class":111},"  seed ",[97,277,131],{"class":103},[97,279,280],{"class":111}," (seed ",[97,282,283],{"class":103},"*",[97,285,286],{"class":155}," 16807",[97,288,289],{"class":103}," +",[97,291,213],{"class":155},[97,293,294],{"class":111},") ",[97,296,297],{"class":103},"%",[97,299,300],{"class":155}," 2147483647\n",[97,302,303,305,307,310,313,315,318],{"class":99,"line":137},[97,304,228],{"class":103},[97,306,280],{"class":111},[97,308,309],{"class":103},"-",[97,311,312],{"class":155}," 1",[97,314,294],{"class":111},[97,316,317],{"class":103},"\u002F",[97,319,320],{"class":155}," 2147483646\n",[97,322,323],{"class":99,"line":176},[97,324,237],{"class":111},[22,326,328],{"id":327},"_4-devicepixelratio-高清适配","4. devicePixelRatio 高清适配",[14,330,331,332,335,336,339,340,343],{},"设置 ",[29,333,334],{},"canvas.width = cssWidth * dpr","，再用 ",[29,337,338],{},"ctx.setTransform(dpr, 0, 0, dpr, 0, 0)"," 缩放坐标系，Retina 屏上线条不会发虚。",[29,341,342],{},"resize"," 时按容器尺寸重算并整棵重绘。",[18,345,346],{"id":346},"在线体验",[14,348,349,350,355],{},"点击下方画布可重新生成一棵树。也可 ",[351,352,354],"a",{"href":353},"\u002Fdemos\u002Frandom-tree","全屏预览"," 打开独立页面。",[357,358],"random-tree-demo",{},[18,360,361],{"id":361},"小结",[14,363,364,365,368,369,372],{},"随机分形树的套路很固定：",[64,366,367],{},"递归 + 深度递减 + 随机参数","。难点在于细节——线宽平滑过渡、中途分叉、角度约束、末梢开花，这些让「算法树」看起来像「自然树」。如果你要做自己的生成艺术，可以从固定 ",[29,370,371],{},"maxDepth"," 和单一分叉规则开始，再逐步加随机性和装饰。",[374,375,376],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":93,"searchDepth":137,"depth":137,"links":378},[379,385,386],{"id":20,"depth":122,"text":20,"children":380},[381,382,383,384],{"id":24,"depth":137,"text":25},{"id":70,"depth":137,"text":71},{"id":240,"depth":137,"text":241},{"id":327,"depth":137,"text":328},{"id":346,"depth":122,"text":346},{"id":361,"depth":122,"text":361},"frontend","2026-06-10","用 Canvas 递归分形画随机树，LCG 伪随机保证可复现，点击换一棵，并适配高清屏 DPR。",false,"md",{},true,"\u002Fblog\u002Fcanvas-random-tree",{"title":5,"description":389},"blog\u002Fcanvas-random-tree",[398,399,400],"canvas","recursion","generative-art","aFdlBHe97GxQsBjnQGInjuLSEwD6EV7SRTsDTScAShA",1782309645709]