Skip to main content
Clarified the complete set of MathJax header to be added to the user's document
Source Link

TheAltogether, the proper MathJax headerheaders added in the user's document looksthen look like this:

...
<script>
  MathJax = {

// run scrollTo() a second time after MathJax typesetting is complete
    startup: {
      pageReady: () => {
        return MathJax.startup.defaultPageReady().then(() => {
          scrollTo({
            left: mjx.xscroll,
            top: mjx.yscroll,
            behavior: "smooth"
          });
          console.log("Second Scroll: " + mjx.xscroll + " " + mjx.yscroll);
          parent.mjax = true;
        });
      },
  ...
  };
...
</script>
...
<script>
  document.querySelector('html').onmouseenter = function() {
   var scriptx = document.createElement('script');
   scriptx.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js';
//   scriptx.src = 'mathjax/tex-chtml-full.js';
   scriptx.async = true;
   document.head.appendChild(scriptx);
  };
</script>

The proper MathJax header in the user's document looks like this:

...
<script>
  MathJax = {

// run scrollTo() a second time after MathJax typesetting is complete
    startup: {
      pageReady: () => {
        return MathJax.startup.defaultPageReady().then(() => {
          scrollTo({
            left: mjx.xscroll,
            top: mjx.yscroll,
            behavior: "smooth"
          });
          console.log("Second Scroll: " + mjx.xscroll + " " + mjx.yscroll);
          parent.mjax = true;
        });
      },
  ...
  };
...
</script>
...

Altogether, the proper MathJax headers added in the user's document then look like this:

...
<script>
  MathJax = {

// run scrollTo() a second time after MathJax typesetting is complete
    startup: {
      pageReady: () => {
        return MathJax.startup.defaultPageReady().then(() => {
          scrollTo({
            left: mjx.xscroll,
            top: mjx.yscroll,
            behavior: "smooth"
          });
          console.log("Second Scroll: " + mjx.xscroll + " " + mjx.yscroll);
          parent.mjax = true;
        });
      },
  ...
  };
...
</script>
...
<script>
  document.querySelector('html').onmouseenter = function() {
   var scriptx = document.createElement('script');
   scriptx.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js';
//   scriptx.src = 'mathjax/tex-chtml-full.js';
   scriptx.async = true;
   document.head.appendChild(scriptx);
  };
</script>
Moved the code comments above the code snippet
Source Link

Also, the footer links, except for "Feedback" and "Created by", are all getting jumbled on top of each other by Firefox Nightly on linux, which does not seem to handle the img alt attribute gracefully. One alternative is to add float: left; overflow: hidden; to the img style. A better looking alternative is to move the img alt attribute text into the anchor content proper, after the img tag. Hmm - the coding style in the page is often using the same exact text string as 1) an HTML Element or 2) an element Attribute or 3) a Document Object Model Property for 4) an element ID, and 5) a javascript Variable. I find that this style can be misleading and prefer visually distinguishing the strings for an ID or Variable from an Element or Attribute or Property, and from each other.

In the "Reset" button function, the line download.download = 'template.html'; appears to be redundant, since this value has already been set in the corresponding Anchor Attribute. This value is not being changed globally by the "input file"/"Browse" button function, in download.download = file.name;.

I find the Anchor tag content "Download" used in the page header to be misleading. The connotation of the term "Download" is that this link will transfer some data from a remote location to the browser. But in fact, the exact opposite function is being performed, where the local browser content is instead being sent to a "remote" location. In this case, the "local" content is being "downloaded" and the user, usually, is then prompted to save their local content to file.

A more intuitive moniker for this Anchor tag would be "Save", rather than "Download", preferably styled as a button, in similarity to the other function buttons in the page header.

This can be done by simply replacing the Anchor content, as:

<a href="" download="template.html" title="Save your HTML document" id="download"><button>Save</button></a>

Hmm - the coding style in the page is often using the same exact text string as 1) an HTML Element or 2) an element Attribute or 3) a Document Object Model Property for 4) an element ID, and 5) a javascript Variable. I find that this style can be misleading and prefer visually distinguishing the strings for an ID or Variable from an Element or Attribute or Property, and from each other.

In the "Reset" button function, the line download.download = 'template.html'; appears to be redundant, since this value has already been set in the corresponding Anchor Attribute. This value is not being changed globally by the "input file"/"Browse" button function, in download.download = file.name;.

