본문 바로가기

첫 웹 페이지 만들기

HTML 구조와 CSS 스타일, JavaScript 클릭 이벤트를 연결해 자기소개가 담긴 첫 웹 페이지를 완성합니다.

앞의 글들에서 웹의 기본 원리와 세 언어, 개발 환경을 살펴봤습니다. 이 글에서는 그 도구로 이름과 간단한 소개가 담긴 자기소개 페이지를 직접 만듭니다. HTML로 구조를 만들고, CSS로 모양을 입히고, 자바스크립트로 버튼에 동작을 붙인 뒤, 브라우저에서 세 파일이 어떤 순서로 불러와지는지 확인합니다.

첫 페이지 파일 연결과 브라우저 실행 순서 — 상태 추적

HTML 파서·서버·일반 script의 실제 메시지 순서와 버튼 handler 준비 여부가 중요하다. 본문 contactButton 예제에 맞춰 파일 요청→스크립트 등록→DOMContentLoaded를 분리할 가치가 있다.

본문 끝 스크립트가 앞의 버튼에 동작을 연결합니다

FIRST PAGE INTEGRATION

본문 끝 스크립트가 앞의 버튼에 동작을 연결합니다

HTML 파싱 중 CSS·JS를 요청하고, 교재의 일반 script가 실행된 뒤 파싱 완료와 DOMContentLoaded에 도달합니다.

교재 세 파일의 연결과 실행HTML 파싱 중 CSS와 body 끝 일반 스크립트를 요청한다. 스크립트 응답을 받아 실행 준비가 되면 앞서 파싱된 버튼에 핸들러를 등록하고, 파싱을 마쳐 DOMContentLoaded가 발생한다. CSS 응답과 대기 시간은 생략했다.문서·HTML 파서DOM을 구성로컬 HTTP 서버HTML · CSS · JSJavaScript일반 script 실행my_profile.html 응답profile_style.css 요청profile_script.js 요청스크립트 응답 · 실행 준비*contactButton에 click 핸들러 등록문서 파싱 완료DOMContentLoaded
세 파일 요청과 본문 끝 스크립트의 모바일 시퀀스왼쪽 문서 파서, 가운데 로컬 HTTP 서버, 오른쪽 일반 JavaScript의 세 수명선을 따라 위에서 아래로 읽는다. 서버에서 문서로 HTML 응답, 문서에서 서버로 CSS 요청과 JS 요청, 서버에서 JS로 스크립트 응답, JS에서 문서로 contactButton click 핸들러 등록이 이어지고 문서 파싱을 마친다. CSS 응답과 대기는 생략했다. 데스크톱과 같은 참여자와 다섯 메시지다.HTML 응답CSS 요청JS 요청JS 응답*click 등록문서파서서버HTTPJS일반문서 파싱 완료DOMContentLoaded

본문 끝 script · 대상 요소를 먼저 구성

<button id="contactButton">
  문의하기
</button>
<script
  src="profile_script.js">
</script>

그림은 교재의 async·defer 없는 일반 script를 기준으로 합니다. 클릭 핸들러는 문의 기능 대신 alert를 보여줍니다.

* 응답 수신과 실행은 구분

일반 script는 먼저 발견된 blocking stylesheet를 기다릴 수 있습니다. CSS 응답은 그림에서 생략했습니다. 모바일의 HTML·CSS·JS는 각각 my_profile.html·profile_style.css·profile_script.js를 가리킵니다. 요청 간격·수명선 길이는 실제 소요 시간이 아닙니다.

