请查看源代码并提供反馈:
<!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&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>
使我迷迷糊糊,而</html>
却没有。这也是一个好习惯...顺便说一句,为什么代码缩进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 = false
和reader.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;
}
#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
将新功能中的
preview
和createURL
函数删除了
fileChooser.onclick
函数:此函数现在通过Reset
按钮完成。在
downloader.download = file.name;
函数中添加了fileChooser.onchange
,因此downloader
download
属性的值与导入文件的值相同名称。现在,文本字段内容,fileChooser
值和downloader
属性值之间存在逻辑关系。在
download
函数中,将resize
更改为flex
,因为flexGrow
和flexShrink
从不更改为<
flexBasis
按钮,因此它不仅可以重置文本字段,还可以重置Reset
和fileChooser
属性值如果修改了文本字段,则在页面退出时添加了确认消息
添加了新选项:
downloader
;进行了一些代码改进在
download
和Dark 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&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&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 \ $
知道了!我会。
–森
14年7月29日在5:34
评论
\ $ \ 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