HTML编辑器是具有最小化方法的在线HTML编辑器。编辑您的HTML,CSS和JavaScript代码并监视实时实时预览。

请查看源代码并提供反馈:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="description" content="An online HTML editor with real-time preview">
    <title>HTML Editor</title>
    <link rel="icon" href="favicon.ico" type="image/x-icon">
    <base target="_blank">
    <style>
        html,
        body {
            margin: 0;
            padding: 0;
            height: 100%;
        }
        body {
            display: -webkit-flex;
            /* WebKit prefixes are added to support Safari. */
            display: flex;
            -webkit-flex-direction: column;
            flex-direction: column;
        }
        header,
        .shown {
            display: -webkit-flex;
            display: flex;
            -webkit-align-items: center;
            align-items: center;
            padding: 5px;
        }
        header {
            background: linear-gradient(#FFF, #CCC);
        }
        #fileSaver,
        [type="button"],
        #fileChooser,
        label,
        span {
            font: bold 11px arial;
            color: #333;
        }
        #selector,
        #resizer,
        #viewsToggle,
        [title$="Twitter"] {
            margin-right: 5px;
            margin-left: 5px;
        }
        #fileSaver {
            margin-right: 5px;
        }
        #fileChooser,
        [title$="Facebook"] {
            margin-right: auto;
        }
        #resizer {
            margin-top: 0;
            margin-bottom: 0;
            padding: 0;
        }
        /* to remove the extra margins and padding in some browsers, e.g. IE11 */
        span {
            width: 35px;
        }
        #footerToggle {
            margin-right: 0;
            margin-left: 5px;
            border: 0;
            padding: 0;
            background: transparent;
        }
        main {
            display: -webkit-flex;
            display: flex;
            -webkit-flex: 1;
            flex: 1;
        }
        .horizontal {
            -webkit-flex-direction: column;
            flex-direction: column;
        }
        main * {
            margin: 0;
            -webkit-flex: 50;
            flex: 50;
            background: #FFF;
            min-height: 100%;
            /* to ensure that the flex items are stretched to use available space; IE11, for example, doesn't stretch the iframe. */
        }
        .horizontal * {
            min-width: 100%;
            min-height: 0;
            /* to get back to the initial value */
        }
        textarea {
            box-sizing: border-box;
            border: 0;
            outline: 0;
            padding: 5px;
            resize: none;
            overflow: auto;
            /* to remove the default scrollbar in IE11 */
        }
        .minSize {
            padding: 0;
        }
        iframe {
            border: solid #CCC;
            border-width: 0 0 0 5px;
            padding: 0;
        }
        .horizontal iframe {
            border-width: 5px 0 0;
        }
        .shown {
            background: linear-gradient(#CCC, #FFF);
        }
        img {
            display: block;
            width: 20px;
            height: 20px;
        }
        address,
        address a {
            color: #333;
        }
    </style>
</head>

<body>
    <header>
        <a download="myFile.html" title="Save as..." id="fileSaver">Save as...</a>
        <input type="button" value="Reset" id="resetter">
        <input type="button" value="Select" id="selector">
        <input type="file" accept="text/html" id="fileChooser">
        <label for="resizer">Text field size</label>
        <input type="range" id="resizer">
        <span id="indicator">50%</span> 
        <!-- The semantic element to use instead of span is output. But it's not supported in IE11. -->
        <label for="viewsToggle">Horizontal view</label>
        <input type="checkbox" id="viewsToggle">
        <input type="button" value="▲" title="Show footer" id="footerToggle">
    </header>
    <main id="main">
        <textarea spellcheck="false" id="editor"><!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>HTML Document Template</title>
  </head>
  <body>
    <p>Hello, world!</p>
  </body>
</html></textarea>
        <iframe id="viewer"></iframe>
    </main>
    <footer hidden id="footer">
        <a href="https://plus.google.com/share?url=http%3A%2F%2Fhtmleditor.gitlab.io%2F" title="Share on Google+">
            <img src="images/google+.png" alt="Google+">
        </a>
        <a href="https://twitter.com/share?text=HTML%20Editor&amp;url=http%3A%2F%2Fhtmleditor.gitlab.io%2F" title="Share on Twitter">
            <img src="images/twitter.png" alt="Twitter">
        </a>
        <a href="https://www.facebook.com/sharer.php?u=http%3A%2F%2Fhtmleditor.gitlab.io%2F" title="Share on Facebook">
            <img src="images/facebook.png" alt="Facebook">
        </a>
        <address><a href="feedback.html" title="Feedback">Feedback</a> / Created by <a href="https://plus.google.com/+MortezaMirmojarabian?rel=author" title="Google+ profile" rel="author">Mori</a></address>
    </footer>
    <script>
        var editor = document.getElementById('editor'),
            viewer = document.getElementById('viewer'),
            fileChooser = document.getElementById('fileChooser'),
            resizer = document.getElementById('resizer');

        function preview() {
            try {
                var viewerDoc = viewer.contentDocument;
                viewerDoc.open();
                viewerDoc.write(editor.value);
                viewerDoc.close();
            } catch (e) { // in case of iframe redirection to a different origin
                viewer.src = 'about:blank';
                setTimeout(preview, 4); // minimum delay
            }
        }
        preview();
        editor.oninput = preview;

        function createURL() {
            var blob = new Blob([editor.value], {
                type: 'text/html'
            });
            document.getElementById('fileSaver').href = window.URL.createObjectURL(blob);
        }
        createURL();
        editor.onchange = createURL;
        fileChooser.onclick = function () { // to empty the fileList so you can rechoose the same file
            this.value = '';
        };
        fileChooser.onchange = function () {
            var file = this.files[0],
                reader = new FileReader();
            if (file) { // to ensure that there's a file to read so IE11 doesn't run this function on clicking fileChooser before you choose a file
                reader.readAsText(file);
                reader.onload = function () {
                    editor.value = this.result;
                    preview();
                    createURL();
                };
            }
        };
        document.getElementById('viewsToggle').onchange = function () {
            document.getElementById('main').classList.toggle('horizontal');
        };
        resizer.oninput = resizer.onchange = function () { // The onchange property is added to support IE11.
            var resizerVal = this.value;
            editor.style.webkitFlex = resizerVal;
            editor.style.flex = resizerVal;
            viewer.style.webkitFlex = 100 - resizerVal;
            viewer.style.flex = 100 - resizerVal;
            document.getElementById('indicator').textContent = resizerVal + '%';
            if (resizerVal == 0) {
                editor.className = 'minSize';
            } else {
                editor.className = '';
            }
        };
        document.getElementById('selector').onclick = function () {
            editor.select();
        };
        document.getElementById('resetter').onclick = function () {
            if (!editor.value || editor.value != editor.defaultValue && confirm('Are you sure?')) {
                editor.value = editor.defaultValue;
                preview();
                createURL();
            }
        };
        document.getElementById('footerToggle').onclick = function () {
            var footerClasses = document.getElementById('footer').classList;
            footerClasses.toggle('shown');
            if (footerClasses.length) {
                this.value = '▼';
                this.title = 'Hide footer';
            } else {
                this.value = '▲';
                this.title = 'Show footer';
            }
        };
    </script>
</body>

</html>


#1 楼


考虑对文本区域的内容进行转义。看到缩进的</html>使我迷迷糊糊,而&lt;/html&gt却没有。这也是一个好习惯...顺便说一句,为什么代码缩进4个空格,但是默认textarea值却使用2个空格,这是有原因的吗?您需要捕获按键。

Internet Explorer 9及更低版本不支持<input type="range" />。在这种情况下,您的窗格将无法调整大小,并且Text field size控件将令人困惑(尽管功能正常)。在IE9中。 Blob不可用,并停止执行脚本。至少,您应该显示一条消息,指示不支持浏览器。



除此之外,代码似乎还不错。但是,从用户的角度来看:


Save as...控件是一个链接,但其他所有按钮都是按钮。这似乎有点不一致。
如果我选择一个文件然后重置,我希望它会重置为我选择的文件。如果不是这种情况,则窗体控件应重置为无值,因此我看到的是“未选择文件”,而不是我的文件名。没有理由隐藏页脚,更不用说隐藏默认情况下的页脚了。即使有,我也希望隐藏按钮位于页脚本身上。
如果可以使用分隔符而不是range输入来调整窗格的大小,那将是很好的选择。


编辑后,我又看了一眼,还注意到了一些其他内容:尽管您的file输入具有accept="text/html",但用户仍然可以选择上传任何文件。考虑在file.type中验证fileChooser.onchange()
如果选择文件之前比较好,如果我还没有保存更改,则会收到确认消息。
同样,如果我有未保存的更改,可以设置一个onbeforeunload吗?以下内容将实现最后两点。请注意,保存检测是幼稚的,因为据我所知,尚无法确定用户是否已将文件保存到磁盘。 (您还必须在changed = falsereader.onload()中设置resetter.onclick()。)

var changed = false;
editor.oninput = function() {
    changed = true;
    preview();
}
fileChooser.onclick = function() {
    return changed && confirm("Your changes will be lost if you select another file. Are you sure you want to continue?");
}
window.onbeforeunload = function() {
    return changed ? "You have unsaved changes. Are you sure you want to leave this page?" : undefined;
}
fileSaver.onclick = function() {
    changed = false;
}


评论


\ $ \ begingroup \ $
至少,您应该显示一条消息,指示不支持浏览器:如果@Mori检测到功能的功能,而不是检测到浏览器+版本,那会更好。
\ $ \ endgroup \ $
– ANeves认为SE是邪恶的
2014年7月8日在18:27



\ $ \ begingroup \ $
@Mori IE9用户的百分比比您想象的要大。 Caniuse声称IE9为2.6%,而Safari(您在评论中特别提到)仅为1.64%。可悲的事实是,IE8的普及率高达4.46%。那真是让人难以接受。另外,如果它被认为是允许用户设计原型的工具,那么某些浏览器无法用它进行测试这一事实是很重要的。
\ $ \ endgroup \ $
–cimmanon
2014年7月14日在11:09



\ $ \ begingroup \ $
@Schism:感谢您的建议,但是我不想在编辑器中增加/分散注意力,更不用说在查看器中看到编码结果的地方了:预览应该是纯净的。
\ $ \ endgroup \ $
–森
16年1月10日,下午3:18

\ $ \ begingroup \ $
“如果不是这种情况,那么表单控件应该重置为无值,因此我看到的是“未选择文件”,而不是我的文件名。说得通!更正! “页脚淋浴/顶盖三角形对我来说没有多大意义。”我找不到隐藏和显示页脚的更好方法。我决定隐藏页脚,因为它会分散注意力并且与HTML编辑器的主要任务没有直接关系。此外,它占用了一些可用于编码的空间。页脚上的内容,例如我的Google+个人资料,您一直都不需要。它们可能只使用了一次。
\ $ \ endgroup \ $
–森
17年5月22日在16:52

\ $ \ begingroup \ $
@Schism:根据规范,可以指定accept属性,以向用户代理提示将接受哪种文件类型。 accept属性过滤在“打开”对话框中看到的文件,因此您可以轻松地在混乱中找到并使用所需的文件,同时仍可以选择“所有文件”并查看/选择其他文件。那么,此属性并不意味着是一种验证工具。尽管HTML编辑器是HTML版本,但是您可以使用它来打开,编辑和保存任何文本文件。
\ $ \ endgroup \ $
–森
17年5月22日在17:04

#2 楼

总体而言,令人印象深刻的代码中有一些指针:


除了建议通过===与0比较之外,JsHint找不到任何内容
考虑使用addEventListener代替旧的skool onxxx

我会将preview();createURL();放在一起,而不是将它们放在事件处理程序分配的中间,而不是在var分配之后或在最底部。

或您甚至可以考虑假装单击“重新设置”来完成所有这些操作,如果您不使用addEventListener,则只需执行

document.getElementById('resetter').onclick();  



您的朋友在这里:

 if (resizerVal == 0) {
     editor.className = 'minSize';
 } else {
     editor.className = '';
 }


可以是

 editor.className = resizerVal ? '' : 'minSize';




评论


\ $ \ begingroup \ $
“我会将Preview();和createURL();放在一起”,在定义它们以提高代码可读性之后,我立即调用了这些函数。 “或者您甚至可以考虑假装点击“代理商”来完成所有这些工作。”我不确定您的意思。您介意提供样品吗? “三元可以在这里成为你的朋友”我对三元不熟悉,应该搜索一下。谢谢你的建议!
\ $ \ endgroup \ $
–森
2014年7月9日在9:01

\ $ \ begingroup \ $
现在,三元是链接,全名是三元运算符。添加了一个(伪造的)示例,该示例表示我虚假的单击。如果您想使用addEventListener,Google应该会为您提供帮助。
\ $ \ endgroup \ $
– konijn
2014年7月9日在13:01

\ $ \ begingroup \ $
刚刚了解了三元运算符。应该是editor.className = resizerVal == 0吗? 'minSize':'';。
\ $ \ endgroup \ $
–森
2014年7月10日在9:32

\ $ \ begingroup \ $
在我的示例中,我评估resizerVal本身,如果不为0,则为true;如果为0,则为false,请尝试。
\ $ \ endgroup \ $
– konijn
2014年7月10日在12:58

\ $ \ begingroup \ $
我没有用。我认为这是因为值是一个字符串,即使将其设置为零,也永远不会伪造。
\ $ \ endgroup \ $
–森
2014年7月11日下午0:19

#3 楼

主要更新:


用两个空格而不是四个空格缩进的代码
download属性值myFile.html更改为有意义的值:template.html

将新功能中的previewcreateURL函数
删除了fileChooser.onclick函数:此函数现在通过Reset按钮完成。
downloader.download = file.name;函数中添加了fileChooser.onchange,因此downloader download属性的值与导入文件的值相同名称。现在,文本字段内容,fileChooser值和downloader属性值之间存在逻辑关系。
download函数中,将resize更改为flex,因为flexGrowflexShrink从不更改
为< flexBasis按钮,因此它不仅可以重置文本字段,还可以重置ResetfileChooser属性值
如果修改了文本字段,则在页面退出时添加了确认消息
添加了新选项:downloader;进行了一些代码改进
downloadDark theme中添加了flex-wrap: wrap;,以便在需要时可以折叠flex项目
添加了运行停止切换开关:


有时您不应该t运行代码,直到完成编码,否则它将使浏览器崩溃。例如,编写循环时可能会导致无限循环。
例如,在使用CSS动画时,您可能想重新运行代码并重新查看结果。您可以通过双击“运行”复选框来实现它。


现在Edge支持header-将footer更改为outputObject.value

信用:

特别感谢Schism的详细指示!

最终源代码:

<!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">
  <base target="_blank">
  <style>
    html,
    body {
      margin: 0;
      padding: 0;
      height: 100%;
    }

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

    header,
    footer.shown {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      padding: 5px;
    }

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

    label,
    #downloader,
    [type="button"],
    #fileChooser,
    output {
      font: bold 11px Arial;
      color: #333;
    }

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

    #resetter,
    #resizer {
      margin: 0 5px;
    }

    #selector {
      margin: 0;
    }

    #fileChooser {
      margin: 0 auto 0 5px;
    }

    #resizer,
    iframe {
      padding: 0;
    }

    output {
      width: 3.5ch;
      margin-right: 10px;
    }

    #more {
      margin: 0;
      border: 0;
      padding: 0;
      background: transparent;
    }

    #more.on {
      border-bottom: 2px solid;
      margin-bottom: -2px;
    }

    main {
      flex: 1;
      display: flex;
    }

    main.horizontal {
      flex-direction: column;
    }

    div {
      flex-basis: 0;
      position: relative;
    }

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

    main.horizontal #viewerWrapper {
      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;
      color: #333;
    }

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

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

    [title$="Twitter"] {
      margin-left: 5px;
      margin-right: 5px;
    }

    img {
      display: block;
      width: 20px;
      height: 20px;
    }

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

    address a {
      color: inherit;
    }
  </style>