DOMContentLoaded는 문서 파싱과 관련 스크립트 실행 완료를 알리는 시점입니다. 이미지 등 모든 자원 로드와는 구분합니다. Network에서 실제 세 파일 요청, Console에서 오류·load 메시지, 버튼 조작에서 alert를 확인합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
원도표 설명그림은 교재의 async·defer 없는 일반 script를 기준으로 합니다. 클릭 핸들러는 문의 기능 대신 alert를 보여 줍니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
HTML 파싱 중문서: 아직 파싱 중 / 버튼: 핸들러 등록 전파서가 첫 문서를 읽으며 필요한 파일을 요청한다문서와 script는 별도 입력이다
스크립트 실행스크립트: 현재 실행 / 버튼: 이벤트 handler 등록해당 스크립트가 DOM 대상을 찾아 handler를 연결한다실제 DOM에 handler가 연결되어 후속 클릭을 받을 수 있다
파싱 완료를 알린다문서: 파싱 완료 / 이미지: 모두 완료라는 뜻은 아님DOMContentLoaded는 문서 파싱 및 해당 대기 스크립트 뒤의 사건이다DOMContentLoaded와 전체 자원 load는 서로 다른 완료 조건이다

첫 페이지 파일 연결과 브라우저 실행 순서 — 상태 추적

HTML 파서·서버·일반 script의 실제 메시지 순서와 버튼 handler 준비 여부가 중요하다. 본문 contactButton 예제에 맞춰 파일 요청→스크립트 등록→DOMContentLoaded를 분리할 가치가 있다.

본문 끝 스크립트가 앞의 버튼에 동작을 연결합니다

FIRST PAGE INTEGRATION

본문 끝 스크립트가 앞의 버튼에 동작을 연결합니다

HTML 파싱 중 CSS·JS를 요청하고, 교재의 일반 script가 실행된 뒤 파싱 완료와 DOMContentLoaded에 도달합니다.

교재 세 파일의 연결과 실행HTML 파싱 중 CSS와 body 끝 일반 스크립트를 요청한다. 스크립트 응답을 받아 실행 준비가 되면 앞서 파싱된 버튼에 핸들러를 등록하고, 파싱을 마쳐 DOMContentLoaded가 발생한다. CSS 응답과 대기 시간은 생략했다.문서·HTML 파서DOM을 구성로컬 HTTP 서버HTML · CSS · JSJavaScript일반 script 실행my_profile.html 응답profile_style.css 요청profile_script.js 요청스크립트 응답 · 실행 준비*contactButton에 click 핸들러 등록문서 파싱 완료DOMContentLoaded
세 파일 요청과 본문 끝 스크립트의 모바일 시퀀스왼쪽 문서 파서, 가운데 로컬 HTTP 서버, 오른쪽 일반 JavaScript의 세 수명선을 따라 위에서 아래로 읽는다. 서버에서 문서로 HTML 응답, 문서에서 서버로 CSS 요청과 JS 요청, 서버에서 JS로 스크립트 응답, JS에서 문서로 contactButton click 핸들러 등록이 이어지고 문서 파싱을 마친다. CSS 응답과 대기는 생략했다. 데스크톱과 같은 참여자와 다섯 메시지다.HTML 응답CSS 요청JS 요청JS 응답*click 등록문서파서서버HTTPJS일반문서 파싱 완료DOMContentLoaded

본문 끝 script · 대상 요소를 먼저 구성

<button id="contactButton">
  문의하기
</button>
<script
  src="profile_script.js">
</script>

그림은 교재의 async·defer 없는 일반 script를 기준으로 합니다. 클릭 핸들러는 문의 기능 대신 alert를 보여줍니다.

* 응답 수신과 실행은 구분

일반 script는 먼저 발견된 blocking stylesheet를 기다릴 수 있습니다. CSS 응답은 그림에서 생략했습니다. 모바일의 HTML·CSS·JS는 각각 my_profile.html·profile_style.css·profile_script.js를 가리킵니다. 요청 간격·수명선 길이는 실제 소요 시간이 아닙니다.