I find the Anchor tag content "Download" used in the page header to be misleading. The connotation of the term "Download" is that this link will transfer some data from a remote location to the browser. But in fact, the exact opposite function is being performed, where the local browser content is instead being sent to a "remote" location. In this case, the "local" content is being "downloaded" and the user, usually, is then prompted to save their local content to file.

A more intuitive moniker for this Anchor tag would be "Save", rather than "Download", preferably styled as a button, in similarity to the other function buttons in the page header.

This can be done by simply replacing the Anchor content, as:

<a href="" download="template.html" title="Save your HTML document" id="download"><button>Save</button></a>

Also, the footer links, except for "Feedback" and "Created by", are all getting jumbled on top of each other by Firefox Nightly on linux, which does not seem to handle the img alt attribute gracefully. One alternative is to add float: left; overflow: hidden; to the img style. A better looking alternative is to move the img alt attribute text into the anchor content proper, after the img tag.

Hmm - the coding style in the page is often using the same exact text string as 1) an HTML Element or 2) an element Attribute or 3) a Document Object Model Property for 4) an element ID, and 5) a javascript Variable. I find that this style can be misleading and prefer visually distinguishing the strings for an ID or Variable from an Element or Attribute or Property, and from each other.

In the "Reset" button function, the line download.download = 'template.html'; appears to be redundant, since this value has already been set in the corresponding Anchor Attribute. This value is not being changed globally by the "input file"/"Browse" button function, in download.download = file.name;.

I find the Anchor tag content "Download" used in the page header to be misleading. The connotation of the term "Download" is that this link will transfer some data from a remote location to the browser. But in fact, the exact opposite function is being performed, where the local browser content is instead being sent to a "remote" location. In this case, the "local" content is being "downloaded" and the user, usually, is then prompted to save their local content to file.

A more intuitive moniker for this Anchor tag would be "Save", rather than "Download", preferably styled as a button, in similarity to the other function buttons in the page header.

This can be done by simply replacing the Anchor content, as:

<a href="" download="template.html" title="Save your HTML document" id="download"><button>Save</button></a>

Also, the footer links, except for "Feedback" and "Created by", are all getting jumbled on top of each other by Firefox Nightly on linux, which does not seem to handle the img alt attribute gracefully. One alternative is to add float: left; overflow: hidden; to the img style. A better looking alternative is to move the img alt attribute text into the anchor content proper, after the img tag. Hmm - the coding style in the page is often using the same exact text string as 1) an HTML Element or 2) an element Attribute or 3) a Document Object Model Property for 4) an element ID, and 5) a javascript Variable. I find that this style can be misleading and prefer visually distinguishing the strings for an ID or Variable from an Element or Attribute or Property, and from each other.

In the "Reset" button function, the line download.download = 'template.html'; appears to be redundant, since this value has already been set in the corresponding Anchor Attribute. This value is not being changed globally by the "input file"/"Browse" button function, in download.download = file.name;.

I find the Anchor tag content "Download" used in the page header to be misleading. The connotation of the term "Download" is that this link will transfer some data from a remote location to the browser. But in fact, the exact opposite function is being performed, where the local browser content is instead being sent to a "remote" location. In this case, the "local" content is being "downloaded" and the user, usually, is then prompted to save their local content to file.

A more intuitive moniker for this Anchor tag would be "Save", rather than "Download", preferably styled as a button, in similarity to the other function buttons in the page header.

This can be done by simply replacing the Anchor content, as:

<a href="" download="template.html" title="Save your HTML document" id="download"><button>Save</button></a>
Edited the code snippet to include solution for MathJax scrolling
Source Link
const run = document.getElementById('run'),
  textarea = document.querySelector('textarea'),
  download = document.getElementById('download'),
  chooseFile = document.getElementById('chooseFile');

var mjax = false;

function preview() {
  if (!mjax) {
    ifpreview();
  } else {
    console.log("mjax = " + mjax);
    document.querySelector('iframe').addEventListener("mouseenter", ifpreview);
  }
}

function ifpreview() {
  if (run.checked) {

    let myIframe = document.querySelector('iframe'); 

    let xscroll = myIframe.contentWindow.scrollX;
    let yscroll = myIframe.contentWindow.scrollY;

    console.log("ifpreview mjax = " + mjax);
    const iframex = document.createElement('iframe'); // a fresh iframe to delete JavaScript variables
    document.querySelector('iframe').replaceWith(iframex);
    iframex.contentWindow.mjx = {
      xscroll,
      yscroll
    };

    const iframeDoc = iframex.contentDocument;
    iframeDoc.write(textarea.value);
    iframeDoc.close();

    document.querySelector('iframe').contentWindow.scrollTo({
      left: xscroll,
      top: yscroll,
      behavior: "instant",
    });

  }
}

