动态演示复平面变换

复变函数可视化工具:动态演示复平面变换

引言

在复变函数的学习过程中,如何直观地理解函数对复平面的变换一直是一个挑战。为了帮助学习者更好地理解复变函数的几何意义,我开发了一个基于Web的复变函数可视化工具。这个工具能够动态展示复变函数如何将复平面上的点进行映射变换,让抽象的数学概念变得生动可见。最后会给出完整代码。

功能概述

界面布局

工具采用左右对比的展示方式:

  • 左侧显示原始复平面,包含标准网格和坐标轴
  • 右侧实时展示函数变换后的结果,通过动画效果直观呈现变换过程

支持的函数变换

目前支持以下几种典型的复变函数:

  1. f(z) = z (恒等映射)

    • 作为参考基准,帮助理解其他变换
    • 输出等同于输入,网格保持不变
  2. f(z) = z² (平方映射)

    • 展示了复数平方的几何效果
    • 可以观察到角度加倍、距离平方的现象
  3. f(z) = 1/z (倒数映射)

    • 演示了复数倒数的几何意义
    • 体现了圆反演的特性
  4. f(z) = z² + 1

    • 展示了复数多项式的行为
    • 观察平移变换的效果
  5. f(z) = e^z (指数映射)

    • 展示了复指数函数的周期性
    • 观察条带区域如何映射到螺旋形状

技术实现

复数运算

核心是实现了Complex类处理复数运算:

javascript 复制代码
class Complex {
    constructor(re, im) {
        this.re = re;
        this.im = im;
    }
    
    // 复数加法
    plus(other) {
        return new Complex(
            this.re + other.re,
            this.im + other.im
        );
    }
    
    // 复数乘法
    times(other) {
        return new Complex(
            this.re * other.re - this.im * other.im,
            this.re * other.im + this.im * other.re
        );
    }
    
    // 其他复数运算...
}

动画实现

采用requestAnimationFrame实现平滑动画效果:

javascript 复制代码
function animate() {
    if (!playing) return;
    t += 0.01; // 控制动画速度
    if (t > 1) {
        t = 1;
        playing = false;
    }
    drawTransGrid();
    if (playing) {
        requestAnimationFrame(animate);
    }
}

绘图技术

使用Canvas进行网格绘制,主要步骤:

  1. 坐标变换:将数学坐标映射到画布坐标
  2. 网格线绘制:通过循环绘制水平和垂直线条
  3. 实时更新:根据动画参数t更新网格位置

使用指南

  1. 选择函数

    • 从下拉菜单中选择要观察的函数
    • 每个函数都有其特定的几何特性
  2. 控制动画

    • 点击"播放"开始动画
    • "暂停"可以在任意时刻停止观察
    • "重置"返回初始状态
  3. 观察要点

    • 关注坐标轴的变化
    • 观察网格线的扭曲方式
    • 注意特殊点的映射关系

教育价值

这个工具在数学教育中有多重价值:

  1. 直观理解

    • 将抽象的数学概念可视化
    • 帮助建立几何直觉
  2. 互动学习

    • 学习者可以自主探索
    • 即时观察变换效果
  3. 概念联系

    • 建立代数和几何的联系
    • 理解不同函数间的关系

技术特点

  1. 响应式设计

    • 适配不同屏幕尺寸
    • 良好的移动端体验
  2. 性能优化

    • 使用requestAnimationFrame确保动画流畅
    • 优化绘图算法提高效率
  3. 代码组织

    • 模块化设计
    • 清晰的代码结构

未来展望

计划添加的功能:

  1. 更多复变函数

    • 三角函数
    • 对数函数
    • 有理函数
  2. 交互增强

    • 自定义函数输入
    • 放大缩小功能
    • 特殊点标记
  3. 教育功能

    • 添加教学注释
    • 保存动画过程
    • 导出图像功能

