写在前面
网上很多格式化的工具,但是公司内网比较受限制,所以找了一个html的离线版的自己使用,如果你有更好用的离线版工具,也可以分享一下!
主要功能
-
多语言支持:
-
JavaScript(包括ES6+特性)
-
HTML
-
CSS
-
TypeScript
-
JSX
-
-
智能格式化:
-
自动缩进和空格处理
-
保留所有原始逻辑和注释
-
一致的代码风格
-
-
实用功能:
-
实时格式化(输入时自动更新)
-
复制结果到剪贴板
-
清空编辑器
-
显示字符数和行数统计
-
显示处理时间
-
-
响应式设计:
-
在桌面和移动设备上都能良好显示
-
小屏幕时自动切换为垂直布局
-
-
语法高亮:
-
基本语法高亮增强可读性
-
不同语言使用不同的颜色方案
-
使用说明
-
在左侧编辑器中输入或粘贴源代码
-
选择代码的语言类型(默认是JavaScript)
-
右侧会自动显示格式化后的结果
-
使用"复制结果"按钮复制格式化后的代码
-
使用"清空"按钮重置编辑器
这个工具使用Prettier作为格式化引擎,确保格式化过程不会修改任何逻辑、变量或注释,只调整代码的格式结构。
效果展示
html源码
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端代码格式化工具</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/prettier@2.8.8/standalone.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prettier@2.8.8/parser-babel.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prettier@2.8.8/parser-html.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prettier@2.8.8/parser-postcss.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prettier@2.8.8/parser-typescript.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
color: #333;
min-height: 100vh;
padding: 20px;
display: flex;
flex-direction: column;
}
.container {
max-width: 1600px;
margin: 0 auto;
width: 100%;
}
header {
text-align: center;
padding: 25px 0;
margin-bottom: 30px;
}
h1 {
color: white;
font-size: 2.8rem;
margin-bottom: 15px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.subtitle {
color: #e0e0ff;
font-size: 1.2rem;
max-width: 700px;
margin: 0 auto;
line-height: 1.6;
}
.card {
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
overflow: hidden;
margin-bottom: 30px;
}
.toolbar {
background: #2c3e50;
padding: 15px 25px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 15px;
}
.language-selector {
display: flex;
align-items: center;
gap: 10px;
}
.language-selector label {
color: #ecf0f1;
font-weight: 500;
font-size: 1.1rem;
}
.language-selector select {
background: #34495e;
color: white;
border: 2px solid #3498db;
border-radius: 8px;
padding: 8px 15px;
font-size: 1rem;
cursor: pointer;
outline: none;
transition: all 0.3s ease;
}
.language-selector select:focus {
border-color: #1abc9c;
box-shadow: 0 0 0 3px rgba(26, 188, 156, 0.3);
}
.actions {
display: flex;
gap: 15px;
}
.btn {
background: linear-gradient(to right, #3498db, #1abc9c);
color: white;
border: none;
border-radius: 8px;
padding: 10px 20px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.25);
}
.btn:active {
transform: translateY(1px);
}
.btn-outline {
background: transparent;
border: 2px solid #3498db;
color: #3498db;
}
.editor-container {
display: flex;
flex-wrap: wrap;
min-height: 500px;
}
.editor-panel {
flex: 1;
min-width: 300px;
padding: 20px;
border-right: 1px solid #eee;
display: flex;
flex-direction: column;
}
.editor-panel:last-child {
border-right: none;
}
.panel-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
color: #2c3e50;
font-weight: 600;
font-size: 1.2rem;
}
.editor {
flex: 1;
position: relative;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
textarea, pre {
width: 100%;
height: 100%;
padding: 20px;
font-family: 'Fira Code', 'Consolas', monospace;
font-size: 15px;
line-height: 1.6;
border: none;
outline: none;
resize: none;
background: #f8f9fa;
overflow: auto;
tab-size: 4;
}
pre {
white-space: pre-wrap;
word-break: break-word;
}
.stats {
display: flex;
gap: 25px;
margin-top: 15px;
color: #7f8c8d;
font-size: 0.9rem;
}
.stat-item {
display: flex;
align-items: center;
gap: 5px;
}
footer {
text-align: center;
color: rgba(255, 255, 255, 0.7);
padding: 30px 0;
margin-top: auto;
font-size: 0.95rem;
}
.features {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 30px;
margin: 30px 0;
}
.feature-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 25px;
width: 250px;
text-align: center;
transition: transform 0.3s ease;
}
.feature-card:hover {
transform: translateY(-5px);
background: rgba(255, 255, 255, 0.15);
}
.feature-card i {
font-size: 2.5rem;
color: #1abc9c;
margin-bottom: 15px;
}
.feature-card h3 {
color: white;
margin-bottom: 10px;
font-size: 1.4rem;
}
.feature-card p {
color: #e0e0ff;
line-height: 1.6;
}
@media (max-width: 768px) {
.editor-container {
flex-direction: column;
}
.editor-panel {
border-right: none;
border-bottom: 1px solid #eee;
min-height: 300px;
}
.toolbar {
flex-direction: column;
align-items: flex-start;
}
.actions {
width: 100%;
justify-content: center;
}
}
/* Syntax highlighting for pre tag */
.token.comment {
color: #6a9955;
}
.token.keyword {
color: #569cd6;
}
.token.string {
color: #ce9178;
}
.token.function {
color: #dcdcaa;
}
.token.tag {
color: #569cd6;
}
.token.attr-name {
color: #9cdcfe;
}
.token.attr-value {
color: #ce9178;
}
.token.punctuation {
color: #d4d4d4;
}
.token.operator {
color: #d4d4d4;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1><i class="fas fa-code"></i> 前端代码格式化工具</h1>
<p class="subtitle">输入任何前端源代码(HTML、CSS、JavaScript、JSX等),工具将自动格式化代码,保持原始逻辑、变量和注释不变。</p>
</header>
<div class="features">
<div class="feature-card">
<i class="fas fa-code"></i>
<h3>智能格式化</h3>
<p>自动识别代码结构,应用一致的缩进和空格</p>
</div>
<div class="feature-card">
<i class="fas fa-comments"></i>
<h3>保留注释</h3>
<p>所有注释保持不变,不会被修改或删除</p>
</div>
<div class="feature-card">
<i class="fas fa-sync-alt"></i>
<h3>实时更新</h3>
<p>随着输入自动更新格式化结果</p>
</div>
<div class="feature-card">
<i class="fas fa-language"></i>
<h3>多语言支持</h3>
<p>支持 JavaScript、HTML、CSS、JSX、TypeScript</p>
</div>
</div>
<div class="card">
<div class="toolbar">
<div class="language-selector">
<label for="language"><i class="fas fa-file-code"></i> 语言:</label>
<select id="language">
<option value="babel">JavaScript</option>
<option value="html">HTML</option>
<option value="css">CSS</option>
<option value="typescript">TypeScript</option>
<option value="babel">JSX</option>
</select>
</div>
<div class="actions">
<button id="copyBtn" class="btn"><i class="fas fa-copy"></i> 复制结果</button>
<button id="clearBtn" class="btn btn-outline"><i class="fas fa-eraser"></i> 清空</button>
</div>
</div>
<div class="editor-container">
<div class="editor-panel">
<div class="panel-header">
<i class="fas fa-edit"></i>
<h2>输入源代码</h2>
</div>
<div class="editor">
<textarea id="input" placeholder="在此处粘贴或输入源代码...">// 示例代码
function example(hello) {
// 这是一个示例函数
const message = "Hello, World!";
console.log(message);
// 返回计算结果
return {
status: 'success',
data: [1, 2, 3, 4, 5].map(n => n * 2)
};
}</textarea>
</div>
<div class="stats">
<div class="stat-item"><i class="fas fa-font"></i> 字符数: <span id="charCount">0</span></div>
<div class="stat-item"><i class="fas fa-grip-lines"></i> 行数: <span id="lineCount">0</span></div>
</div>
</div>
<div class="editor-panel">
<div class="panel-header">
<i class="fas fa-magic"></i>
<h2>格式化结果</h2>
</div>
<div class="editor">
<pre id="output"></pre>
</div>
<div class="stats">
<div class="stat-item"><i class="fas fa-sync-alt"></i> 状态: <span id="status">就绪</span></div>
<div class="stat-item"><i class="fas fa-history"></i> 处理时间: <span id="processTime">0ms</span></div>
</div>
</div>
</div>
</div>
<footer>
<p>© 2023 前端代码格式化工具 | 保留所有原始逻辑、变量和注释</p>
</footer>
</div>
<script>
// DOM元素
const inputEl = document.getElementById('input');
const outputEl = document.getElementById('output');
const languageEl = document.getElementById('language');
const copyBtn = document.getElementById('copyBtn');
const clearBtn = document.getElementById('clearBtn');
const charCountEl = document.getElementById('charCount');
const lineCountEl = document.getElementById('lineCount');
const statusEl = document.getElementById('status');
const processTimeEl = document.getElementById('processTime');
// 支持的解析器映射
const parserMap = {
'babel': 'babel',
'html': 'html',
'css': 'css',
'typescript': 'typescript'
};
// 初始化
function init() {
updateStats();
formatCode();
// 添加事件监听
inputEl.addEventListener('input', () => {
updateStats();
formatCode();
});
languageEl.addEventListener('change', formatCode);
copyBtn.addEventListener('click', copyToClipboard);
clearBtn.addEventListener('click', () => {
inputEl.value = '';
updateStats();
formatCode();
});
}
// 更新统计信息
function updateStats() {
const text = inputEl.value;
charCountEl.textContent = text.length;
lineCountEl.textContent = text.split('\n').length;
}
// 格式化代码
function formatCode() {
statusEl.textContent = '处理中...';
const startTime = performance.now();
try {
const code = inputEl.value;
const parser = parserMap[languageEl.value] || 'babel';
if (!code.trim()) {
outputEl.innerHTML = '';
statusEl.textContent = '就绪';
processTimeEl.textContent = '0ms';
return;
}
// 使用Prettier进行格式化
const formatted = prettier.format(code, {
parser: parser,
plugins: window.prettierPlugins,
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: 'all',
bracketSpacing: true,
arrowParens: 'always'
});
// 简单语法高亮(实际应用中可以使用highlight.js等库)
const highlighted = highlightCode(formatted, parser);
outputEl.innerHTML = highlighted;
const endTime = performance.now();
statusEl.textContent = '格式化成功';
processTimeEl.textContent = `${Math.round(endTime - startTime)}ms`;
} catch (error) {
outputEl.innerHTML = `<span style="color: #e74c3c;">格式化错误: ${error.message}</span>`;
statusEl.textContent = '错误';
processTimeEl.textContent = '0ms';
}
}
// 简单的语法高亮(仅用于演示)
function highlightCode(code, parser) {
// 实际应用中应使用专门的语法高亮库
// 这里使用简单的正则替换进行演示
if (parser === 'babel' || parser === 'typescript') {
return code
.replace(/(\/\/.*)/g, '<span class="token comment">$1</span>')
.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="token comment">$1</span>')
.replace(/\b(function|const|let|var|return|if|else|for|while|switch|case|break|class|extends|import|export|default|new|this)\b/g, '<span class="token keyword">$1</span>')
.replace(/(`[^`]*`|'[^']*'|"[^"]*")/g, '<span class="token string">$1</span>')
.replace(/\b([a-zA-Z_$][\w$]*)\s*\(/g, '<span class="token function">$1</span>(');
} else if (parser === 'html') {
return code
.replace(/<(\/?)([\w-]+)/g, '<$1<span class="token tag">$2</span>')
.replace(/(\s+[\w-]+)=/g, '<span class="token attr-name">$1</span>=')
.replace(/=(".*?"|'.*?')/g, '=<span class="token attr-value">$1</span>')
.replace(/(\/?)>/g, '$1<span class="token punctuation">></span>');
} else if (parser === 'css') {
return code
.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="token comment">$1</span>')
.replace(/([\w-]+)\s*:/g, '<span class="token keyword">$1</span>:')
.replace(/(#[0-9a-fA-F]{3,6}|rgb\(.*?\)|rgba\(.*?\))/g, '<span class="token string">$1</span>');
}
return code;
}
// 复制结果到剪贴板
function copyToClipboard() {
const text = outputEl.innerText;
if (!text) return;
navigator.clipboard.writeText(text)
.then(() => {
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check"></i> 已复制';
setTimeout(() => {
copyBtn.innerHTML = originalText;
}, 2000);
})
.catch(err => {
console.error('复制失败:', err);
alert('复制失败,请手动复制');
});
}
// 初始化应用
window.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>