textarea.addEventListener('input', preview);

run.addEventListener('change', preview);

for (const eventx of ['click', 'contextmenu']) {
  download.addEventListener(eventx, function() {
    const blob = new Blob([textarea.value], {
      type: 'text/html; charset=utf-8'
    });
    this.href = URL.createObjectURL(blob);
  });
}

document.getElementById('fontSize').addEventListener('change', function() {
  textarea.style.fontSize = this.value + 'px';
});

document.getElementById('reset').addEventListener('click', function() {
  if (!textarea.value || textarea.value != textarea.defaultValue && confirm('Your input will be lost.\nAre you sure you want to reset?')) {
    chooseFile.value = '';
    textarea.value = textarea.defaultValue;
    preview();
  }
});

document.getElementById('select').addEventListener('click', function() {
  textarea.select();
});

chooseFile.addEventListener('change', async function() {
  const file = this.files[0];
  if (file) { // to ensure that there's a file to read so Chrome, for example, doesn't run this function when you cancel choosing a new file
    download.download = file.name;
    textarea.value = await file.text();
    preview();
  }
});

document.getElementById('textareaSize').addEventListener('input', function() {
  const range = this.value;
  document.getElementById('textareaWrap').style.flexGrow = range;
  document.getElementById('iframeWrap').style.flexGrow = 100 - range;
});

document.getElementById('viewsToggle').addEventListener('change', function() {
  document.querySelector('main').classList.toggle('horizontal');
});

document.getElementById('themesToggle').addEventListener('change', function() {
  textarea.classList.toggle('dark');
});

document.getElementById('footerToggle').addEventListener('click', function() {
  this.classList.toggle('on');
  document.querySelector('footer').toggleAttribute('hidden');
});

document.getElementById('copy').addEventListener('click', function() {
  navigator.clipboard.writeText(location);

  function toggleNotification() {
    document.getElementById('notification').toggleAttribute('hidden');
  }
  toggleNotification();
  setTimeout(toggleNotification, 1500);
});

window.addEventListener('beforeunload', function(event) {
  if (textarea.value && textarea.value != textarea.defaultValue) {
    event.preventDefault();
    event.returnValue = '';
  }
});

preview();
html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: #777;
}

body {
  display: flex;
  flex-direction: column;
}

header,
footer:not([hidden]) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  padding: 5px;
}

header {
  background: linear-gradient(#FFF, #CCC);
}

label,
#download,
#fontSize,
#reset,
#select,
#chooseFile,
output,
span {
  font: bold 11px Arial;
  color: #333;
}

[type="checkbox"] {
  margin: 0 5px 0 0;
}

[for="fontSize"] {
  margin-left: 5px;
}

#fontSize,
button,
#textareaSize {
  margin: 0;
}

#chooseFile {
  margin: 0 auto 0 0;
}

#textareaSize,
iframe {
  padding: 0;
}

output {
  margin-right: 5px;
  font-family: monospace;
}

#footerToggle {
  width: 16px;
  height: 16px;
  border: 1px solid #666;
  border-bottom-width: 5px;
  padding: 0;
  background: transparent;
}

#footerToggle.on {
  border-color: #333;
  background: #FFF;
}

main {
  flex: 1;
  display: flex;
  background: #777;
}

main.horizontal {
  flex-direction: column;
}

div {
  flex: 0px;
  position: relative;
}

div * {
  position: absolute;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  background: #FFF;
}

#iframeWrap {
  border-left: 5px solid #CCC;
}

main.horizontal #iframeWrap {
  border-left: 0;
  border-top: 5px solid #CCC;
}

textarea {
  box-sizing: border-box;
  padding: 5px;
  outline: 0;
  resize: none;
  font-size: 14px;
  color: #333;
}

textarea.dark {
  background: #333;
  color: #FFF;
}

footer {
  background: linear-gradient(#CCC, #FFF);
}

img {
  display: block;
  float: left;
  overflow: hidden;
}

#copy {
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
}

address {
  margin-left: auto;
  font: italic 16px 'Times New Roman';
  color: #333;
}

address a {
  color: inherit;
}
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="Edit your HTML, CSS, and JavaScript code and monitor the instant live preview.">
  <title>HTML Editor: online HTML editor with real-time preview</title>
  <link rel="icon" href="favicon.ico">
</head>

