Back to skills
extension
Category: Productivity & OfficeNo API key required

数学动画生成技能

数学题目动画生成器。当用户输入一道数学题目(文本或图片),自动求解并生成包含 LaTeX 题目描述、详细解答、JSXGraph 交互式动画的完整项目包(index.html + animation.js + 本地 css/js 依赖,imgs资源)。支持轨迹问题、切线问题、几何构造、函数变换、圆锥曲线等高中数学题型。触发词:数学题动画、生成数学动画、题目动画演示、math animation、几何动画、函数图像动画、解析几何动画、数学题解答动画、数学作图、数学动态演示、帮我做一道数学题的动画。

personAuthor: wodesqAIhubgithub

数学题目动画生成器

概述

将用户输入的数学题目(文本描述或图片)转化为一个完整的项目包,包含:

  1. index.html — LaTeX 题目描述 + JSXGraph 交互动画 + 详细解答
  2. animation.js — 独立的动画逻辑脚本
  3. css/js/imgs/— 从 math-animation-skills 项目中的assets文件夹中复制到本地依赖文件

所有文件使用本地相对路径引用(css/js/、、imgs/animation.js),不依赖 CDN(MathJax LaTeX 渲染除外)。

工作流程

Step 1: 题目分析

从用户输入中提取:

  • 题目文字(如果是图片,读取图片内容提取文字和数学表达式)
  • 题目类型:轨迹问题 / 切线问题 / 最值问题 / 几何构造 / 函数变换 / 圆锥曲线 / 数列 / 其他
  • 已知条件:坐标系、方程、点的坐标、几何约束等
  • 求解目标

Step 2: 数学求解

对题目进行完整求解:

  • 写出详细的推导步骤
  • 计算关键数值(坐标、斜率、方程参数等)
  • 得到最终答案
  • 所有数学表达式使用 LaTeX 格式($...$ 行内,\\(...\\) 行内,$$...$$ 块级)

Step 3: 动画设计

根据题目类型设计动画步骤(3-6 步为宜):

