Note
Please review the new question and ignore the following.
Overview
HTML Editor is an online HTML editor with a minimalist approach. Edit your HTML, CSS, and JavaScript code and monitor the instant live preview. It can also create, open and edit other types of text files such as .txt
, .css
, .js
, .svg
, etc.
Please review the code and provide feedback.
Source code
const run = document.getElementById('run'),
iframe = document.querySelector('iframe'),
textarea = document.querySelector('textarea'),
download = document.getElementById('download'),
chooseFile = document.getElementById('chooseFile'),
textareaWrap = document.getElementById('textareaWrap'),
iframeWrap = document.getElementById('iframeWrap'),
output = document.querySelector('output'),
main = document.querySelector('main'),
footer = document.querySelector('footer'),
notification = document.getElementById('notification');
function preview() {
if (run.checked) {
iframe.replaceWith(iframe); // a fresh iframe to delete JavaScript variables
const iframeDoc = iframe.contentDocument;
iframeDoc.write(textarea.value);
iframeDoc.close();
}
}
textarea.addEventListener('input', preview);
run.addEventListener('change', preview);
for (const event of ['click', 'contextmenu']) {
download.addEventListener(event, 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 = '';
download.download = 'template.html';
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;
textareaWrap.style.flexGrow = range;
iframeWrap.style.flexGrow = 100 - range;
output.value = (range / 100).toFixed(2);
});
document.getElementById('viewsToggle').addEventListener('change', function() {
main.classList.toggle('horizontal');
});
document.getElementById('themesToggle').addEventListener('change', function() {
textarea.classList.toggle('dark');
});
document.getElementById('footerToggle').addEventListener('click', function() {
this.classList.toggle('on');
footer.toggleAttribute('hidden');
});
document.getElementById('copy').addEventListener('click', function() {
navigator.clipboard.writeText(location);
function toggleNotification() {
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%;
}
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;
}
main.horizontal {
flex-direction: column;
}
div {
flex: 0px;
position: relative;
}
#iframeWrap {
border-left: 5px solid #CCC;
}
main.horizontal #iframeWrap {
border-left: 0;
border-top: 5px solid #CCC;
}
div * {
position: absolute;
width: 100%;
height: 100%;
margin: 0;
border: 0;
background: #FFF;
}
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;
}
#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;
}
<header>
<label for="run">Run</label>
<input type="checkbox" id="run" checked>
<a href="" download="template.html" title="Download the HTML document" id="download">Download</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="textareaSize">Textarea size</label>
<input type="range" id="textareaSize">
<output for="textareaSize">0.50</output>
<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>
</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>
<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/x.svg" width="16" height="16" alt="X"></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" alt="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" alt="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" alt="Email"></a>
<button type="button" id="copy"><img src="images/link.svg" width="16" height="16" alt="Link"></button>
<span id="notification" hidden>Copied!</span>
<address><a href="https://codereview.stackexchange.com/questions/291941/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>
id
andclass
selectors as kebab-case instead of camelCase. That's also what the Google HTML/CSS style guide recommends: hyphens for id and hyphens for class \$\endgroup\$const iframeWrap = document.getElementById('iframeWrap');
compared toconst iframeWrap = document.getElementById('iframe-wrap');
, for example. \$\endgroup\$