DOMContentLoaded는 문서 파싱과 관련 스크립트 실행 완료를 알리는 시점입니다. 이미지 등 모든 자원 로드와는 구분합니다. Network에서 실제 세 파일 요청, Console에서 오류·load 메시지, 버튼 조작에서 alert를 확인합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
원도표 설명그림은 교재의 async·defer 없는 일반 script를 기준으로 합니다. 클릭 핸들러는 문의 기능 대신 alert를 보여 줍니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
HTML 파싱 중문서: 아직 파싱 중 / 버튼: 핸들러 등록 전파서가 첫 문서를 읽으며 필요한 파일을 요청한다문서와 script는 별도 입력이다
스크립트 실행스크립트: 현재 실행 / 버튼: 이벤트 handler 등록해당 스크립트가 DOM 대상을 찾아 handler를 연결한다실제 DOM에 handler가 연결되어 후속 클릭을 받을 수 있다
파싱 완료를 알린다문서: 파싱 완료 / 이미지: 모두 완료라는 뜻은 아님DOMContentLoaded는 문서 파싱 및 해당 대기 스크립트 뒤의 사건이다DOMContentLoaded와 전체 자원 load는 서로 다른 완료 조건이다

위 그림은 이 글에서 완성할 세 파일이 브라우저에서 처리되는 순서를 보여 줍니다. 로컬 HTTP 서버가 my_profile.html을 응답하면 HTML 파서가 DOM을 만들면서 profile_style.css와 profile_script.js를 요청합니다. 스크립트를 받아 실행할 준비가 되면 이미 만들어진 contactButton 버튼에 클릭 핸들러를 등록하고, 문서 파싱을 마치면 DOMContentLoaded 이벤트가 발생합니다.

그림 아래의 설명처럼 응답을 받는 것과 실행하는 것은 구분됩니다. async나 defer가 없는 일반 스크립트는 앞에서 발견한 스타일 시트를 다 받을 때까지 실행을 기다릴 수 있으며, 그림에서는 CSS 응답과 대기 시간을 생략했습니다. 요청 사이의 간격도 실제 걸리는 시간이 아닙니다. 완성한 뒤에는 Network 패널에서 세 파일의 요청을, Console 패널에서 오류와 로드 메시지를, 버튼을 눌러 안내창을 확인합니다.


HTML: 페이지의 뼈대

먼저 HTML로 페이지의 구조를 만듭니다. 개발 환경 설정에서 만든 web-dev-practice 폴더를 VS Code로 열고, 그 안에 my_profile.html을 새로 만듭니다. 앞에서 만든 index.html은 그대로 둡니다.

my_profile.html에 다음 코드를 입력합니다. [여러분의 이름]은 자리 표시자이므로 title, h1, 푸터의 세 곳을 모두 자신의 이름으로 바꾸세요. title의 이름은 브라우저 탭에, h1의 이름은 페이지 안에 보입니다.

my_profile.html
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>나의 첫 프로필 페이지 - [여러분의 이름]</title>
</head>
<body>
    <header>
        <h1>안녕하세요, [여러분의 이름]입니다!</h1>
        <p>웹 개발을 시작하는 초보 개발자입니다.</p>
    </header>

    <section>
        <h2>저를 소개합니다</h2>
        <p>
            저는 새로운 기술을 배우는 것을 즐기며, 특히 웹 개발에 깊은 흥미를 느끼고 있습니다.
            이 책과 함께 여러분과 함께 성장하고 싶습니다.
        </p>
        <ul>
            <li><strong>취미:</strong> 독서, 산책</li>
            <li><strong>좋아하는 것:</strong> 맛있는 커피, 새로운 지식</li>
            <li><strong>목표:</strong> 나만의 멋진 웹 서비스 만들기</li>
        </ul>
    </section>

    <footer>
        <p>&copy; 2026 [여러분의 이름]. All rights reserved.</p>
        <button id="contactButton">문의하기</button>
    </footer>
</body>
</html>

<!DOCTYPE html>은 브라우저에 이 문서가 현재 HTML 표준을 따른다고 알리고, <html lang="ko">는 문서의 기본 언어가 한국어임을 밝혀 검색 엔진과 스크린 리더가 올바른 언어로 처리하게 합니다. <head>에는 화면에 보이지 않는 문서 정보가 들어갑니다. <meta charset="UTF-8">은 문자 인코딩을 UTF-8로 정해 한글이 깨지지 않게 하고, <meta name="viewport" ...>는 모바일 기기에서도 화면 너비에 맞춰 페이지를 표시하게 하며, <title>은 브라우저 탭의 제목을 정합니다.