<body>

  <header>
    <label for="run">Run</label>
    <input type="checkbox" id="run" checked>
    <a href="" download="template.html" title="Save your HTML document" id="download"><button>Save</button></a>
    <label for="fontSize">Font size</label>
    <select id="fontSize">
      <option>12</option>
      <option>13</option>
      <option selected>14</option>
      <option>15</option>
      <option>16</option>
      <option>17</option>
      <option>18</option>
      <option>19</option>
      <option>20</option>
    </select>
    <button type="button" id="reset">Reset</button>
    <button type="button" id="select">Select</button>
    <input type="file" accept="text/html" id="chooseFile">

    <label for="viewsToggle">Horizontal view</label>
    <input type="checkbox" id="viewsToggle">
    <label for="themesToggle">Dark theme</label>
    <input type="checkbox" id="themesToggle">
    <button type="button" title="Toggle footer" id="footerToggle"></button>

    <input type="range" id="textareaSize" style="width:100%;height:5px;">

  </header>

  <main>
    <div id="textareaWrap">

      <textarea spellcheck="false">

<!doctype html>
<html lang="en">
<head>
  <title>HTML Document Template</title>
  <style>
    p {
      font-family: Arial;
    }
  </style>
</head>

<body>
  <p>Hello, world!</p>

  <p>While editing documents containing MathJax markup, uncheck the "Run" checkbox, or run MathJax
  typesetting conditionally, by moving the mouse cursor into, or outof and into, the rendered page window, and
  including the following Javascipt in the head of your document:</p>

  <div style="background: #f7fff7">
  <pre>
&amp;lt;head>
...
&amp;lt;script>
  MathJax = {

// run scrollTo() a second time after MathJax typesetting is complete
    startup: {
      pageReady: () => {
        return MathJax.startup.defaultPageReady().then(() => {
          console.log("Second Scroll: " + mjx.xscroll + " " + mjx.yscroll);
          scrollTo({
            left: mjx.xscroll,
            top: mjx.yscroll,
            behavior: "smooth"
          });
        });
      }
    },
  ...
  };
...
&amp;lt;/script>
...
&amp;lt;script>
  document.querySelector('html').onmouseenter = function() {
   var scriptx = document.createElement('script');
   scriptx.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js';
//   scriptx.src = 'mathjax/tex-chtml-full.js';
   scriptx.async = true;
   document.head.appendChild(scriptx);
  };
&amp;lt;/script>
...
&amp;lt;/head>
  </pre>
  </div>

  <p>Test your browser's scroll position stability while editing by watching the position of these long
  lines:</p>

aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>
ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>

<script>
    console.log(document.querySelector('p').textContent);
  </script>
</body>
</html>

      </textarea>

    </div>

    <div id="iframeWrap">
      <iframe></iframe>
    </div>

  </main>

  <footer hidden>
    <span>Share</span>

    <a href="https://twitter.com/intent/tweet?text=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview&url=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank">
      <img src="images/twitter.svg" width="16" height="16">Twitter</a>
    <a href="https://www.facebook.com/sharer.php?u=https%3A%2F%2Fhtmleditor.gitlab.io&t=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview" target="_blank">
      <img src="images/facebook.svg" width="16" height="16">Facebook</a>
    <a href="https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank">
      <img src="images/linkedin.svg" width="16" height="16">LinkedIn</a>
    <a href="mailto:?subject=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview&body=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank">
      <img src="images/email.svg" width="16" height="16">Email</a>

    <button type="button" id="copy">
     <img src="images/link.svg" width="16" height="16">Link</button>
    <span id="notification" hidden>Copied!</span>

    <address><a href="https://codereview.stackexchange.com/questions/284918/html-editor-online-html-editor-with-real-time-preview"
      title="Code Review Stack Exchange">Feedback</a> | Created by <a href="https://mori.pages.dev" rel="author">Mori</a></address>
  </footer>

</body>

</html>
const run = document.getElementById('run'),
  textarea = document.querySelector('textarea'),
  download = document.getElementById('download'),
  chooseFile = document.getElementById('chooseFile');

function preview() {
  if (run.checked) {

    let myIframe = document.querySelector('iframe');
    let xscroll = myIframe.contentWindow.scrollX;
    let yscroll = myIframe.contentWindow.scrollY;

    const iframex = document.createElement('iframe'); // a fresh iframe to delete JavaScript variables
    document.querySelector('iframe').replaceWith(iframex);

    const iframeDoc = iframex.contentDocument;
    iframeDoc.write(textarea.value);
    iframeDoc.close();

    document.querySelector('iframe').contentWindow.scrollTo({
      left: xscroll,
      top: yscroll,
      behavior: "instant",
    });

  }
}