动画设计原则:

  • 每步对应解题过程中的一个关键操作
  • 步骤之间通过 setTimeout 自动链式触发
  • 每步开始时显示该步骤的文字说明(红色文字,col2
  • 最终步展示结论

注意:动画中呈现的文字不能重叠。

典型动画步骤模板:

| 题型 | 步骤示例 | |------|---------| | 轨迹问题 | 1.展示基本图形 + 关键点 → 2.放置动点 → 3.移动动点展示变化 → 4.展示轨迹曲线和结论 | | 几何构造 | 1.已知图形元素 → 2.逐步构造辅助线/点 → 3.展示结论关系 | | 函数图像 | 1.坐标系 + 函数图像 → 2.参数 slider 变化 → 3.标注特殊点(极值/零点) | | 切线问题 | 1.曲线 + 外点 → 2.切线的形成 → 3.切点标注 → 4.展示几何关系 | | 圆锥曲线 | 1.椭圆/双曲线/抛物线 → 2.焦点/准线标注 → 3.参数变化展示 |

Step 4: 编写动画脚本(animation.js)

生成独立的 animation.js 文件,遵循 references/animation-patterns.md 中的模式。

4.1 animation.js 基本结构

// animation.js - 由 math-animation-generator 自动生成
var board, col = "black", col2 = "red", col3 = "#1d04e1ff", col4 = "#9605e3ff";
var time1 = 1, finish = 1, play = 1;
var text, P, A, B, angle, Interval, cartoon_set;

$(document).ready(function() {
    initial();
    window.addEventListener('orientationchange', function(event) {
        if (window.orientation == 180 || window.orientation == 0) {
            var width = $(window).width();
            if (board) board.resizeContainer(width - 20);
            if (typeof reset === 'function') reset();
        }
        if (window.orientation == 90 || window.orientation == -90) {
            var width = $(window).width();
            if (board) board.resizeContainer(width - 20);
        }
    });
});

$(document).on("click", "#jxgbox", function(e) { e.stopPropagation(); });

// ====== 初始化画板 ======
function initial() {
    board = JXG.JSXGraph.initBoard('jxgbox', {
        boundingbox: [x_min, y_max, x_max, y_min],
        keepaspectratio: true,
        axis: false, 
        Grids:false,
        showNavigation: false
    });
    board.options.line.strokeColor = 'black';

    // 坐标轴标记
    var box_range = board.getBoundingBox();
    board.create('text', [function() { return box_range[2] - 0.8; }, 0.3, 'x'], { fixed: true, fontsize: 14, strokeColor: col });
    board.create('text', [0.2, function() { return box_range[1] - 0.2; }, 'y'], { fixed: true, fontsize: 14, strokeColor: col });

    X= board.create('arrow',[[-2.2,0],[18.2,0]],{strokeColor:col,strokeWidth:2,fixed:true});
    Y= board.create('arrow',[[0,-10.8],[0,15]],{strokeColor:col,strokeWidth:2,fixed:true});  

    // ★★★ 在此创建初始图形元素(曲线、点、线段等) ★★★

    // 隐藏的 slider 驱动动画
    a = board.create('slider', [[sx1, sy], [sx2, sy], [start, start, end]], {
        size: 2, withLabel: true, name: 'a'
    });
    a.hideElement();
}

// ====== 动画函数(每个步骤对应一个 AnimationN) ======
function Animation1() {
    play = 0;
    $("#catroong_config1").attr("playing_cartoon", "1");

    cartoon_time = $("#catroong_config1").attr("cartoon_1");
    playing_time = $("#catroong_config1").attr("playing_time");
    animation_time = cartoon_time - parseInt(playing_time);

    a.moveTo([target_value, slider_y], animation_time, {
        callback: function() {
            clearInterval(Interval);
            $("#catroong_config1").attr("is_clear2", "0");
            $("#catroong_config1").attr("playing_time", "0");

            // 显示文字说明
            CheckTXT();
            text = board.create('text', [tx, ty, function() { return "步骤1的说明"; }], {
                fixed: true, fontsize: 16, strokeColor: col2
            });
            $("#catroong_config1").attr("is_text", "0");

            // ★★★ 创建/更新图形元素 ★★★

            // 链式触发下一步
            $("#catroong_config1").attr("playing_cartoon", "2");
            $("#catroong_config1").attr("is_clear", "1");
            cartoon_set = setTimeout(function() {
                clearTimeout(cartoon_set);
                $("#catroong_config1").attr("is_clear", "0");
                Animation2();
                CountTime();
            }, 2500);
        }
    });
}

function Animation2() { /* 类似结构 */ }
function Animation3() { /* 类似结构 */ }
function Animation4() {
    // 最后一步...
    a.moveTo([final_value, slider_y], animation_time, {
        callback: function() {
            // ... 清理和显示结论 ...

            finish = 0;
            $("#catroong_config1").attr("playing_cartoon", "1");
            $("#catroong_config1").attr("is_text", "1");
            $("#catroong_config1").attr("is_clear", "0");
            $("#start").css("display", "");
            $("#stop").css("display", "none");
        }
    });
}

// ====== 播放控制 ======
$(document).on("click", "#start", function() {
    var pc = $("#catroong_config1").attr("playing_cartoon");
    var fn = "Animation" + pc + "()";
    if (finish == "0") {
        JXG.JSXGraph.freeBoard(board);
        initial();
        board.update();
        finish = 1;
        angle = "";
    }
    if (time1 == "1") {
        $("#loading").css("display", "");
        $("#jxgbox").css("display", "none");
        $("#regist").attr("disabled", "true");
        setTimeout(function() {
            $("#loading").hide();
            $("#jxgbox").show();
            time1 = 2;
            $("#regist").removeAttr("disabled");
        }, 1000);
        cartoon_set = setTimeout(function() {
            clearTimeout(cartoon_set);
            $("#catroong_config1").attr("is_clear", "0");
            eval(fn);
            if (play == 0) CountTime();
        }, 2000);
    } else {
        eval(fn);
        if (play == 0) CountTime();
    }
    $("#start").css("display", "none");
    $("#stop").css("display", "");
});

$(document).on("click", "#stop", function() {
    $("#start").css("display", "");
    $("#stop").css("display", "none");
    if ($("#catroong_config1").attr("is_clear") == "1") {
        clearTimeout(cartoon_set);
        $("#catroong_config1").attr("is_clear", "0");
    }
    if ($("#catroong_config1").attr("is_clear2") == "1") {
        clearInterval(Interval);
        $("#catroong_config1").attr("is_clear2", "0");
    }
    board.stopAllAnimation();
});

$(document).on("click", "#regist", function() {
    if (typeof($("#regist").attr("disabled")) == "undefined") {
        reset();
    }
    angle = "";
});

// ====== 辅助函数 ======
function CountTime() {
    $("#catroong_config1").attr("is_clear2", "1");
    Interval = setInterval(function() {
        var pt = $("#catroong_config1").attr("playing_time");
        $("#catroong_config1").attr("playing_time", parseInt(pt) + 100);
    }, 100);
}

function CheckTXT() {
    if ($("#catroong_config1").attr("is_text") == "0") {
        text.remove();
        $("#catroong_config1").attr("is_text", "1");
    }
}

function Show_Answer() { $("#ShowAnswer").hide(); $("#HideAnswer").show(); $("#answer").show(); }
function Hide_Answer() { $("#ShowAnswer").show(); $("#HideAnswer").hide(); $("#answer").hide(); }

function getLength(a, b) {
    return Math.abs(Math.sqrt(Math.pow(a.X() - b.X(), 2) + Math.pow(a.Y() - b.Y(), 2)));
}

function getAngle(a, b, c) {
    return Math.acos((Math.pow(getLength(a, b), 2) + Math.pow(getLength(b, c), 2) - Math.pow(getLength(a, c), 2)) / (2 * getLength(a, b) * getLength(b, c))) / Math.PI * 180;
}

function changeTwoDecimal(x) {
    var f_x = parseFloat(x);
    if (isNaN(f_x)) return "";
    return Math.round(x * 100) / 100;
}

function reset() {
    if ($("#catroong_config1").attr("is_clear") == "1") {
        clearTimeout(cartoon_set);
        $("#catroong_config1").attr("is_clear", "0");
    }
    if ($("#catroong_config1").attr("is_clear2") == "1") {
        clearInterval(Interval);
        $("#catroong_config1").attr("is_clear2", "0");
    }
    board.stopAllAnimation();
    $("#catroong_config1").attr("playing_cartoon", "1");
    $("#catroong_config1").attr("is_text", "1");
    $("#catroong_config1").attr("is_clear", "0");
    $("#catroong_config1").attr("playing_time", "0");
    JXG.JSXGraph.freeBoard(board);
    initial();
    board.update();
    finish = 1;
    $("#start").css("display", "");
    $("#stop").css("display", "none");
}

Step 5: 组装项目包

生成的项目包结构如下:

project-name/
├── index.html          # 主页面
├── animation.js        # 动画脚本
├── imgs/               # 从项目复制的图片资源
│   ├──start.png
│   ├──stop.png
│   ├──reset.png
├── css/                # 从项目复制的 CSS
│   ├── bootstrap.min.css
│   ├── font-awesome.min.css
│   ├── ionicons.min.css
│   ├── AdminLTE2.css
│   ├── font-awesome.min.css
│   └── Math.css
└── js/                 # 从项目复制的 JS
    ├── jquery.min.js
    ├── bootstrap.min.js
    ├── Math.js
    ├── jsxgraphcore.js
    └── AdminLTE/
        └── apps.js

操作步骤:

  1. 创建输出目录(在工作目录下创建,如 E:\AI-agent\math-animation-skills\generated\<题目名>\

  2. 复制依赖文件 — 运行脚本:

    python scripts/copy_deps.py <输出目录路径>
    

    或手动从 E:\AI-agent\math-animation-skills\css\E:\AI-agent\math-animation-skills\js\ 复制所需文件到输出目录的对应位置。

  3. 生成 animation.js — 写入独立的动画 JavaScript 文件

  4. 生成 index.html — 读取 assets/template.html,替换占位符:

    | 占位符 | 替换内容 | 示例 | |--------|---------|------| | {{TITLE}} | 页面标题 | 《椭圆中的定值与轨迹方程问题》 | | {{PROBLEM_STATEMENT}} | Tab1 题目描述(LaTeX, <p> 包裹) | <p>已知椭圆\(C:\frac{x^2}{9}+\frac{y^2}{4}=1\)...</p> | | {{CONCLUSION}} | 动画结论(LaTeX) | <p>\(P\)轨迹是半径为\(\sqrt{13}\)的圆,方程为\(x^2+y^2=13\)</p> | | {{FULL_PROBLEM}} | Tab2 完整题目 | 同 PROBLEM_STATEMENT 或更详细版本 | | {{SOLUTION}} | 详细解答(LaTeX, <p> 分步) | <p>解:(Ⅰ)可知\(c=\sqrt{5}\)...</p> | | {{CARTOON_TIMES}} | 动画时长配置属性 | cartoon_1="800" cartoon_2="400" cartoon_3="2000" cartoon_4="11000" |

  5. 验证目录结构 — 确认所有文件就位

Step 6: 预览

  • 确认所有 LaTeX 表达式语法正确
  • 确认 JSXGraph 代码逻辑正确
  • 确认动画步骤数量与 cartoon_N 属性数量匹配
  • preview_url 预览生成的 index.html

重要注意事项

坐标系设置

boundingbox: [x_min, y_max, x_max, y_min] 决定了可见区域。根据题目需要合理设置,确保所有图形元素都在可视范围内。

Slider 作为动画引擎

  • Slider 始终隐藏(a.hideElement()
  • Slider 值变化驱动所有关联点坐标变化
  • 动画持续时间合理(300ms-15000ms)

颜色约定

| 用途 | 颜色 | |------|------| | 坐标轴 | col = "black" | | 文字说明/高亮 | col2 = "red" | | 主图形(椭圆/曲线等) | col3 = "#1d04e1ff" | | 辅助线 | col4 = "#9605e3ff" |

LaTeX 转义

  • 在 HTML body 中使用 $...$
  • 不能使用 \(...\) 单反斜杠(会被 HTML 解析破坏)

数学求解准确性

  • 解析几何题目必须正确计算交点、切点、斜率等
  • 轨迹方程推导要有清晰的数学逻辑
  • 特殊情形(垂直于 x 轴、k 不存在等)必须考虑

依赖文件路径

  • HTML 使用 css/js/ 引用(因为 index.html 在项目根目录,css/js 在平级目录)
  • animation.js 使用 animation.js(同一目录)
  • AdminLTE JS 文件名为 apps.js(不是 app.js),HTML 中引用 js/AdminLTE/apps.js

icon/ 缺失处理

原项目 icon/ 目录不存在。模板使用内联 SVG 图标替代播放/停止/重置按钮:

  • 播放按钮 → 蓝色圆形 + 白色三角形
  • 停止按钮 → 红色圆形 + 白色矩形
  • 重置按钮 → 灰色圆形 + 白色弧形箭头

参考资源

  • references/animation-patterns.md:JSXGraph 动画模式和 API 详解
  • references/html-structure.md:HTML 页面结构与依赖说明
  • assets/template.html:完整 HTML 模板(本地引用版本)
  • assets/css.. 、assets/js 、assets/imgs:本地的css和js、imgs(本地引用)
  • scripts/copy_deps.py:自动复制 CSS/JS 依赖到输出目录的 Python 脚本

示例用法

用户输入 1(文本):

已知椭圆 C: x²/9 + y²/4 = 1,若动点 P(x₀, y₀) 为椭圆 C 外一点,且点 P 到椭圆 C 的两条切线相互垂直,求点 P 的轨迹。

输出项目包:

generated/ellipse-tangent-trajectory/
├── index.html       ← 双Tab页面(动画解析 + 题目解答)
├── animation.js     ← 4步动画(取P点→做垂线→相切→轨迹圆)
├── css/             ← 5个CSS文件(bootstrap, font-awesome, ionicons, AdminLTE2, Math.css)
└── js/              ← 5个JS文件(jquery, bootstrap, Math.js, jsxgraphcore, AdminLTE/apps.js)

用户输入 2(图片):

[一张包含数学题目的截图]

处理: 先用视觉能力读取图片中的题目文字和数学公式,然后按上述流程生成完整项目包。