数学公式生成器HTML版

数学公式生成器HTML版

上一篇介绍了"轻量级数学符号点击复制工具HTML版" https://blog.csdn.net/cnds123/article/details/156151803

现在介绍数学公式生成器。这是一个功能相对完整的LaTeX公式生成器,具有将公式转换为图片并复制的功能。用于在博客、文档中插入数学公式,使用HTML+CSS+JavaScript实现。

运行截图:

复制图片到word中的情况

代码中引用了https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js,起作用引入的MathJax库版本3的tex-svg.js脚本。这个脚本的作用是让网页能够解析和渲染LaTeX数学公式,并以SVG(可缩放矢量图形)的形式呈现。它提供了以下功能:

1.识别页面中的LaTeX代码(通常包裹在...或.....中,或者使用...和...)。

2.将LaTeX代码转换为高质量的SVG图形,并嵌入到网页中。

3.由于使用SVG,公式可以无损缩放,适应不同的屏幕大小和缩放级别。

在代码中,我们通过以下方式使用MathJax:

• 在实时预览中,我们将公式代码包裹在$$中,然后调用MathJax.typeset()来重新渲染。

• 在复制图片时,我们使用MathJax.tex2svg()函数直接生成SVG节点,然后将其转换为图片。

源码如下:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LaTeX 公式生成器(图片复制版)</title>
    <!-- 引入 MathJax 用于渲染 LaTeX 公式 -->
    <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: "Microsoft YaHei", sans-serif;
        }
        body {
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 20px;
        }
        /* 省略了部分样式代码以保持简洁 */
        .toolbar {
            margin-bottom: 15px;
            padding: 10px;
            background: #f8f9fa;
            border-radius: 6px;
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
        }
        .toolbar button {
            padding: 6px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            background: white;
            cursor: pointer;
            transition: all 0.2s;
        }
        .toolbar button:hover {
            background: #007bff;
            color: white;
            border-color: #007bff;
        }
        .editor-area {
            display: flex;
            gap: 20px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        @media (max-width: 768px) {
            .editor-area {
                flex-direction: column;
            }
        }
        .input-box, .preview-box {
            flex: 1;
            min-width: 300px;
        }
        textarea {
            width: 100%;
            height: 200px;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 6px;
            resize: vertical;
            font-size: 14px;
            line-height: 1.5;
        }
        .preview {
            width: 100%;
            height: 200px;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 6px;
            background: white;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 16px;
            color: #666;
        }
        .btn-group {
            display: flex;
            gap: 10px;
            justify-content: center;
            margin-top: 15px;
        }
        .btn-group button {
            padding: 8px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        .btn-copy-code {
            background: #28a745;
            color: white;
        }
        .btn-copy-img {
            background: #007bff;
            color: white;
        }
        .btn-clear {
            background: #dc3545;
            color: white;
        }
        .toast {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 10px 20px;
            background: rgba(0,0,0,0.7);
            color: white;
            border-radius: 4px;
            opacity: 0;
            transition: opacity 0.3s;
            pointer-events: none;
            z-index: 9999;
        }
        .toast.show {
            opacity: 1;
        }
        .loading {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 50px;
            height: 50px;
            border: 5px solid #f3f3f3;
            border-top: 5px solid #007bff;
            border-radius: 50%;
            animation: spin 1s linear infinite;
            display: none;
            z-index: 9998;
        }
        @keyframes spin {
            0% { transform: translate(-50%, -50%) rotate(0deg); }
            100% { transform: translate(-50%, -50%) rotate(360deg); }
        }
        /* 新增:纯净的截图容器样式,避免干扰 */
        #screenshot-container {
            position: fixed;
            top: -9999px;
            left: -9999px;
            background: white;
            padding: 15px;
            /* 禁用硬件加速,避免截图重复 */
            transform: none !important;
            -webkit-transform: none !important;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>LaTeX 公式生成器(图片复制版)</h1>
        
        <!-- 快捷工具栏 -->
        <div class="toolbar">
            <button onclick="insertLatex('\\frac{分子}{分母}')">分式 (frac)</button>
            <button onclick="insertLatex('\\sqrt{被开方数}')">根号 (sqrt)</button>
            <button onclick="insertLatex('x^{上标}')">上标</button>
            <button onclick="insertLatex('x_{下标}')">下标</button>
            <button onclick="insertLatex('\\sum_{i=1}^n x_i')">求和 (sum)</button>
            <button onclick="insertLatex('\\int_{下限}^{上限} f(x)dx')">积分 (int)</button>
            <button onclick="insertLatex('\\lim_{x \\to 0} f(x)')">极限 (lim)</button>
            <button onclick="insertLatex('\\begin{matrix}a&b\\\\c&d\\end{matrix}')">矩阵 (matrix)</button>
            <button onclick="insertLatex('\\begin{cases}y=x, & x>0\\\\y=-x, & x\\leq0\\end{cases}')">分段函数 (cases)</button>
        </div>
        
        <!-- 编辑和预览区域 -->
        <div class="editor-area">
            <div class="input-box">
                <h3>LaTeX 代码输入</h3>
                <textarea id="latex-input" placeholder="请输入LaTeX公式代码,例如:E=mc^2 或 \\frac{1}{2}mv^2"></textarea>
            </div>
            <div class="preview-box">
                <h3>公式预览</h3>
                <div id="latex-preview" class="preview">预览区域</div>
            </div>
        </div>

        <!-- 功能按钮组 -->
        <div class="btn-group">
            <button class="btn-copy-code" onclick="copyLatexCode()">复制LaTeX代码</button>
            <button class="btn-copy-img" onclick="copyLatexAsImage()">复制公式图片</button>
            <button class="btn-clear" onclick="clearInput()">清空输入</button>
        </div>
    </div>
    
    <!-- 提示框和加载动画 -->
    <div class="toast" id="toast"></div>
    <div class="loading" id="loading"></div>

    <script>
        // 实时渲染公式
        const latexInput = document.getElementById('latex-input');
        const latexPreview = document.getElementById('latex-preview');
        
        latexInput.addEventListener('input', renderLatex);
        
        // 初始化渲染
        renderLatex();
        
        // 渲染LaTeX公式
        function renderLatex() {
            const latexCode = latexInput.value.trim();
            if (!latexCode) {
                latexPreview.innerHTML = '预览区域';
                return;
            }
            // 使用MathJax渲染公式,包裹$确保是数学公式
            latexPreview.innerHTML = `$$${latexCode}$$`;
            // 触发MathJax重新渲染
            MathJax.typeset([latexPreview]);
        }
        
        // 插入LaTeX模板
        function insertLatex(template) {
            const cursorPos = latexInput.selectionStart;
            const currentValue = latexInput.value;
            // 在光标位置插入模板
            latexInput.value = currentValue.substring(0, cursorPos) + template + currentValue.substring(cursorPos);
            latexInput.focus();
            // 重新渲染
            renderLatex();
        }
        
        // 复制LaTeX代码
        function copyLatexCode() {
            const code = latexInput.value.trim();
            if (!code) {
                showToast('请先输入LaTeX代码');
                return;
            }
            navigator.clipboard.writeText(code).then(() => {
                showToast('LaTeX代码已复制');
            }).catch(err => {
                showToast('复制失败:' + err);
            });
        }
        
        // 复制公式为图片(核心优化部分)
        // copyLatexAsImage 函数
        async function copyLatexAsImage() {
            const latexCode = latexInput.value.trim();
            if (!latexCode) {
                showToast('请先输入LaTeX代码');
                return;
            }

            const loading = document.getElementById('loading');
            loading.style.display = 'block';

            try {
                // 使用 MathJax.tex2svg 直接渲染,不要额外加 \[ \]
                const node = MathJax.tex2svg(latexCode, { display: true });
                const svg = node.querySelector('svg');
                svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
                svg.style.background = 'white';

                const serializer = new XMLSerializer();
                const svgStr = serializer.serializeToString(svg);
                const blob = new Blob([svgStr], { type: 'image/svg+xml;charset=utf-8' });
                const url = URL.createObjectURL(blob);

                const img = new Image();
                img.onload = async () => {
                    const canvas = document.createElement('canvas');
                    const ctx = canvas.getContext('2d');
                    const scale = 2;
                    canvas.width = img.width * scale;
                    canvas.height = img.height * scale;
                    ctx.fillStyle = 'white';
                    ctx.fillRect(0, 0, canvas.width, canvas.height);
                    ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);

                    canvas.toBlob(async (pngBlob) => {
                        try {
                            await navigator.clipboard.write([new ClipboardItem({ 'image/png': pngBlob })]);
                            showToast('公式图片已复制');
                        } catch (e) {
                            fallbackDownloadFromCanvas(canvas);
                        }
                        URL.revokeObjectURL(url);
                        loading.style.display = 'none';
                    }, 'image/png');
                };
                img.src = url;

            } catch (err) {
                console.error(err);
                loading.style.display = 'none';
                showToast('生成失败');
            }
        }


        function fallbackDownloadFromCanvas(canvas) {
            const link = document.createElement('a');
            link.download = 'formula.png';
            link.href = canvas.toDataURL('image/png');
            link.click();
        }


        // 降级方案:下载图片
        async function fallbackDownloadImage() {
            const preview = document.getElementById('latex-preview');
            const canvas = await html2canvas(preview, { scale: 2, useCORS: true });
            const link = document.createElement('a');
            link.download = 'latex-formula.png';
            link.href = canvas.toDataURL('image/png');
            link.click();
        }
        
        // 清空输入
        function clearInput() {
            latexInput.value = '';
            latexPreview.innerHTML = '预览区域';
            renderLatex();
            showToast('已清空输入');
        }
        
        // 显示提示框
        function showToast(message) {
            const toast = document.getElementById('toast');
            toast.textContent = message;
            toast.classList.add('show');
            setTimeout(() => {
                toast.classList.remove('show');
            }, 2000);
        }
    </script>
</body>
</html>
相关推荐
卸任17 小时前
Vibe coding工具使用心得
前端·ai编程
brien918 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance18 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M19 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写19 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李19 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易19 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马19 小时前
前沿视界
前端
kyriewen19 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易19 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员