数学公式生成器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>
相关推荐
Marshmallowc2 小时前
CSS 布局原理:为何“负边距”是栅格系统的基石?
前端·css·面试
Rysxt_2 小时前
Vue 3 项目核心:App.vue 文件的作用与配置详解
前端·javascript·vue.js
洛阳纸贵2 小时前
JAVA高级工程师--Maven父子关系专题
java·前端·maven
imkaifan2 小时前
10、vue3中针对图片的处理
前端·javascript·vue.js
柯南二号2 小时前
【大前端】【iOS】iOS 使用 Objective-C 绘制几大常见布局(UIKit / Core Graphics 实战)
前端·ios
invicinble2 小时前
对于使用html去进行前端开发的全面认识,以及过度到vue开发
前端·javascript·vue.js
我这一生如履薄冰~2 小时前
element-plus去除el-dropdown组件当鼠标移入文本时会出现边框
前端·elementui·vue
小果子^_^3 小时前
div或按钮鼠标经过或鼠标点击后效果样式
前端·css·计算机外设
han_3 小时前
前端性能优化之性能瓶颈点,Web 页面加载全流程解析
前端·javascript·性能优化