You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:03:25