화면에 보이는 내용은 모두 <body> 안에 있습니다. <header>는 페이지의 머리글, <section>은 주제가 있는 한 덩어리의 내용, <footer>는 저작권이나 연락처를 담는 바닥글입니다. <h1>과 <h2>는 제목의 단계를, <p>는 문단을, <ul>과 <li>는 순서 없는 목록과 그 항목을, <strong>은 중요한 내용을 나타냅니다. <button>의 id="contactButton"은 나중에 자바스크립트에서 이 버튼을 찾을 때 쓰는 고유한 이름입니다.

파일을 저장하고 브라우저로 엽니다. Live Server를 설치했다면 탐색기에서 my_profile.html을 마우스 오른쪽 버튼으로 눌러 Open with Live Server를 고르고, 주소 끝이 my_profile.html인지 확인합니다. Go Live 버튼은 index.html을 먼저 열 수 있기 때문입니다. 확장 프로그램이 없다면 파일을 더블 클릭해 열고, 고칠 때마다 직접 새로고침합니다. 아직 스타일이 없으므로 글과 목록, 버튼이 브라우저의 기본 모양으로만 보입니다.


CSS: 페이지의 모양

이제 CSS로 색과 글꼴, 여백을 정합니다. 같은 폴더에 profile_style.css를 새로 만들고, my_profile.html의 </head> 바로 앞에 다음 한 줄을 넣어 두 파일을 연결합니다.

my_profile.html
<link rel="stylesheet" href="profile_style.css">

href의 경로는 my_profile.html이 있는 폴더를 기준으로 해석되므로, 두 파일이 같은 폴더에 있다면 파일 이름만 적으면 됩니다. profile_style.css에는 다음 CSS를 씁니다. 규칙은 페이지의 위에서 아래 순서대로 body, header, section, footer, 버튼 순으로 놓여 있고, header h1처럼 선택자를 띄어 쓴 규칙은 header 안에 있는 h1에만 적용됩니다. 버튼만 #contactButton처럼 id 선택자로 골랐습니다.

profile_style.css
/* 전체 페이지에 적용될 기본 스타일 */
body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f2f5; /* 연한 회색 배경 */
    color: #333;
    line-height: 1.6;
    margin: 0;
    padding: 0;
    display: flex; /* Flexbox로 콘텐츠를 세로로 쌓고 가운데 정렬 */
    flex-direction: column;
    align-items: center;
    min-height: 100vh; /* 최소 화면 높이 */
}

/* 헤더 스타일 */
header {
    background-color: #007bff; /* 파란색 배경 */
    color: white;
    padding: 40px 20px;
    width: 100%;
    box-sizing: border-box; /* padding을 지정한 너비 안에 포함 */
    text-align: center;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

header h1 {
    margin-bottom: 10px;
    font-size: 2.8em;
}

header p {
    font-size: 1.2em;
    opacity: 0.9;
}

/* 섹션 스타일 */
section {
    background-color: white;
    margin: 30px 20px;
    padding: 30px;
    border-radius: 10px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
    max-width: 800px;
    width: calc(100% - 40px); /* 좌우 margin 20px씩 제외 */
    box-sizing: border-box; /* padding을 지정한 너비 안에 포함 */
    text-align: left;
}

section h2 {
    color: #0056b3;
    margin-bottom: 20px;
    font-size: 2em;
    border-bottom: 2px solid #eee;
    padding-bottom: 10px;
}

section ul {
    list-style: none; /* 목록 마커 제거 */
    padding: 0;
    margin-top: 20px;
}

section ul li {
    background-color: #e9f7fe; /* 연한 하늘색 배경 */
    margin-bottom: 10px;
    padding: 12px 15px;
    border-left: 5px solid #007bff;
    border-radius: 5px;
    font-size: 1.1em;
}

section ul li strong {
    color: #004d66;
}

/* 푸터 스타일 */
footer {
    background-color: #333;
    color: white;
    text-align: center;
    padding: 20px;
    width: 100%;
    box-sizing: border-box; /* padding을 지정한 너비 안에 포함 */
    margin-top: auto; /* 남는 공간을 위쪽에 두어 화면 아래로 밀기 */
}

footer p {
    margin-bottom: 15px;
    font-size: 0.9em;
    opacity: 0.8;
}

/* 버튼 스타일 */
#contactButton { /* id 선택자 */
    background-color: #28a745; /* 녹색 버튼 */
    color: white;
    padding: 12px 25px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 1.1em;
    transition: background-color 0.3s ease, transform 0.2s ease; /* 호버 애니메이션 */
}