textarea.addEventListener('input', preview);

run.addEventListener('change', preview);

for (const eventx of ['click', 'contextmenu']) {
  download.addEventListener(eventx, function() {
    const blob = new Blob([textarea.value], {
      type: 'text/html; charset=utf-8'
    });
    this.href = URL.createObjectURL(blob);
  });
}

document.getElementById('fontSize').addEventListener('change', function() {
  textarea.style.fontSize = this.value + 'px';
});

document.getElementById('reset').addEventListener('click', function() {
  if (!textarea.value || textarea.value != textarea.defaultValue && confirm('Your input will be lost.\nAre you sure you want to reset?')) {
    chooseFile.value = '';
    textarea.value = textarea.defaultValue;
    preview();
  }
});

document.getElementById('select').addEventListener('click', function() {
  textarea.select();
});

chooseFile.addEventListener('change', async function() {
  const file = this.files[0];
  if (file) { // to ensure that there's a file to read so Chrome, for example, doesn't run this function when you cancel choosing a new file
    download.download = file.name;
    textarea.value = await file.text();
    preview();
  }
});

document.getElementById('textareaSize').addEventListener('input', function() {
  const range = this.value;
  document.getElementById('textareaWrap').style.flexGrow = range;
  document.getElementById('iframeWrap').style.flexGrow = 100 - range;
});

document.getElementById('viewsToggle').addEventListener('change', function() {
  document.querySelector('main').classList.toggle('horizontal');
});

document.getElementById('themesToggle').addEventListener('change', function() {
  textarea.classList.toggle('dark');
});

document.getElementById('footerToggle').addEventListener('click', function() {
  this.classList.toggle('on');
  document.querySelector('footer').toggleAttribute('hidden');
});

document.getElementById('copy').addEventListener('click', function() {
  navigator.clipboard.writeText(location);

  function toggleNotification() {
    document.getElementById('notification').toggleAttribute('hidden');
  }
  toggleNotification();
  setTimeout(toggleNotification, 1500);
});

window.addEventListener('beforeunload', function(event) {
  if (textarea.value && textarea.value != textarea.defaultValue) {
    event.preventDefault();
    event.returnValue = '';
  }
});

preview();
html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: #777;
}

body {
  display: flex;
  flex-direction: column;
}

header,
footer:not([hidden]) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  padding: 5px;
}

header {
  background: linear-gradient(#FFF, #CCC);
}

label,
#download,
#fontSize,
#reset,
#select,
#chooseFile,
output,
span {
  font: bold 11px Arial;
  color: #333;
}

[type="checkbox"] {
  margin: 0 5px 0 0;
}

[for="fontSize"] {
  margin-left: 5px;
}

#fontSize,
button,
#textareaSize {
  margin: 0;
}

#chooseFile {
  margin: 0 auto 0 0;
}

#textareaSize,
iframe {
  padding: 0;
}

output {
  margin-right: 5px;
  font-family: monospace;
}

#footerToggle {
  width: 16px;
  height: 16px;
  border: 1px solid #666;
  border-bottom-width: 5px;
  padding: 0;
  background: transparent;
}

#footerToggle.on {
  border-color: #333;
  background: #FFF;
}

main {
  flex: 1;
  display: flex;
  background: #777;
}

main.horizontal {
  flex-direction: column;
}

div {
  flex: 0px;
  position: relative;
}

div * {
  position: absolute;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  background: #FFF;
}

#iframeWrap {
  border-left: 5px solid #CCC;
}

main.horizontal #iframeWrap {
  border-left: 0;
  border-top: 5px solid #CCC;
}

textarea {
  box-sizing: border-box;
  padding: 5px;
  outline: 0;
  resize: none;
  font-size: 14px;
  color: #333;
}

textarea.dark {
  background: #333;
  color: #FFF;
}

footer {
  background: linear-gradient(#CCC, #FFF);
}

img {
  display: block;
  float: left;
  overflow: hidden;
}

#copy {
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
}

address {
  margin-left: auto;
  font: italic 16px 'Times New Roman';
  color: #333;
}

address a {
  color: inherit;
}
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="Edit your HTML, CSS, and JavaScript code and monitor the instant live preview.">
  <title>HTML Editor: online HTML editor with real-time preview</title>
  <link rel="icon" href="favicon.ico">
</head>

