브라우저 기반 코드 에디터 구축 가이드
1. 프로젝트 개요
1.1 프로젝트 소개
이 가이드는 완전히 브라우저에서 실행되는 경량 코드 에디터를 구축하는 방법을 다룹니다. HTML, CSS, JavaScript를 위한 탭형 에디터를 제공하며, 샌드박스 iframe에서 즉석 라이브 미리보기를 지원합니다.
1.2 주요 기능
- 탭형 에디터: HTML, CSS, JavaScript 각각을 위한 독립적인 탭
- 라이브 미리보기: 샌드박스 iframe을 통한 즉시 결과 확인
- 키보드 단축키: 빠르고 부드러운 에디터 조작
- 유효성 검사: JavaScript 코드 검증을 위한 검사 기능
- 저장/로드 기능: JSON 다운로드 및 로컬 스토리지 복원
- 접근성: ARIA 상태, 포커스 관리, 완전한 키보드 단축키
- 신뢰성: CDN 고정 및 graceful fallback 적용
1.3 사용 장면
- 수업용 교육 도구
- 과제 및 연습용 플랫폼
- 빠른 프로토타이핑
- 온라인 코딩 환경
2. 프로젝트 시연 및 기능 소개
2.1 인터페이스 구조
프로젝트는 다음과 같은 구조로 구성됩니다:
헤더 섹션
- 로고 및 제목 (“Code Editor Live in Browser”)
- Save 및 Load 버튼
메인 섹션 (3개 하위 섹션)
-
사이드바: 과제/할당 패널
- 과제 설명 텍스트 영역
- JavaScript 전용 유효성 검사 (선택사항)
-
중앙 에디터: HTML/CSS/JavaScript 에디터
- Run 버튼: 모든 코드를 한 번에 실행
- “Open Preview in Window” 버튼: 새 창에서 미리보기
-
출력 패널: 결과 및 로그 표시
- “Run with Tests” 버튼
- “Clear Log” 버튼
2.2 실제 사용 예제
색상 전환기 예제
- HTML로 기본 구조 생성
- CSS로 스타일링 적용
- JavaScript로 색상 변경 기능 구현
- 실시간으로 빨강, 초록, 파랑 색상 전환 가능
할일 목록 예제
- HTML로 입력 폼과 목록 구조 생성
- CSS로 레이아웃 스타일링
- JavaScript로 동적 할일 추가/완료 기능
- 완료된 항목에 취소선 표시
랜덤 명언 생성기 예제
- 버튼 클릭 시 랜덤 명언 표시
- 동적 콘텐츠 업데이트 기능
RGB 색상 믹서 예제
- 빨강, 초록, 파랑 채널별 슬라이더
- 실시간 색상 조합 미리보기
- 색상 값 표시
2.3 저장 및 로드 기능
- 저장: JSON 형식으로 프로젝트 다운로드 (academy-web.json)
- 로드: JSON 파일을 선택하여 이전 작업 복원
- 로컬 스토리지: 브라우저 종료 후에도 작업 내용 유지
3. HTML 구조 구축
3.1 기본 HTML5 구조
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Editor - Programming Ocean Academy</title>
</head>
<body>
<!-- 컨텐츠 구조 -->
</body>
</html>
3.2 헤더 요소 구축
컨테이너 구조
div.container.row: 행 방향 레이아웃 컨테이너div.brand: 브랜드 정보 (로고 + 제목)div.row: 버튼 컨테이너
브랜드 영역
<div class="brand">
<div class="logo"></div>
<div class="title">Code Editor Live in Browser</div>
</div>
버튼 영역
<div class="row">
<button class="btn secondary" id="save-btn" title="Save work locally">
<span>Save</span>
</button>
<button class="btn secondary" id="load-btn" title="Load work file">
<span>Load</span>
</button>
<input type="file" id="open-file" accept="application/json" hidden>
</div>
3.3 메인 섹션 구축
사이드바 (과제 패널)
<aside class="card panel stack">
<h2>Task/Assignment</h2>
<textarea id="assignment" class="text"
placeholder="Write the task description here..."></textarea>
<div class="stack">
<label for="test-area">Validation Tests (JavaScript only - optional)</label>
<textarea id="test-area" class="text"
placeholder="Write simple tests to append after the student's code to check the solution..."></textarea>
</div>
<div class="muted">
<span>Tip: <span class="kbd">Ctrl + S</span> to save,
<span class="kbd">Ctrl + Enter</span> to run</span>
</div>
</aside>
웹 에디터 섹션
<section class="stack" id="web-only">
<div id="web-editors" class="stack panel card">
<div class="row">
<h2>HTML/CSS/JavaScript</h2>
<div class="row">
<button class="btn" id="run-web">Run</button>
<button class="btn secondary" id="open-preview">Preview</button>
</div>
</div>
<!-- 탭 네비게이션 -->
<div class="tabs" id="web-tabs" role="tablist" aria-label="Web editors">
<button class="tab active" role="tab"
aria-selected="true" tabindex="0"
data-pane="html">HTML</button>
<button class="tab" role="tab"
aria-selected="false" tabindex="-1"
data-pane="css">CSS</button>
<button class="tab" role="tab"
aria-selected="false" tabindex="-1"
data-pane="javascript">JavaScript</button>
</div>
<!-- 에디터 래퍼들 -->
<div class="editor-wrap" data-pane="html">
<div id="editor-html" class="editor"></div>
</div>
<div class="editor-wrap" data-pane="css" hidden>
<div id="editor-css" class="editor"></div>
</div>
<div class="editor-wrap" data-pane="javascript" hidden>
<div id="editor-js" class="editor"></div>
</div>
<!-- 미리보기 -->
<div class="stack">
<h3>Preview</h3>
<iframe id="preview" class="preview"
sandbox="allow-scripts allow-same-origin allow-modals allow-forms">
</iframe>
</div>
</div>
</section>
출력 패널
<aside class="card panel stack">
<h2>Output</h2>
<div id="output" class="out" aria-live="polite"></div>
<div class="row">
<button class="btn warn" id="run-tests">Run with Tests</button>
<button class="btn secondary" id="clear-log">Clear Log</button>
</div>
<h3>Notes</h3>
<ul class="muted">
<li>Everything runs inside the browser in a sandbox</li>
<li>You can save the work as a JSON file and restore it</li>
</ul>
</aside>
3.4 시맨틱 HTML 요소의 중요성
main 요소
- HTML5 시맨틱 요소
- 웹 페이지의 주요 콘텐츠를 나타냄
- 한 문서당 하나만 사용해야 함
- 보조 기술에게 주요 콘텐츠 영역을 알려줌
접근성 속성
role="tablist": 탭 목록임을 명시aria-label: 보조 기술용 라벨aria-selected: 선택된 탭 상태aria-live="polite": 동적 콘텐츠 변경 알림
3.5 CSS 및 JavaScript 파일 연결
<!-- CSS 연결 -->
<link rel="stylesheet" href="styles.css">
<!-- JavaScript 연결 (body 끝부분) -->
<script src="script.js"></script>
4. CSS 스타일링 및 레이아웃
4.1 CSS 변수 정의
:root {
--background: #0F121B;
--body-background: #0B0F1D;
--panel: #141B2B;
--muted: #8B93A7;
--txt: #E9ECF1;
--brand: #7D3FC7;
--brand-2: #6B0A5A;
--btn-ok: #22C55E;
--btn-warn: #EAB308;
--btn-error: #EF4444;
--radius: 18px;
--gap: 14px;
}
4.2 전역 리셋
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
}
4.3 바디 스타일링
body {
font-family: sans-serif;
background: linear-gradient(180deg,
var(--body-background) 35%,
var(--txt));
color: var(--txt);
}
4.4 헤더 스타일링
header {
padding: 20px;
background: linear-gradient(90deg,
rgba(96, 165, 250, 0.15),
rgba(125, 211, 252, 0.1));
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
4.5 컨테이너 및 레이아웃
컨테이너
.container {
max-width: 1200px;
margin-inline: auto;
padding: 14px;
justify-content: space-between;
}
브랜드 영역
.brand {
display: flex;
align-items: center;
gap: 10px;
font-weight: 700;
}
.brand .logo {
inline-size: 36px;
block-size: 36px;
border-radius: 10px;
background: url('https://programmingocean.com/codeeditors/new-logo.png')
center/cover no-repeat;
}
.title {
font-size: clamp(18px, 2vw, 22px);
}
4.6 그리드 레이아웃 시스템
main {
max-width: 1560px;
margin-inline: auto;
padding: 18px;
display: grid;
grid-template-columns: 280px 1fr 360px;
gap: var(--gap);
}
/* 반응형 미디어 쿼리 */
@media (max-width: 1100px) {
main {
grid-template-columns: 1fr;
}
}
4.7 카드 및 패널 스타일링
.card {
background: linear-gradient(180deg,
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.03));
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: var(--radius);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}
.panel {
padding: 16px;
}
4.8 플렉스박스 레이아웃
스택 레이아웃 (세로 방향)
.stack {
display: flex;
flex-direction: column;
gap: 10px;
}
행 레이아웃 (가로 방향)
.row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
justify-content: space-between;
}
4.9 버튼 스타일링
.btn {
appearance: none;
border: none;
background: linear-gradient(180deg, var(--brand-2), var(--brand));
color: #081F2F;
font-weight: 700;
padding: 10px 14px;
border-radius: 12px;
cursor: pointer;
box-shadow: 0 10px 24px rgba(96, 165, 250, 0.25);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 15px 30px rgba(96, 165, 250, 0.4);
}
.btn.secondary {
background: rgba(255, 255, 255, 0.1);
color: var(--txt);
}
.btn.warn {
background: var(--btn-warn);
color: #1A1A1A;
}
4.10 탭 스타일링
.tabs {
display: flex;
gap: 4px;
background: rgba(0, 0, 0, 0.3);
padding: 4px;
border-radius: 12px;
}
.tab {
flex: 1;
padding: 10px 16px;
background: transparent;
border: none;
color: var(--muted);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
}
.tab.active {
background: var(--brand);
color: white;
}
4.11 에디터 및 미리보기 스타일링
.editor {
height: 200px;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
background: rgba(0, 0, 0, 0.3);
}
.preview {
width: 100%;
height: 300px;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
background: white;
}
4.12 텍스트 영역 스타일링
.text {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
color: var(--txt);
padding: 12px;
font-family: inherit;
resize: vertical;
min-height: 100px;
}
.text::placeholder {
color: var(--muted);
}
5. JavaScript 기능 구현
5.1 헬퍼 함수
DOM 선택 함수
const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => document.querySelectorAll(selector);
const out = $('#output');
const preview = $('#preview');
HTML 이스케이프 함수
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
5.2 로깅 시스템
로그 출력 함수
function log(message, type = 'info') {
const span = document.createElement('span');
span.className = type === 'error' ? 'error' :
type === 'warn' ? 'warn' : 'brand';
span.textContent = message;
out.appendChild(span);
}
function clearOut() {
out.innerHTML = '';
}
5.3 Ace 에디터 설정
에디터 생성 함수
function makeEditor(mode) {
const editor = ace.edit(document.createElement('div'));
editor.setTheme('ace/theme/monokai');
editor.session.setMode(mode);
// 키 바인딩 설정
editor.commands.addCommand({
name: 'run',
bindKey: { win: 'Ctrl-Enter', mac: 'Cmd-Enter' },
exec: () => runWeb(false)
});
editor.commands.addCommand({
name: 'save',
bindKey: { win: 'Ctrl-S', mac: 'Cmd-S' },
exec: saveProject
});
return editor;
}
에디터 인스턴스 생성
const tabOrder = ['html', 'css', 'javascript'];
const editors = {
html: makeEditor('ace/mode/html'),
css: makeEditor('ace/mode/css'),
javascript: makeEditor('ace/mode/javascript')
};
5.4 탭 시스템
활성 탭 관리
function activePanes() {
return [...$$('.tab')].filter(tab =>
tab.getAttribute('aria-selected') === 'true'
);
}
function activePane() {
const actives = activePanes();
return actives.length ? actives[0].dataset.pane : 'html';
}
function showPane(name) {
// 모든 탭 비활성화
$$('.tab').forEach(tab => {
tab.classList.remove('active');
tab.setAttribute('aria-selected', 'false');
tab.tabIndex = -1;
});
// 선택된 탭 활성화
const selectedTab = $(`.tab[data-pane="${name}"]`);
selectedTab.classList.add('active');
selectedTab.setAttribute('aria-selected', 'true');
selectedTab.tabIndex = 0;
// 에디터 패널 표시/숨김
$$('.editor-wrap').forEach(wrap => {
wrap.hidden = wrap.dataset.pane !== name;
});
// 에디터 리사이즈 및 포커스
if (editors[name]) {
requestAnimationFrame(() => {
editors[name].resize(true);
editors[name].focus();
});
}
}
5.5 키보드 네비게이션
$('#web-tabs').addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
e.preventDefault();
const current = activePane();
const currentIndex = tabOrder.indexOf(current);
const delta = e.key === 'ArrowRight' ? 1 : -1;
const newIndex = (currentIndex + delta + tabOrder.length) % tabOrder.length;
showPane(tabOrder[newIndex]);
}
});
$('#web-tabs').addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn) return;
showPane(btn.dataset.pane);
});
5.6 코드 실행 시스템
HTML 문서 생성
function buildWebSrcDoc() {
const html = editors.html.getValue();
const css = editors.css.getValue();
const js = editors.javascript.getValue();
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>${css}</style>
</head>
<body>
${html}
<script>
try {
${js}
} catch (error) {
console.error('JavaScript Error:', error);
}
</script>
</body>
</html>`;
}
코드 실행 함수
function runWeb(withTests = false) {
try {
const srcDoc = buildWebSrcDoc();
preview.srcdoc = srcDoc;
const message = withTests ? 'Run with tests' : 'Web preview updated';
log(message);
if (withTests) {
const testCode = $('#test-area').value.trim();
if (testCode) {
// 테스트 코드 실행 로직
log('Tests executed');
}
}
} catch (error) {
log(`Error: ${error.message}`, 'error');
}
}
5.7 프로젝트 저장/로드 시스템
프로젝트 JSON 생성
function projectJSON() {
return {
version: 1,
kind: 'web-only',
assignment: $('#assignment').value,
testArea: $('#test-area').value,
html: editors.html.getValue(),
css: editors.css.getValue(),
javascript: editors.javascript.getValue()
};
}
프로젝트 저장
function saveProject() {
try {
const data = JSON.stringify(projectJSON(), null, 2);
// 로컬 스토리지에 저장
localStorage.setItem(storageKey, data);
// 파일 다운로드
const blob = new Blob([data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'academy-web.json';
a.click();
URL.revokeObjectURL(url);
log('Project saved');
} catch (error) {
log(`Save error: ${error.message}`, 'error');
}
}
프로젝트 로드
function loadProject(data) {
try {
$('#assignment').value = data.assignment || '';
$('#test-area').value = data.testArea || '';
editors.html.setValue(data.html || '', -1);
editors.css.setValue(data.css || '', -1);
editors.javascript.setValue(data.javascript || '', -1);
log('Project loaded');
} catch (error) {
log(`Load error: ${error.message}`, 'error');
}
}
5.8 이벤트 리스너 설정
// 실행 버튼
$('#run-web').addEventListener('click', () => runWeb(false));
$('#run-tests').addEventListener('click', () => runWeb(true));
// 저장/로드 버튼
$('#save-btn').addEventListener('click', saveProject);
$('#load-btn').addEventListener('click', () => $('#open-file').click());
// 파일 선택
$('#open-file').addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
loadProject(data);
} catch (error) {
log(`Invalid file: ${error.message}`, 'error');
}
};
reader.readAsText(file);
});
// 기타 버튼
$('#clear-log').addEventListener('click', clearOut);
$('#open-preview').addEventListener('click', () => {
const newWindow = window.open('about:blank');
newWindow.document.write(buildWebSrcDoc());
});
5.9 로컬 스토리지 복원
const storageKey = 'academy-web-editor';
function setDefaultContent() {
editors.html.setValue('', -1);
editors.css.setValue('', -1);
editors.javascript.setValue('', -1);
}
// 앱 초기화
try {
const cache = localStorage.getItem(storageKey);
if (cache) {
loadProject(JSON.parse(cache));
} else {
setDefaultContent();
}
} catch (e) {
setDefaultContent();
}
log('Ready: Web-only editor HTML/CSS/JavaScript');
6. Ace Editor 라이브러리 통합
6.1 CDN 추가
HTML 파일의 script 태그 전에 Ace Editor CDN을 추가합니다:
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js"></script>
<script src="script.js"></script>
6.2 Ace Editor 기능
- 구문 강조: HTML, CSS, JavaScript 코드 하이라이팅
- 자동 완성: 코드 자동 완성 기능
- 코드 폴딩: 코드 블록 접기/펼치기
- 테마 지원: 다양한 에디터 테마
- 키바인딩: 다양한 에디터 키보드 단축키
7. 실용적인 팁 및 주의사항
7.1 개발 팁
- CSS 변수 활용: 일관된 색상 및 크기 관리
- 플렉스박스 사용: 반응형 레이아웃 구현
- 그리드 레이아웃: 복잡한 2D 레이아웃 관리
- 접근성 고려: ARIA 속성 및 키보드 네비게이션
7.2 성능 최적화
- requestAnimationFrame: 에디터 리사이즈 시 사용
- 이벤트 위임: 효율적인 이벤트 처리
- 로컬 스토리지: 자동 진행상황 저장
7.3 보안 고려사항
- iframe sandbox: 안전한 코드 실행 환경
- HTML 이스케이프: XSS 공격 방지
- JSON 파싱: 안전한 데이터 처리
7.4 브라우저 호환성
- 모던 브라우저: ES6+ 기능 활용
- CSS Grid: 최신 레이아웃 기술
- Flexbox: 널리 지원되는 레이아웃
7.5 확장 가능성
- 테마 시스템: CSS 변수로 쉬운 테마 변경
- 언어 확장: 추가 프로그래밍 언어 지원
- 플러그인 시스템: Ace Editor 플러그인 활용
8. 주요 키보드 단축키
- Ctrl + Enter: 코드 실행
- Ctrl + S: 프로젝트 저장
- 화살표 키: 탭 간 이동
- Tab: 포커스 이동
9. 프로젝트 완성 및 배포
이 가이드를 통해 완전히 기능하는 브라우저 기반 코드 에디터를 구축할 수 있습니다. 최종 결과물은:
- 단일 HTML 페이지로 실행
- 서버 없이 브라우저에서 완전 동작
- 교육용 및 학습용으로 최적화
- 반응형 디자인으로 모든 기기 지원
이 코드 에디터는 웹 개발 교육, 빠른 프로토타이핑, 온라인 코딩 환경으로 활용할 수 있는 강력하고 실용적인 도구입니다.