#contactButton:hover {
    background-color: #218838; /* 진한 녹색 */
    transform: translateY(-2px); /* 살짝 위로 */
}

두 파일을 저장하면(Live Server를 쓴다면 자동으로 새로고침됩니다) 배경이 연한 회색이 되고, 파란 헤더와 흰 소개 카드, 어두운 푸터로 영역이 나뉘어 보입니다. 목록 항목은 왼쪽에 파란 선이 있는 하늘색 상자가 되고, 버튼에 마우스를 올리면 색이 진해지며 살짝 올라갑니다.

body에 display: flex, flex-direction: column, min-height: 100vh를 주고 footer에 margin-top: auto를 준 것은, 내용이 짧아도 푸터가 화면 아래쪽에 붙게 하려는 것입니다. 푸터 위의 남는 공간을 모두 margin-top이 차지하기 때문입니다. 스타일이 적용되지 않으면 href의 파일 이름과 profile_style.css가 같은 폴더에 있는지 확인합니다.


자바스크립트: 버튼의 동작

마지막으로 문의하기 버튼을 누르면 안내창이 뜨게 만듭니다. 같은 폴더에 profile_script.js를 만들고, my_profile.html의 </body> 바로 앞, 푸터 아래에 다음 한 줄을 넣습니다.

<script src="profile_script.js"></script>

브라우저는 HTML을 위에서부터 읽으며 DOM을 만듭니다. 이 <script>는 버튼보다 뒤에 있으므로, 스크립트가 실행될 때 contactButton 요소는 이미 DOM에 있습니다. 스크립트를 버튼보다 앞, 예를 들어 <head> 안으로 옮기면 아직 없는 요소를 찾다가 null을 얻고, addEventListener()를 호출할 때 오류가 납니다.

profile_script.js에는 다음 코드를 씁니다.

profile_script.js
// '문의하기' 버튼 요소를 id로 찾습니다.
const contactButton = document.getElementById('contactButton');

// 버튼에 클릭 이벤트 리스너를 등록합니다.
contactButton.addEventListener('click', function() {
    // 버튼을 누르면 안내창(alert)을 띄웁니다.
    alert('아직 문의 기능은 개발 중입니다. 잠시만 기다려주세요! 😊');
});

// 이미지 같은 자원까지 모두 불러온 뒤 실행됩니다(선택 사항).
window.addEventListener('load', function() {
    console.log('프로필 페이지가 성공적으로 로드되었습니다!');
    // 개발자 도구의 Console 패널에서 이 메시지를 확인할 수 있습니다.
});

파일을 저장하고 페이지를 다시 연 뒤 문의하기 버튼을 누르면 “아직 문의 기능은 개발 중입니다. 잠시만 기다려주세요! 😊”라는 안내창이 뜹니다. Console 패널에는 “프로필 페이지가 성공적으로 로드되었습니다!”가 출력됩니다. 안내창이 뜨지 않으면 src의 파일 이름과 profile_script.js를 저장했는지부터 확인합니다.

앞 그림의 DOMContentLoaded는 HTML 파싱과 일반 스크립트의 실행이 끝난 시점이고, 이 코드의 load 이벤트는 이미지 같은 나머지 자원까지 모두 받은 뒤의 시점입니다. 두 시점의 차이는 MDN의 DOMContentLoaded 설명에서 자세히 확인할 수 있습니다.