<body>

  <header>
    <label for="run">Run</label>
    <input type="checkbox" id="run" checked>
    <a href="" download="template.html" title="Save your HTML document" id="download"><button>Save</button></a>
    <label for="fontSize">Font size</label>
    <select id="fontSize">
      <option>12</option>
      <option>13</option>
      <option selected>14</option>
      <option>15</option>
      <option>16</option>
      <option>17</option>
      <option>18</option>
      <option>19</option>
      <option>20</option>
    </select>
    <button type="button" id="reset">Reset</button>
    <button type="button" id="select">Select</button>
    <input type="file" accept="text/html" id="chooseFile">

    <label for="viewsToggle">Horizontal view</label>
    <input type="checkbox" id="viewsToggle">
    <label for="themesToggle">Dark theme</label>
    <input type="checkbox" id="themesToggle">
    <button type="button" title="Toggle footer" id="footerToggle"></button>

    <input type="range" id="textareaSize" style="width:100%;height:5px;">

  </header>

  <main>
    <div id="textareaWrap">

      <textarea spellcheck="false">

<!doctype html>
<html lang="en">
<head>
  <title>HTML Document Template</title>
  <style>
    p {
      font-family: Arial;
    }
  </style>
</head>

<body>
  <p>Hello, world!</p>

  <p>While editing documents containing MathJax markup, uncheck the "Run" checkbox, or run MathJax
  typesetting conditionally, by moving the mouse cursor outof and into the rendered page window, and
  including the following Javascipt in the head of your document:</p>

  <div style="background: #f7fff7">
  <pre>
&amp;lt;head>
...
&amp;lt;script>
  document.querySelector('html').onmouseenter = function() {
   var scriptx = document.createElement('script');
   scriptx.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js';
//   scriptx.src = 'mathjax/tex-chtml-full.js';
   scriptx.async = true;
   document.head.appendChild(scriptx);
  };
&amp;lt;/script>
...
&amp;lt;/head>
  </pre>
  </div>

  <p>Test your browser's scroll position stability while editing by watching the position of these long
  lines:</p>

aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>
ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>

<script>
    console.log(document.querySelector('p').textContent);
  </script>
</body>
</html>

      </textarea>

    </div>

    <div id="iframeWrap">
      <iframe></iframe>
    </div>

  </main>

  <footer hidden>
    <span>Share</span>

    <a href="https://twitter.com/intent/tweet?text=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview&url=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank">
      <img src="images/twitter.svg" width="16" height="16">Twitter</a>
    <a href="https://www.facebook.com/sharer.php?u=https%3A%2F%2Fhtmleditor.gitlab.io&t=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview" target="_blank">
      <img src="images/facebook.svg" width="16" height="16">Facebook</a>
    <a href="https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank">
      <img src="images/linkedin.svg" width="16" height="16">LinkedIn</a>
    <a href="mailto:?subject=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview&body=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank">
      <img src="images/email.svg" width="16" height="16">Email</a>

    <button type="button" id="copy">
     <img src="images/link.svg" width="16" height="16">Link</button>
    <span id="notification" hidden>Copied!</span>

    <address><a href="https://codereview.stackexchange.com/questions/284918/html-editor-online-html-editor-with-real-time-preview"
      title="Code Review Stack Exchange">Feedback</a> | Created by <a href="https://mori.pages.dev" rel="author">Mori</a></address>
  </footer>

</body>

</html>
const run = document.getElementById('run'),
  textarea = document.querySelector('textarea'),
  download = document.getElementById('download'),
  chooseFile = document.getElementById('chooseFile');

var mjax = false;

function preview() {
  if (!mjax) {
    ifpreview();
  } else {
    console.log("mjax = " + mjax);
    document.querySelector('iframe').addEventListener("mouseenter", ifpreview);
  }
}

function ifpreview() {
  if (run.checked) {

    let myIframe = document.querySelector('iframe'); 

    let xscroll = myIframe.contentWindow.scrollX;
    let yscroll = myIframe.contentWindow.scrollY;

    console.log("ifpreview mjax = " + mjax);
    const iframex = document.createElement('iframe'); // a fresh iframe to delete JavaScript variables
    document.querySelector('iframe').replaceWith(iframex);
    iframex.contentWindow.mjx = {
      xscroll,
      yscroll
    };

    const iframeDoc = iframex.contentDocument;
    iframeDoc.write(textarea.value);
    iframeDoc.close();

    document.querySelector('iframe').contentWindow.scrollTo({
      left: xscroll,
      top: yscroll,
      behavior: "instant"
    });

  }
}

textarea.addEventListener('input', preview);

