-->

MathJax is duplicating my equations — why and how

2020-07-27 05:15发布

问题:

I have the following page (css useless--omitted):

HTML:

<!DOCTYPE html>
<html lang=en>
    <head>
        <meta charset="UTF-8">
        <title>Command Line</title>
        <script type="text/x-mathjax-config">
        MathJax.Hub.Config({
            extensions: ["http://cs.jsu.edu/mathjax-ext/contrib/forminput/forminput.js"],
            tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]},
            TeX: {extensions: ["AMSmath.js","AMSsymbols.js"]}
        });
        </script>
        <script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
        <script src="http://code.jquery.com/jquery-1.10.2.js"></script>
        <script src="!!IMPORTANT_JS!!.js"></script>
        <script src="theta.js"></script>
        <script src="cmd.js"></script>
        <link rel="STYLESHEET" type="text/css" href="styles.css">
    </head>
    <header>
        <h1 id="title">COMMAND LINE v.2.0</h1><span id="copy">&copy; 2015 ~ Conor O'Brien</span>
    </header>
    <body>
        <div id="subtitle">Now with $\LaTeX$!</div>
        <div id="out"></div>
        <h3 id="sQ"></h3>
    </body>
    <footer>
        <input type="text" id="cmd">
        <input type="color" id="col">
        <div id="left">&gt;&gt;</div>
    </footer>
</html>

JavaScript:

$( document ).ready(function(){
    window.t = false;
    $("#col").on("change",function(){
        var d = $q(this).value;
        $q("#out").innerHTML+="<p>Color: <span style=\"color: "+d+";\">"+d.toUpperCase()+", "+Color.hexToRgb(d).v+" - "+linkC("/color "+d,"Use")+"</span>.</p>";
    });
    $("#cmd")[0].addEventListener("keypress",function(e){
        if(e.key=="Enter"){
            submit();
        }
    });
    $("#out").html("Initializing...");
    window.init = function(){
        var d = [br(3)+
                "********* INITIALIZED *********",
                "* Welcome to the experimental *",
                "* JavaScript command line! It *",
                "* will execute exactly like a *",
                "* handheld calculator, having *",
                "* the capacity to execute the *",
                "*        user's inputs.       *",
                "*                             *",
                "* ~-~-~-~-~-~-~-~-~-~-~-~-~-~ *",
                "*                             *",
                "* Type 'help' for environment *",
                "*           commands.         *",
                "*******************************"+br(),
                "<hr>"],i=0;
        function x(d,i){
            $q("#out").innerHTML += (d[i++])||"";
            $q("#out").innerHTML += br();
            if(i<d.length){ setTimeout(x,20,d,i); } else {
                window.t = true;
            }
        }
        setTimeout(x,500,d,i);
    }
    init();
});

function br(x){
    var o = "",x=x||1;
    for(i=0;i<x;i++){
        o+="<br>";
    }
    return o;
}

function $q(i){
    return $(i)[0];
}

function submit(){
    if(t){
        var $out = getOutput();
        if($out){
            var d = "<p id='a"+(el++)+"'>"+$out+"</p>";
            $q("#out").innerHTML += d;
            lines.push(d);
            marker = lines.length;
            location = "file:///C:/Users/Conor%20O%27Brien/Documents/Programming/command%20line%20v.2.0/main.html#sQ";
            $q("#cmd").focus();
            console.log(el);
            MathJax.Hub.Queue(["Typeset",MathJax.Hub,"a"+el+""]);
        }
    }
}

var pi = Math.PI;
var e  = Math.E;

function Reval(str){
    with(Math){var tstr = str.replace(/(.+)\^(.+)/g,"Math.pow($1,$2)");return eval(tstr);}
}

function Dval(str){
    return "$"+str.replace(/(.+)\^(.+)/g,"{$1}^{$2}")+"$";
}

var el = 0;

function getOutput(){
    var val = $q("#cmd").value;
    if(val=="help"){
        $q("#out").innerHTML = "";
        return "<h1>HELP</h1><h2>Commands</h2>"+
        [
            "= help",
            "  - displays help document.",
            "= open {target}",
            "  - displays {target}'s contents.",
            "  - {target} values:",
            "    * options: adjust options with subpoints (i.e. options.{prop} = {1/0} [1=True,0=False])"
        ].join(br())+"<hr>";
    } else if(val=="cls"||val=="clear"){
        $q("#out").innerHTML = "";
        return "Cleared.";
    } else if(val.substr(0,5)=="open "){
        var target = val.substr(5,val.length);
        console.log(target);
        switch(target){
            case "options":

            break;
            default:
                return new Error(target+" is not a valid target!");
            break;
        }
    } else {
        try {
            console.log(val,Reval(val));
            return Dval(val)+br()+"<span class='ans'>"+Reval(val)+"</span>";
        } catch(e) {
            return e;
        }
    }
}


Upon executing, this (ideally) will able to be evaluate the user's input (i.e. sin(3) or 3 + 5^3 / 99.3). This code does this successfully, though displaying it is trickier. I am using MathJax to display the math correctly, and using MathJax.Hub.Queue(["Typeset",MathJax.Hub,"a"+el+""]); to target the element a{number} and redo its MathJax. This somehow refreshes the entire page and furthermore, after a few entries into the input, will start to duplicate the previous equations.

I have narrowed it down to what I believe would be the solution: MathJax has two observable phases.

  1. It formats the equations, then
  2. It applies fonts.

The MathJax function only seems to perform these two steps in my code twice: once, at the beginning, and once, when the function is first executed.

My question is, how can I fix this? I've been trying to fix this, but to no avail.

回答1:

There are several problems with your approach. The reason that MathJax is reprocessing the whole document is because of how you increment el in the submit() function. You use a post-increment, el++, so that after the line

var d = "<p id='a"+(el++)+"'>"+$out+"</p>";

el is one larger than the ID of the paragraph you are adding. So later when you do

MathJax.Hub.Queue(["Typeset",MathJax.Hub,"a"+el+""]);

the ID that you are passing doesn't refer to the newly added <p> tag, and in fact, there is no such ID. When the ID can't be found, MathJax processes the whole page.

As for the duplication of math, that is because you are using innerHTML to update the output area. Doing that destroys the existing DOM and replaces it with a new one (a very inefficient approach). In doing so, you sever the connection between the MathJax and the existing mathematics in the DOM, so MathJax doesn't know that the math has been processed. So when it reprocesses the page, it tries to typeset the math again (though it only gets as far as creating the preview in this case, but that is the math you see as a duplicate).

It is best not to use innerHTML to replace the contents of an element that contains MathJax output. In addition to disconnecting the output from MathJax, you also lose the event handlers that MathJax uses for things like its contextual menu and zooming functions. It would be better to use explicit DOM manipulation commands like document.createElement() and appendChild(). These take a little more work, but will be much more efficient, especially for longer documents, and will not cause MathJax problems.

MathJax actually provides some support for creating elements this way, so you could take advantage of those rather than doing it all by hand.