정리

  • HTML의 <head>에는 인코딩, 뷰포트, 제목 같은 문서 정보를, <body>에는 header, section, footer처럼 의미에 맞는 요소로 화면의 내용을 둡니다.
  • <link>로 연결한 CSS는 HTML을 바꾸지 않고 색, 글꼴, 여백, 배치를 정하며, margin-top: auto로 푸터를 화면 아래쪽에 붙일 수 있습니다.
  • <script>를 </body> 바로 앞에 두면 스크립트가 실행될 때 앞의 요소가 이미 DOM에 있어 이벤트를 등록할 수 있습니다.
  • DOMContentLoaded는 문서 파싱이 끝난 시점이고, load는 이미지 같은 자원까지 모두 받은 시점입니다.
  • 완성한 페이지는 Network 패널로 세 파일의 요청을, Console 패널로 오류와 메시지를, 직접 조작으로 동작을 확인합니다.

연습 문제

1. 자기소개 페이지 바꿔 보기

취미 목록에 항목을 하나 더하고, 헤더 배경을 다른 색으로 바꾸고, 안내창의 문구를 바꾸려고 합니다. 세 변경을 각각 어느 파일의 어디에서 해야 할까요? 저장하고 새로고침했더니 취미만 늘어났다면 무엇을 의심해야 할까요?

해설 보기

목록 항목은 my_profile.html의 ul 안에 li를 하나 더하고, 헤더 배경은 profile_style.css의 header 규칙에 있는 background-color를, 안내 문구는 profile_script.js의 클릭 함수 안 alert()의 문자열을 바꿉니다.

취미만 늘어났다면 HTML은 반영되었지만 CSS와 자바스크립트 파일은 저장되지 않았거나 다른 파일을 고쳤을 수 있습니다. 두 파일의 저장 상태와, Network 패널에서 그 파일들을 다시 받아 왔는지 확인합니다.

2. 스크립트를 head로 옮기면

<script src="profile_script.js"></script>를 </body> 앞이 아니라 <head> 안, <link> 바로 아래로 옮겼습니다. 페이지를 열고 버튼을 누르면 어떻게 될까요? Console 패널에는 무엇이 보일까요?

해설 보기

버튼을 눌러도 안내창이 뜨지 않습니다. 스크립트가 실행되는 시점에는 파서가 아직 <body>를 읽지 않아 버튼이 DOM에 없으므로, getElementById('contactButton')이 null을 돌려줍니다. 이어서 null의 addEventListener()를 호출하므로 Console 패널에 TypeError가 표시되고, 그 아래 줄의 load 리스너 등록도 실행되지 않아 로드 메시지도 나오지 않습니다.

<head> 안에 두고 싶다면 <script src="profile_script.js" defer></script>처럼 defer를 붙입니다. 그러면 스크립트는 문서 파싱이 끝난 뒤에 실행되므로 버튼을 찾을 수 있습니다.

3. 키보드로 누르기

마우스를 쓰지 않고 Tab 키로 문의하기 버튼까지 이동한 뒤 Enter 키나 Space 키를 눌렀습니다. 안내창이 뜰까요? 버튼 대신 <span id="contactButton">문의하기</span>를 썼다면 어떨까요?

해설 보기

뜹니다. <button>은 키보드 초점을 받을 수 있고, Enter와 Space 키를 누르면 브라우저가 click 이벤트를 발생시키므로 같은 함수가 실행됩니다.

<span>은 초점을 받지 않아 Tab 키로 이동할 수 없고, 키를 눌러도 click 이벤트가 생기지 않습니다. 마우스로는 동작하더라도 키보드 사용자는 기능을 쓸 수 없으므로, 누를 수 있는 기능에는 <button>을 씁니다. 이 예제는 안내창만 띄울 뿐 서버에 문의를 보내지는 않습니다.

이것으로 1장을 마칩니다. 1장에서는 웹이 요청과 응답으로 동작하는 원리와 역사, HTML, CSS, 자바스크립트가 맡는 일, 개발 환경과 첫 페이지를 살펴봤습니다. 다음 장의 HTML 구조와 문법부터는 HTML의 요소와 문법을 하나씩 자세히 다룹니다.