run.addEventListener('change', preview);

for (const eventx of ['click', 'contextmenu']) {
  download.addEventListener(eventx, function() {
    const blob = new Blob([textarea.value], {
      type: 'text/html; charset=utf-8'
    });
    this.href = URL.createObjectURL(blob);
  });
}

document.getElementById('fontSize').addEventListener('change', function() {
  textarea.style.fontSize = this.value + 'px';
});

document.getElementById('reset').addEventListener('click', function() {
  if (!textarea.value || textarea.value != textarea.defaultValue && confirm('Your input will be lost.\nAre you sure you want to reset?')) {
    chooseFile.value = '';
    textarea.value = textarea.defaultValue;
    preview();
  }
});

document.getElementById('select').addEventListener('click', function() {
  textarea.select();
});

chooseFile.addEventListener('change', async function() {
  const file = this.files[0];
  if (file) { // to ensure that there's a file to read so Chrome, for example, doesn't run this function when you cancel choosing a new file
    download.download = file.name;
    textarea.value = await file.text();
    preview();
  }
});

document.getElementById('textareaSize').addEventListener('input', function() {
  const range = this.value;
  document.getElementById('textareaWrap').style.flexGrow = range;
  document.getElementById('iframeWrap').style.flexGrow = 100 - range;
});

document.getElementById('viewsToggle').addEventListener('change', function() {
  document.querySelector('main').classList.toggle('horizontal');
});

document.getElementById('themesToggle').addEventListener('change', function() {
  textarea.classList.toggle('dark');
});

document.getElementById('footerToggle').addEventListener('click', function() {
  this.classList.toggle('on');
  document.querySelector('footer').toggleAttribute('hidden');
});

document.getElementById('copy').addEventListener('click', function() {
  navigator.clipboard.writeText(location);

  function toggleNotification() {
    document.getElementById('notification').toggleAttribute('hidden');
  }
  toggleNotification();
  setTimeout(toggleNotification, 1500);
});

window.addEventListener('beforeunload', function(event) {
  if (textarea.value && textarea.value != textarea.defaultValue) {
    event.preventDefault();
    event.returnValue = '';
  }
});

preview();
html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: #777;
}

body {
  display: flex;
  flex-direction: column;
}

header,
footer:not([hidden]) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  padding: 5px;
}

header {
  background: linear-gradient(#FFF, #CCC);
}

label,
#download,
#fontSize,
#reset,
#select,
#chooseFile,
output,
span {
  font: bold 11px Arial;
  color: #333;
}

[type="checkbox"] {
  margin: 0 5px 0 0;
}

[for="fontSize"] {
  margin-left: 5px;
}

#fontSize,
button,
#textareaSize {
  margin: 0;
}

#chooseFile {
  margin: 0 auto 0 0;
}

#textareaSize,
iframe {
  padding: 0;
}

output {
  margin-right: 5px;
  font-family: monospace;
}

#footerToggle {
  width: 16px;
  height: 16px;
  border: 1px solid #666;
  border-bottom-width: 5px;
  padding: 0;
  background: transparent;
}

#footerToggle.on {
  border-color: #333;
  background: #FFF;
}

main {
  flex: 1;
  display: flex;
  background: #777;
}

main.horizontal {
  flex-direction: column;
}

div {
  flex: 0px;
  position: relative;
}

div * {
  position: absolute;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  background: #FFF;
}

#iframeWrap {
  border-left: 5px solid #CCC;
}

main.horizontal #iframeWrap {
  border-left: 0;
  border-top: 5px solid #CCC;
}

textarea {
  box-sizing: border-box;
  padding: 5px;
  outline: 0;
  resize: none;
  font-size: 14px;
  color: #333;
}

textarea.dark {
  background: #333;
  color: #FFF;
}

footer {
  background: linear-gradient(#CCC, #FFF);
}

img {
  display: block;
  float: left;
  overflow: hidden;
}

#copy {
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
}

address {
  margin-left: auto;
  font: italic 16px 'Times New Roman';
  color: #333;
}

address a {
  color: inherit;
}
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="Edit your HTML, CSS, and JavaScript code and monitor the instant live preview.">
  <title>HTML Editor: online HTML editor with real-time preview</title>
  <link rel="icon" href="favicon.ico">
</head>