</head>

<body>
  <header>
    <label for="runner">Run</label>
    <input type="checkbox" checked id="runner">
    <a download="template.html" title="Download the HTML document" id="downloader">Download</a>
    <input type="button" value="Reset" id="resetter">
    <input type="button" value="Select" id="selector">
    <input type="file" accept="text/html" id="fileChooser">
    <label for="resizer">Editor size</label>
    <input type="range" id="resizer">
    <output for="resizer" id="indicator"></output>
    <label for="viewsToggler">Horizontal view</label>
    <input type="checkbox" id="viewsToggler">
    <label for="themesToggler">Dark theme</label>
    <input type="checkbox" id="themesToggler">
    <input type="button" value="•••" title="More" id="more">
  </header>
  <main id="main">
    <div id="editorWrapper">
      <textarea spellcheck="false" id="editor"><!DOCTYPE html>
<html lang="en">
  <head>
    <title>HTML Document Template</title>
  </head>
  <body>
    <p>Hello, world!</p>
  </body>
</html></textarea>
    </div>
    <div id="viewerWrapper">
      <iframe id="viewer"></iframe>
    </div>
  </main>
  <footer hidden id="footer">
    <a href="https://plus.google.com/share?url=http%3A%2F%2Fhtmleditor.gitlab.io%2F" title="Share on Google+">
      <img src="images/google+.png" alt="Google+">
    </a>
    <a href="https://twitter.com/share?text=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview&amp;url=http%3A%2F%2Fhtmleditor.gitlab.io%2F" title="Share on Twitter">
      <img src="images/twitter.png" alt="Twitter">
    </a>
    <a href="https://www.facebook.com/dialog/share?app_id=664554287087112&amp;href=http%3A%2F%2Fhtmleditor.gitlab.io%2F" title="Share on Facebook">
      <img src="images/facebook.png" alt="Facebook">
    </a>
    <address><a href="https://codereview.stackexchange.com/questions/56106/html-editor-online-html-editor-with-real-time-preview" title="javascript - HTML Editor: online HTML editor with real-time preview - Code Review Stack Exchange">Feedback</a> | Created by <a href="http://google.com/+MortezaMirmojarabian" title="Morteza Mirmojarabian - Google+" rel="author">Mori</a></address>
  </footer>
  <script>
    var runner = document.getElementById('runner'),
      editor = document.getElementById('editor'),
      downloader = document.getElementById('downloader'),
      fileChooser = document.getElementById('fileChooser'),
      resizer = document.getElementById('resizer'),
      viewsToggler = document.getElementById('viewsToggler'),
      themesToggler = document.getElementById('themesToggler');

    function preview() {
      if (runner.checked) {
        var viewer = document.getElementById('viewer');
        try {
          var viewerDoc = viewer.contentDocument;
          viewerDoc.open();
          viewerDoc.write(editor.value);
          viewerDoc.close();
        } catch (e) { // in case of iframe redirection to a different origin
          viewer.src = 'about:blank';
          setTimeout(preview, 4); // minimum delay
        }
      }
    }
    editor.addEventListener('input', preview);
    runner.addEventListener('change', preview);

    function createURL() {
      var blob = new Blob([editor.value], {
        type: 'text/html'
      });
      downloader.href = window.URL.createObjectURL(blob);
    }
    editor.addEventListener('change', createURL);

    function previewAndCreateURL() {
      preview();
      createURL();
    }

    document.getElementById('resetter').addEventListener('click', function() {
      if (!editor.value || editor.value != editor.defaultValue && confirm('Your changes will be lost.\nAre you sure you want to reset?')) {
        downloader.download = 'template.html';
        fileChooser.value = '';
        editor.value = editor.defaultValue;
        previewAndCreateURL();
      } else if (editor.value == editor.defaultValue) {
        downloader.download = 'template.html';
        fileChooser.value = '';
      }
    });

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

    fileChooser.addEventListener('change', function() {
      var file = this.files[0],
        reader = new FileReader();
      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
        downloader.download = file.name;
        reader.readAsText(file);
        reader.addEventListener('load', function() {
          editor.value = this.result;
          previewAndCreateURL();
        });
      }
    });

    function resize() {
      var resizerVal = resizer.value;
      document.getElementById('editorWrapper').style.flexGrow = resizerVal;
      document.getElementById('viewerWrapper').style.flexGrow = 100 - resizerVal;
      document.getElementById('indicator').value = (resizerVal / 100).toFixed(2);
    }
    resizer.addEventListener('input', resize);

    function toggleViews() {
      var main = document.getElementById('main');
      if (viewsToggler.checked) {
        main.className = 'horizontal';
      } else {
        main.className = '';
      }
    }
    viewsToggler.addEventListener('change', toggleViews);

    function toggleThemes() {
      if (themesToggler.checked) {
        editor.className = 'dark';
      } else {
        editor.className = '';
      }
    }
    themesToggler.addEventListener('change', toggleThemes);

    document.getElementById('more').addEventListener('click', function() {
      if (this.title == 'More') {
        this.title = 'Less';
      } else {
        this.title = 'More';
      }
      this.classList.toggle('on');
      document.getElementById('footer').classList.toggle('shown');
    });

    window.addEventListener('beforeunload', function(event) {
      if (editor.value && editor.value != editor.defaultValue) {
        event.returnValue = 'Your changes may be lost.';
      }
    });

    resize();
    toggleViews();
    toggleThemes();
    previewAndCreateURL();
  </script>
</body>

</html>


评论


\ $ \ begingroup \ $
我再次为麻烦感到抱歉。该常见问题解答还指出,您应该提及已更新的内容,因此它仍然是有效的代码审查。然后应将其标记为Community Wiki。
\ $ \ endgroup \ $
– Jamal♦
14年7月29日在5:04

\ $ \ begingroup \ $
“您应该提到更新的内容”嗯...实际上,我有些困惑:我没有在帖子中提到它吗?我已经明确指出,例如,重置功能已更改。如果您还有其他意思,您介意代表我这样做吗?再次感谢!
\ $ \ endgroup \ $
–森
2014年7月29日5:08



\ $ \ begingroup \ $
我的意思是每次更新都进行了个别更改(或者,如果所有建议都来自他们,则仅提及一个或多个答案)。
\ $ \ endgroup \ $
– Jamal♦
2014年7月29日在5:09

\ $ \ begingroup \ $
知道了!我会。 \ $ \ endgroup \ $
–森
14年7月29日在5:34