完整代码

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>复数域函数动态平面变形示例</title>
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col items-center p-4">
  <h1 class="text-2xl font-bold mb-4">复数域函数动态平面变形示例</h1>

  <!-- 函数选择区 -->
  <div class="mb-4 flex space-x-2 items-center">
    <label for="funcSelect" class="mr-2">选择函数:</label>
    <select id="funcSelect" class="p-1 border rounded">
      <option value="z">f(z) = z</option>
      <option value="z2">f(z) = z²</option>
      <option value="1z">f(z) = 1/z</option>
      <option value="z2plus1">f(z) = z² + 1</option>
      <option value="expz">f(z) = e^z</option>
    </select>

    <!-- 动画控制按钮 -->
    <button id="playBtn" class="px-2 py-1 bg-blue-500 text-white rounded">播放动画</button>
    <button id="pauseBtn" class="px-2 py-1 bg-red-500 text-white rounded">暂停动画</button>
    <button id="resetBtn" class="px-2 py-1 bg-gray-500 text-white rounded">复位</button>
  </div>

  <!-- 画布容器 -->
  <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4">
    <!-- 左侧:原平面 -->
    <div class="flex flex-col items-center">
      <h2 class="font-semibold mb-2">原平面 (Domain)</h2>
      <canvas id="domainCanvas" width="400" height="400" class="border border-gray-300"></canvas>
    </div>
    <!-- 右侧:变形过程 (可动态演示从 t=0 到 t=1) -->
    <div class="flex flex-col items-center">
      <h2 class="font-semibold mb-2">动态变形 (Intermediate → Range)</h2>
      <canvas id="transCanvas" width="400" height="400" class="border border-gray-300"></canvas>
    </div>
  </div>

  <script>
    // ========== 全局变量 ==========
    const domainCanvas = document.getElementById("domainCanvas");
    const domainCtx = domainCanvas.getContext("2d");
    const transCanvas = document.getElementById("transCanvas");
    const transCtx = transCanvas.getContext("2d");

    const funcSelect = document.getElementById("funcSelect");
    const playBtn = document.getElementById("playBtn");
    const pauseBtn = document.getElementById("pauseBtn");
    const resetBtn = document.getElementById("resetBtn");

    // 画布大小
    const WIDTH = 400;
    const HEIGHT = 400;

    // 数学坐标范围
    const XMIN = -2, XMAX = 2;
    const YMIN = -2, YMAX = 2;

    // 网格步进
    const STEPS = 20;

    // 动画控制
    let animationRequest = null;
    let t = 0;            // 插值参数 (0 ~ 1)
    let playing = false;  // 是否正在播放

    // ========== 复数结构和函数集 ==========
    class Complex {
      constructor(re, im) {
        this.re = re;
        this.im = im;
      }
      plus(other) {
        return new Complex(this.re + other.re, this.im + other.im);
      }
      times(other) {
        return new Complex(
          this.re * other.re - this.im * other.im,
          this.re * other.im + this.im * other.re
        );
      }
      reciprocal() {
        const denom = this.re * this.re + this.im * this.im;
        return new Complex(this.re / denom, -this.im / denom);
      }
      exp() {
        // e^(x+iy) = e^x (cos y + i sin y)
        const r = Math.exp(this.re);
        return new Complex(r * Math.cos(this.im), r * Math.sin(this.im));
      }
    }

    // (1)恒等映射 f(z) = z
    function f_z(z) {
      return z;
    }
    // (2)平方映射 f(z) = z^2
    function f_z2(z) {
      return z.times(z);
    }
    // (3)倒数映射 f(z) = 1/z
    function f_1z(z) {
      return new Complex(1, 0).times(z.reciprocal());
    }
    // (4)z^2 + 1
    function f_z2plus1(z) {
      return z.times(z).plus(new Complex(1, 0));
    }
    // (5)指数映射 f(z) = e^z
    function f_expz(z) {
      return z.exp();
    }

    // 根据选择返回当前函数
    function getCurrentFunc() {
      switch (funcSelect.value) {
        case "z":          return f_z;
        case "z2":         return f_z2;
        case "1z":         return f_1z;
        case "z2plus1":    return f_z2plus1;
        case "expz":       return f_expz;
      }
      return f_z; // 默认
    }

    // ========== 坐标变换函数 ==========
    // 将数学坐标映射到画布像素坐标
    function toCanvasX(x) {
      return ((x - XMIN) / (XMAX - XMIN)) * WIDTH;
    }
    function toCanvasY(y) {
      // 画布 Y 轴向下
      return HEIGHT - ((y - YMIN) / (YMAX - YMIN)) * HEIGHT;
    }

    // ========== 绘图函数 ==========
    // 绘制原平面的网格
    function drawDomainGrid(ctx) {
      ctx.clearRect(0, 0, WIDTH, HEIGHT);
      ctx.strokeStyle = "#ccc";
      ctx.lineWidth = 1;

      const stepX = (XMAX - XMIN) / STEPS;
      const stepY = (YMAX - YMIN) / STEPS;

      // 水平线
      for (let i = 0; i <= STEPS; i++) {
        const y = YMIN + i * stepY;
        ctx.beginPath();
        ctx.moveTo(toCanvasX(XMIN), toCanvasY(y));
        ctx.lineTo(toCanvasX(XMAX), toCanvasY(y));
        ctx.stroke();
      }

      // 垂直线
      for (let j = 0; j <= STEPS; j++) {
        const x = XMIN + j * stepX;
        ctx.beginPath();
        ctx.moveTo(toCanvasX(x), toCanvasY(YMIN));
        ctx.lineTo(toCanvasX(x), toCanvasY(YMAX));
        ctx.stroke();
      }

      // 画坐标轴
      ctx.strokeStyle = "#000";
      ctx.lineWidth = 2;
      // x 轴
      ctx.beginPath();
      ctx.moveTo(toCanvasX(XMIN), toCanvasY(0));
      ctx.lineTo(toCanvasX(XMAX), toCanvasY(0));
      ctx.stroke();
      // y 轴
      ctx.beginPath();
      ctx.moveTo(toCanvasX(0), toCanvasY(YMIN));
      ctx.lineTo(toCanvasX(0), toCanvasY(YMAX));
      ctx.stroke();
    }

    // 在插值下绘制变形网格
    // t=0 时画与 domainGrid 同位置
    // t=1 时画与 f(z) 后对应的位置
    function drawTransformedGrid(ctx, transformFunc, t) {
      ctx.clearRect(0, 0, WIDTH, HEIGHT);
      ctx.strokeStyle = "#ccc";
      ctx.lineWidth = 1;

      const stepX = (XMAX - XMIN) / STEPS;
      const stepY = (YMAX - YMIN) / STEPS;

      // 插值函数:z(t) = (1-t)*z + t*f(z)
      function interpolate(z, w, t) {
        return new Complex(
          (1 - t) * z.re + t * w.re,
          (1 - t) * z.im + t * w.im
        );
      }

      // 每条水平线
      for (let i = 0; i <= STEPS; i++) {
        const y = YMIN + i * stepY;
        ctx.beginPath();
        let firstPoint = true;
        for (let x = XMIN; x <= XMAX + 1e-9; x += stepX / 4) {
          const z = new Complex(x, y);
          const w = transformFunc(z);
          const zt = interpolate(z, w, t);
          const px = toCanvasX(zt.re);
          const py = toCanvasY(zt.im);

          if (firstPoint) {
            ctx.moveTo(px, py);
            firstPoint = false;
          } else {
            ctx.lineTo(px, py);
          }
        }
        ctx.stroke();
      }

      // 每条垂直线
      for (let j = 0; j <= STEPS; j++) {
        const x = XMIN + j * stepX;
        ctx.beginPath();
        let firstPoint = true;
        for (let y = YMIN; y <= YMAX + 1e-9; y += stepY / 4) {
          const z = new Complex(x, y);
          const w = transformFunc(z);
          const zt = interpolate(z, w, t);
          const px = toCanvasX(zt.re);
          const py = toCanvasY(zt.im);

          if (firstPoint) {
            ctx.moveTo(px, py);
            firstPoint = false;
          } else {
            ctx.lineTo(px, py);
          }
        }
        ctx.stroke();
      }

      // 画插值后的 x 轴和 y 轴
      ctx.strokeStyle = "#000";
      ctx.lineWidth = 2;
      // x 轴: y=0
      ctx.beginPath();
      let firstAxisX = true;
      for (let x = XMIN; x <= XMAX + 1e-9; x += stepX / 4) {
        const z = new Complex(x, 0);
        const w = transformFunc(z);
        const zt = interpolate(z, w, t);
        if (firstAxisX) {
          ctx.moveTo(toCanvasX(zt.re), toCanvasY(zt.im));
          firstAxisX = false;
        } else {
          ctx.lineTo(toCanvasX(zt.re), toCanvasY(zt.im));
        }
      }
      ctx.stroke();

      // y 轴: x=0
      ctx.beginPath();
      let firstAxisY = true;
      for (let y = YMIN; y <= YMAX + 1e-9; y += stepY / 4) {
        const z = new Complex(0, y);
        const w = transformFunc(z);
        const zt = interpolate(z, w, t);
        if (firstAxisY) {
          ctx.moveTo(toCanvasX(zt.re), toCanvasY(zt.im));
          firstAxisY = false;
        } else {
          ctx.lineTo(toCanvasX(zt.re), toCanvasY(zt.im));
        }
      }
      ctx.stroke();
    }

    // ========== 动画相关函数 ==========
    function animate() {
      if (!playing) return;
      t += 0.01; // 调整速度
      if (t > 1) {
        t = 1;
        playing = false; 
      }
      drawTransGrid();    // 绘制当前插值下的网格
      if (playing) {
        animationRequest = requestAnimationFrame(animate);
      }
    }

    function drawAll() {
      // 先画左侧固定的 domain 网格
      drawDomainGrid(domainCtx);
      // 再画右侧根据 t 的插值绘制结果
      drawTransGrid();
    }

    function drawTransGrid() {
      const currentFunc = getCurrentFunc();
      drawTransformedGrid(transCtx, currentFunc, t);
    }

    // ========== 按钮事件 ==========
    playBtn.addEventListener("click", () => {
      if (t >= 1) t = 0; // 如果已经到结尾,再次播放则从头开始
      playing = true;
      if (!animationRequest) animate();
    });

    pauseBtn.addEventListener("click", () => {
      playing = false;
      animationRequest = null;
    });

    resetBtn.addEventListener("click", () => {
      playing = false;
      t = 0;
      animationRequest = null;
      drawAll();
    });

    // 在函数下拉变更时,复位并重新绘制
    funcSelect.addEventListener("change", () => {
      playing = false;
      t = 0;
      animationRequest = null;
      drawAll();
    });

    // 页面载入时初始绘制
    window.onload = () => {
      drawAll();
    };
  </script>
