使用函数式封装绘制科赫雪花(Koch Snowflake)

科赫雪花(Koch Snowflake)是经典的分形几何图形,由 Helge von Koch 在 1904 年提出。它通过不断递归生成边上的"锯齿",形成复杂而美丽的雪花形状。本文将介绍如何用 函数式编程封装科赫雪花生成与绘制,方便在 Canvas 上渲染,同时可封装成可复用的模块。


1. 算法原理

科赫雪花的生成规则非常直观:

  1. 从一个等边三角形开始。
  2. 将每条边分为三等份,并在中间段向外凸出一个等边小三角形。
  3. 对生成的新边重复步骤 2,递归指定层数。
  4. 最终得到雪花状的复杂轮廓。

特点:

  • 每增加一层递归,边数呈指数增长。
  • 轮廓越来越复杂,但数学公式可精确描述。

2. 函数式封装设计

在传统实现中,可能使用类或对象存储状态。函数式封装的思路是 只依赖纯函数和数组操作,保证雪花点集可重复使用,同时避免副作用。

核心函数

  • koch(p1, p2, iter):递归生成科赫曲线上的点
  • createKochSnowflake(size, iterations):生成雪花点集和绘制函数
  • draw(ctx, x, y, strokeStyle, lineWidth):将雪花绘制到 Canvas

3. 完整函数实现

javascript 复制代码
export const createKochSnowflake = function(size = 200, iterations = 4) {
  // 参数校验
  if (typeof size !== "number" || size <= 0) {
    throw new Error("[KochSnowflake] 参数错误: size 必须是大于 0 的数字");
  }
  if (!Number.isInteger(iterations) || iterations < 0) {
    throw new Error("[KochSnowflake] 参数错误: iterations 必须是大于等于 0 的整数");
  }

  // 科赫递归函数
  const koch = (p1, p2, iter) => iter === 0
    ? [p1, p2]
    : (() => {
        const dx = (p2.x - p1.x) / 3;
        const dy = (p2.y - p1.y) / 3;
        const pa = {x: p1.x + dx, y: p1.y + dy};
        const pb = {x: p1.x + 2*dx, y: p1.y + 2*dy};
        const angle = Math.PI / 3;
        const peak = {
          x: pa.x + Math.cos(angle) * (pb.x - pa.x) - Math.sin(angle) * (pb.y - pa.y),
          y: pa.y + Math.sin(angle) * (pb.x - pa.x) + Math.cos(angle) * (pb.y - pa.y)
        };
        return [
          ...koch(p1, pa, iter - 1).slice(0, -1),
          ...koch(pa, peak, iter - 1).slice(0, -1),
          ...koch(peak, pb, iter - 1).slice(0, -1),
          ...koch(pb, p2, iter - 1)
        ];
      })();

  // 初始等边三角形(以原点为中心)
  const h = size * Math.sqrt(3) / 2;
  const triangle = [
    {x: -size/2, y: h/3},
    {x: size/2, y: h/3},
    {x: 0, y: -2*h/3}
  ];

  // 生成雪花点集
  const points = triangle.flatMap((p, i) =>
    koch(p, triangle[(i+1)%3], iterations).slice(0, -1)
  ).concat([triangle[0]]);

  // 返回雪花句柄
  return {
    points,

    /**
     * 绘制雪花
     * @param {CanvasRenderingContext2D} ctx - Canvas 上下文
     * @param {number} x - 中心 X 坐标
     * @param {number} y - 中心 Y 坐标
     * @param {string} strokeStyle - 线条颜色
     * @param {number} lineWidth - 线宽
     */
    draw(ctx, x, y, strokeStyle = "white", lineWidth = 2) {
      if (!ctx || typeof ctx.moveTo !== "function") {
        throw new Error("[KochSnowflake] ctx 必须是 CanvasRenderingContext2D");
      }
      if (typeof x !== "number" || typeof y !== "number") {
        throw new Error("[KochSnowflake] x, y 必须是数字");
      }
      if (typeof strokeStyle !== "string") {
        throw new Error("[KochSnowflake] strokeStyle 必须是字符串");
      }
      if (typeof lineWidth !== "number" || lineWidth <= 0) {
        throw new Error("[KochSnowflake] lineWidth 必须大于 0");
      }

      ctx.beginPath();
      ctx.moveTo(points[0].x + x, points[0].y + y);
      points.slice(1).forEach(p => ctx.lineTo(p.x + x, p.y + y));
      ctx.closePath();
      ctx.strokeStyle = strokeStyle;
      ctx.lineWidth = lineWidth;
      ctx.stroke();
    }
  };
};

4. 使用示例

ini 复制代码
<canvas id="canvas" width="500" height="500"></canvas>
<script type="module">
import { createKochSnowflake } from './koch-snowflake.js';

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

// 创建雪花句柄
const snowflake = createKochSnowflake(150, 4);

// 绘制在不同位置
snowflake.draw(ctx, 150, 150, "cyan", 2);
snowflake.draw(ctx, 350, 150, "yellow", 2);
snowflake.draw(ctx, 250, 350, "white", 2);
</script>

5. 优势与扩展

  1. 函数式封装:避免副作用,易于测试
  2. 性能优化:雪花点只计算一次,可多次绘制
  3. 参数验证:减少调用错误
  4. 可扩展性:未来可增加旋转、缩放、3D 投影或动画

6. 总结

通过函数式封装科赫雪花,我们实现了:

  • 简洁、可维护的代码结构
  • 高性能绘制,可重复使用
  • 易于封装成 NPM 模块或在前端项目中复用

这种方法不仅适用于科赫雪花,也适合其他递归分形图形的封装,实现算法与渲染解耦,方便扩展和复用。

相关推荐
前端大卫3 小时前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘4 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare4 小时前
浅浅看一下设计模式
前端
Lee川4 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix4 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人4 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl4 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人4 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼5 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端