Canvas绘制递归图案被截断 如何计算所需画布尺寸
Canvas递归图案截断问题解决方案
完全可以通过预计算图案的边界范围,得到能完整容纳所有绘制内容的画布尺寸,不需要依赖窗口大小做猜测。
问题原因
当前代码直接将画布尺寸设置为窗口宽高,没有提前计算递归生成的分形图案所有分支的伸展范围,一旦分支延伸到窗口区域外就会被画布截断。
实现逻辑
核心思路是在正式绘图前,先跑一遍和绘图逻辑完全一致的递归遍历,只记录所有线段端点的坐标极值,得到整个图案的外接矩形:
- 遍历过程中持续更新所有点的X坐标最小值、X坐标最大值、Y坐标最小值、Y坐标最大值
- 画布实际宽度 = X最大值 - X最小值,画布实际高度 = Y最大值 - Y最小值
- 正式绘图时给所有坐标统一加偏移量(X方向偏移
-X最小值,Y方向偏移-Y最小值),让整个图案刚好落在画布范围内,既不会被截断,也不会有多余留白
修正后可直接运行的代码
JavaScript部分
const beginLength = 600; const minLength = 1; let alpha = 90; let beta = -90; const ratioA = 0.7; const ratioB = 0.7; // 角度转弧度 alpha = alpha / 180 * Math.PI; beta = beta / 180 * Math.PI; // 初始化边界极值,预计算阶段临时把根点设为(0,0) let minX = 0, maxX = 0, minY = 0, maxY = 0; /** * 递归遍历所有分支,仅计算图案边界,不执行绘图 */ function calcBoundary(rootX, rootY, r, theta) { const endX = rootX + r * Math.cos(theta); const endY = rootY + r * Math.sin(theta); // 更新四个方向的边界值 minX = Math.min(minX, rootX, endX); maxX = Math.max(maxX, rootX, endX); minY = Math.min(minY, rootY, endY); maxY = Math.max(maxY, rootY, endY); // 递归终止条件和绘图逻辑完全一致 if (r * ratioA >= minLength && r * ratioB >= minLength) { calcBoundary(endX, endY, r * ratioA, theta + alpha); calcBoundary(endX, endY, r * ratioB, theta + beta); } } // 执行边界计算,初始方向为垂直向上 calcBoundary(0, 0, beginLength, Math.PI * 1.5); // 计算画布实际尺寸和坐标偏移量 const canvasWidth = maxX - minX; const canvasHeight = maxY - minY; const offsetX = -minX; const offsetY = -minY; // 初始化画布 const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); canvas.width = canvasWidth; canvas.height = canvasHeight; /** * 正式绘图函数 */ function drawLine(rootX, rootY, r, theta) { const endX = rootX + r * Math.cos(theta); const endY = rootY + r * Math.sin(theta); ctx.beginPath(); // 所有坐标加偏移量,对齐到画布可视区域 ctx.moveTo(rootX + offsetX, rootY + offsetY); ctx.lineTo(endX + offsetX, endY + offsetY); ctx.strokeStyle = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`; ctx.stroke(); if (r * ratioA >= minLength && r * ratioB >= minLength) { drawLine(endX, endY, r * ratioA, theta + alpha); drawLine(endX, endY, r * ratioB, theta + beta); } } // 启动绘图 drawLine(0, 0, beginLength, Math.PI * 1.5);
HTML部分
<!DOCTYPE HTML> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> /* 清除默认边距避免画布偏移 */ body { margin: 0; padding: 0; } canvas { display: block; image-rendering: pixelated; } </style> </head> <body> <canvas id="canvas"></canvas> <!-- 上述JS代码可直接写在这里或引入外部JS文件 --> </body> </html>
优化提示
- 预计算遍历的时间复杂度和实际绘图完全一致,在当前递归终止条件下性能开销极低,不会造成页面卡顿
- 后续如果调整分支角度、长度衰减比例、初始线段长度,不需要手动修改画布尺寸,代码会自动适配新的图案边界
- 如果需要图案四周留均匀白边,可以在计算完画布宽高后给宽高各加2040像素,同时把偏移量各加1020像素即可
内容的提问来源于stack exchange,提问作者Dennis
相关产品推荐
相关产品推荐