</body>
</html>

结语

这个复变函数可视化工具不仅是一个教学辅助工具,也是理解复变函数几何意义的有力帮手。通过动态、直观的方式展示复变函数的变换效果,让学习复变函数变得更加生动有趣。欢迎教师和学生使用这个工具进行教学和学习,也欢迎开发者参与改进和扩展这个项目。


参考资源:

  • 复变函数教材
  • HTML5 Canvas文档
  • JavaScript动画最佳实践
相关推荐
ZC跨境爬虫5 小时前
跟着 MDN 学 HTML day_9:(信件语义标记)
前端·css·笔记·ui·html
前端老石人5 小时前
HTML 字符引用完全指南
开发语言·前端·html
nbwenren9 小时前
2026实测:Gemini 3 镜像站视觉能力实践——拍照原型图,一键生成 HTML+CSS 代码
前端·css·html
爱上好庆祝15 小时前
学习js的第五天
前端·css·学习·html·css3·js
前端老石人16 小时前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
jingqingdai318 小时前
别用正则格式化 HTML!我用 DOM 遍历实现零风险本地格式化,老项目重构效率直接拉满
前端·重构·html
a11177620 小时前
“像风之翼“无人机巡检平台仪表盘
前端·javascript·开源·html·无人机
a11177620 小时前
QQ 宠物(怀旧 开源)前端electron项目
前端·开源·html
ZC跨境爬虫20 小时前
跟着 MDN 学 HTML day_8:(高级文本语义标签+适配核心功底)
前端·css·笔记·ui·html
Dxy123931021620 小时前
HTML中的伪类详解:从基础到高级应用的全面指南
前端·html