首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >HTML编辑器:具有实时预览功能的在线HTML编辑器

HTML编辑器:具有实时预览功能的在线HTML编辑器
EN

Code Review用户
提问于 2023-05-10 18:12:49
回答 1查看 259关注 0票数 3

概述

HTML编辑器是一个具有极简主义方法的在线HTML编辑器。编辑您的HTML、CSS和JavaScript代码,并监视即时实时预览。它还可以创建、打开和编辑其他类型的文本文件,如.txt.css.js.svg等。

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

源代码

代码语言:javascript
复制
var runner = document.getElementById('runner'),
  editor = document.getElementById('editor'),
  downloader = document.getElementById('downloader'),
  fileChooser = document.getElementById('fileChooser');

function preview() {
  if (runner.checked) {
    document.getElementById('viewer').srcdoc = editor.value;
  }
}

editor.addEventListener('input', preview);
runner.addEventListener('change', preview);

['click', 'contextmenu'].forEach(event => downloader.addEventListener(event, function() {
  var blob = new Blob([editor.value], {type: 'text/html'});
  this.href = URL.createObjectURL(blob);
}));

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

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

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

fileChooser.addEventListener('change', async function() {
  var 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
    downloader.download = file.name;
    editor.value = await file.text();
    preview();
  }
});

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

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

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

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

document.getElementById('copier').addEventListener('click', function() {
  navigator.clipboard.writeText('https://htmleditor.gitlab.io');
  function toggleNotification() {
    document.getElementById('notification').toggleAttribute('hidden');
  }
  toggleNotification();
  setTimeout(toggleNotification, 1500);
});

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

preview();
代码语言:javascript
复制
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,
#downloader,
select,
#resetter,
#selector,
#fileChooser,
output,
span {
  font: bold 11px Arial;
  color: #333;
}

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

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

select,
button,
#resizer {
  margin: 0;
}

#fileChooser {
  margin: 0 auto 0 0;
}

#resizer,
iframe {
  padding: 0;
}

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

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

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

main {
  flex: 1;
  display: flex;
}

main.horizontal {
  flex-direction: column;
}

div {
  flex: 0px;
  min-width: 0;
  min-height: 0;
}

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

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

div * {
  display: block;
  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;
}

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

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

address a {
  color: inherit;
}
代码语言:javascript
复制
<header>
  <label for="runner">Run</label>
  <input type="checkbox" id="runner" checked>
  <a href="" download="template.html" title="Download the HTML document" id="downloader">Download</a>
  <label for="fontSizer">Font size</label>
  <select id="fontSizer">
    <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="resetter">Reset</button>
  <button type="button" id="selector">Select</button>
  <input type="file" accept="text/html" id="fileChooser">
  <label for="resizer">Editor size</label>
  <input type="range" id="resizer">
  <output for="resizer" id="indicator">0.50</output>
  <label for="viewsToggler">Horizontal view</label>
  <input type="checkbox" id="viewsToggler">
  <label for="themesToggler">Dark theme</label>
  <input type="checkbox" id="themesToggler">
  <button type="button" title="Toggle footer" id="footerToggler"></button>
</header>
<main id="main">
  <div id="editorWrapper">
    <textarea spellcheck="false" id="editor"><!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="viewerWrapper">
    <iframe id="viewer"></iframe>
  </div>
</main>
<footer id="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" alt="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" 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="copier"><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/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>
EN

回答 1

Code Review用户

发布于 2023-05-13 16:48:24

我将检查您的GitLab存储库中的代码,因为它将这些代码放在一个文件中:

index.html

代码语言:javascript
复制
<!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">
  <style>
    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,
    #downloader,
    select,
    #resetter,
    #selector,
    #fileChooser,
    output,
    span {
      font: bold 11px Arial;
      color: #333;
    }

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

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

    select,
    button,
    #resizer {
      margin: 0;
    }

    #fileChooser {
      margin: 0 auto 0 0;
    }

    #resizer,
    iframe {
      padding: 0;
    }

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

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

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

    main {
      flex: 1;
      display: flex;
    }

    main.horizontal {
      flex-direction: column;
    }

    div {
      flex: 0px;
      min-width: 0;
      min-height: 0;
    }

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

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

    div * {
      display: block;
      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;
    }

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

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

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