<body>

  <header>
    <label for="run">Run</label>
    <input type="checkbox" id="run" checked>
    <a href="" download="template.html" title="Save your HTML document" id="download"><button>Save</button></a>
    <label for="fontSize">Font size</label>
    <select id="fontSize">
      <option>12</option>
      <option>13</option>
      <option selected>14</option>
      <option>15</option>
      <option>16</option>
      <option>17</option>
      <option>18</option>
      <option>19</option>
      <option>20</option>
    </select>
    <button type="button" id="reset">Reset</button>
    <button type="button" id="select">Select</button>
    <input type="file" accept="text/html" id="chooseFile">

    <label for="viewsToggle">Horizontal view</label>
    <input type="checkbox" id="viewsToggle">
    <label for="themesToggle">Dark theme</label>
    <input type="checkbox" id="themesToggle">
    <button type="button" title="Toggle footer" id="footerToggle"></button>

    <input type="range" id="textareaSize" style="width:100%;height:5px;">

  </header>

  <main>
    <div id="textareaWrap">

      <textarea spellcheck="false">

<!doctype html>
<html lang="en">
<head>
  <title>HTML Document Template</title>
  <style>
    p {
      font-family: Arial;
    }
  </style>
</head>

<body>
  <p>Hello, world!</p>

  <p>While editing documents containing MathJax markup, uncheck the "Run" checkbox, or run MathJax
  typesetting conditionally, by moving the mouse cursor into, or outof and into, the rendered page window, and
  including the following Javascipt in the head of your document:</p>

  <div style="background: #f7fff7">
  <pre>
&amp;lt;head>
...
&amp;lt;script>
  MathJax = {

// run scrollTo() a second time after MathJax typesetting is complete
    startup: {
      pageReady: () => {
        return MathJax.startup.defaultPageReady().then(() => {
          console.log("Second Scroll: " + mjx.xscroll + " " + mjx.yscroll);
          scrollTo({
            left: mjx.xscroll,
            top: mjx.yscroll,
            behavior: "smooth"
          });
        });
      }
    },
  ...
  };
...
&amp;lt;/script>
...
&amp;lt;script>
  document.querySelector('html').onmouseenter = function() {
   var scriptx = document.createElement('script');
   scriptx.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js';
//   scriptx.src = 'mathjax/tex-chtml-full.js';
   scriptx.async = true;
   document.head.appendChild(scriptx);
  };
&amp;lt;/script>
...
&amp;lt;/head>
  </pre>
  </div>

  <p>Test your browser's scroll position stability while editing by watching the position of these long
  lines:</p>

aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>aa<br>
ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>bb<br>

<script>
    console.log(document.querySelector('p').textContent);
  </script>
</body>
</html>

      </textarea>

    </div>

    <div id="iframeWrap">
      <iframe></iframe>
    </div>

  </main>

  <footer hidden>
    <span>Share</span>

    <a href="https://twitter.com/intent/tweet?text=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview&url=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank">
      <img src="images/twitter.svg" width="16" height="16">Twitter</a>
    <a href="https://www.facebook.com/sharer.php?u=https%3A%2F%2Fhtmleditor.gitlab.io&t=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview" target="_blank">
      <img src="images/facebook.svg" width="16" height="16">Facebook</a>
    <a href="https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank">
      <img src="images/linkedin.svg" width="16" height="16">LinkedIn</a>
    <a href="mailto:?subject=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview&body=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank">
      <img src="images/email.svg" width="16" height="16">Email</a>

    <button type="button" id="copy">
     <img src="images/link.svg" width="16" height="16">Link</button>
    <span id="notification" hidden>Copied!</span>

    <address><a href="https://codereview.stackexchange.com/questions/284918/html-editor-online-html-editor-with-real-time-preview"
      title="Code Review Stack Exchange">Feedback</a> | Created by <a href="https://mori.pages.dev" rel="author">Mori</a></address>
  </footer>

</body>

</html>
Edited to reflect a solution to the MathJax scroll problem
Source Link
Loading
remove a textarea div style used for experimenting
Source Link
Loading
move unicode notes, suggest "Save" button, coding style feedback, note redundant javascript
Source Link
Loading
clarify arbitrary variable name "eventx"
Source Link
Loading
added 2 characters in body
Source Link
Loading
added a complete "code snippet" for my version of the editor
Source Link
Loading
note bug report to MathJax about scroll position corruption
Source Link
Loading
added the word "the" in "the same position"
Source Link
Loading
Realized that real-time editing documents which use MathJax requires nothing extra from the real-time editor.
Source Link
Loading
added some specific code solutions and suggestions
Source Link
Loading
added a thought about determining when to re-run the MathJax typesetting function.
Source Link
Loading
Source Link
Loading