LiveEditor
组件实现
bash
npm install @babel/standalone
组件:
jsx
import React, { useState, useRef, useEffect } from "react";
import Editor from "@monaco-editor/react";
export default function LiveEditor({ code }) {
const htmlMatch = code.match(/<html[^>]*>([\s\S]*?)<\/html>/i);
const cssMatch = code.match(/<style[^>]*>([\s\S]*?)<\/style>/i);
const jsMatch = code.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
const [activeTab, setActiveTab] = useState("html");
const [codes, setCodes] = useState({
html: htmlMatch ? htmlMatch[1].trim() : "<h1>Hello</h1>",
css: cssMatch ? cssMatch[1].trim() : "h1 { color: green; }",
js: jsMatch ? jsMatch[1].trim() : "console.log('Hi');",
});
const iframeRef = useRef(null);
useEffect(() => {
const timeout = setTimeout(() => {
if (iframeRef.current) {
const doc = iframeRef.current.contentDocument;
doc.open();
doc.write(`
<html>
<head><style>${codes.css}</style></head>
<body>${codes.html}<script>${codes.js}<\/script></body>
</html>
`);
doc.close();
}
}, 300);
return () => clearTimeout(timeout);
}, [codes]);
return (
<section style={{ display: "flex", height: "500px", gap: "8px", marginTop: "1rem" }}>
<div style={{ flex: 1, display: "flex", flexDirection: "column", border: "1px solid #333" }}>
<div style={{ display: "flex", background: "#222" }}>
{["html", "css", "js"].map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
style={{
flex: 1,
padding: "6px 0",
background: activeTab === tab ? "#333" : "#222",
color: activeTab === tab ? "#fff" : "#aaa",
border: "none",
cursor: "pointer",
}}
>
{tab.toUpperCase()}
</button>
))}
</div>
<div style={{ flex: 1 }}>
<Editor
height="100%"
language={activeTab}
theme="vs-dark"
value={codes[activeTab]}
onChange={(v) => setCodes((p) => ({ ...p, [activeTab]: v }))}
options={{ automaticLayout: true, fontSize: 14, minimap: { enabled: false } }}
/>
</div>
</div>
<div style={{ flex: 1, border: "1px solid #333" }}>
<iframe ref={iframeRef} title="preview" style={{ width: "100%", height: "100%", border: "none" }} />
</div>
</section>
);
}
css
.live-editor {
display: flex;
gap: 12px;
height: 600px;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
}
/* 左边代码编辑区 */
.editor-panel {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.editor-group {
flex: 1;
display: flex;
flex-direction: column;
}
.editor-group label {
font-size: 14px;
font-weight: 600;
color: #444;
margin-bottom: 4px;
}
.editor-group textarea {
flex: 1;
resize: none;
padding: 8px 10px;
font-size: 13px;
border: 1px solid #ccc;
border-radius: 6px;
line-height: 1.4;
background: #1e1e1e;
color: #f8f8f2;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
}
.editor-group textarea:focus {
outline: none;
border-color: #007acc;
box-shadow: 0 0 0 2px rgba(0,122,204,0.3);
}
/* 右边预览区 */
.preview-panel {
flex: 1;
border: 1px solid #ccc;
border-radius: 6px;
overflow: hidden;
background: #fff;
box-shadow: 0 1px 5px rgba(0,0,0,0.1);
}
.preview-panel iframe {
width: 100%;
height: 100%;
border: none;
}
live-editor 代码块实现
remark