<body>
  <header>
    <label for="runner">Run</label>
    <input type="checkbox" id="runner" checked>
    <a href="" download="template.html" title="Download the HTML document" id="downloader">Download</a>
    <label for="fontSizer">Font size</label>
    <select id="fontSizer">
      <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="resetter">Reset</button>
    <button type="button" id="selector">Select</button>
    <input type="file" accept="text/html" id="fileChooser">
    <label for="resizer">Editor size</label>
    <input type="range" id="resizer">
    <output for="resizer" id="indicator">0.50</output>
    <label for="viewsToggler">Horizontal view</label>
    <input type="checkbox" id="viewsToggler">
    <label for="themesToggler">Dark theme</label>
    <input type="checkbox" id="themesToggler">
    <button type="button" title="Toggle footer" id="footerToggler"></button>
  </header>
  <main id="main">
    <div id="editorWrapper">
      <textarea spellcheck="false" id="editor"><!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="viewerWrapper">
      <iframe id="viewer"></iframe>
    </div>
  </main>
  <footer id="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" alt="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" 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="copier"><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/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>
  <script>
    var runner = document.getElementById('runner'),
      editor = document.getElementById('editor'),
      downloader = document.getElementById('downloader'),
      fileChooser = document.getElementById('fileChooser');

    function preview() {
      if (runner.checked) {
        document.getElementById('viewer').srcdoc = editor.value;
      }
    }

    editor.addEventListener('input', preview);
    runner.addEventListener('change', preview);

    ['click', 'contextmenu'].forEach(event => downloader.addEventListener(event, function() {
      var blob = new Blob([editor.value], {type: 'text/html'});
      this.href = URL.createObjectURL(blob);
    }));

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

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

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

    fileChooser.addEventListener('change', async function() {
      var 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
        downloader.download = file.name;
        editor.value = await file.text();
        preview();
      }
    });

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

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

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

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

    document.getElementById('copier').addEventListener('click', function() {
      navigator.clipboard.writeText('https://htmleditor.gitlab.io');
      function toggleNotification() {
        document.getElementById('notification').toggleAttribute('hidden');
      }
      toggleNotification();
      setTimeout(toggleNotification, 1500);
    });

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

    preview();
  </script>
</body>

</html>

从上面开始。

  1. 大写的DOCTYPE显示您仍然在考虑支持HTML4或XHTML序列化。虽然大小写不敏感,但我看到的现代例子都是小写的。
  2. 虽然charset也是大小写不敏感,但保持小写似乎是现代惯例,就像在引导示例中一样。
  3. 将CSS分离到一个文件中,并按如下方式链接:<link rel="stylesheet" href="styles.css">
  4. 与类选择器相比,您更喜欢ID选择器,而IMO在这里忽略了这个选项。不要重复指定元素,而是创建一个CSS类并将其分配给您希望应用CSS的元素。例如,span元素内容被分配给其他7个唯一的元素,这不是一个好的实践。
  5. flex-directionflex-wrapalign-itemsflex属性在CSS 3.0时都无效。而且,0px也可以是0。下面是优化的CSS文件:

styles.css

代码语言:javascript
复制
html,body {
    height:100%;
    margin:0;
    padding:0
}

header,footer:not([hidden]) {
    display:flex;
    padding:5px
}

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

label,#downloader,select,#resetter,#selector,#fileChooser,output,span {
    color:#333;
    font:bold 11px Arial
}

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

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

select,button,#resizer {
    margin:0
}

#fileChooser {
    margin:0 auto 0 0
}

#resizer,iframe {
    padding:0
}

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

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

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

div {
    min-height:0;
    min-width:0
}

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

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

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

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

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

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

img {
    display:block
}

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

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

address a {
    color:inherit
}

body,main {
    display:flex
}
  1. 在HTML和JS上,最可怕的方面是用户输入的JS可以在浏览器中运行。在安全考虑方面,这可能为XSS攻击打开一扇门。幸运的是,这个项目是原始的HTML,没有外部依赖,并且运行客户端,所以任何现代的web浏览器都应该默认提供保护。然而,清除任何给定的HTML将是一个很好的实践。我还想看看其他的“最佳实践”。
  2. 目前还不清楚您针对的是哪个JS版本,但假设它位于ES2019的大致范围内,您应该使用let而不是var,以便将变量限定为封闭块。
  3. 您的preview函数实际上是一个main函数。创建一个适当的main函数来注册每个侦听器,以便您的元素选择器变量能够正确地限定作用域。这又和安全有关。
  4. 我会将function() {迭代与() => {交换,仅仅是因为它比较干净。
  5. 您的resetter事件侦听器(这里唯一重要的逻辑)可以通过首先检查编辑器值是否为默认值,然后将第一个块放置在一个else-if中,并将确认作为条件来简化。
  6. 将JS分离到一个文件中,并按如下方式链接:<script type="text/javascript" src="editor.js"></script>
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/284918

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档