반응형

🎠 Firebase 실시간 채팅 웹앱 만들기
안녕하세요! 오늘은 놀이공원 테마의 실시간 채팅 앱을 만들어볼 거예요. 위의 예제를 보면서 함께 배워보아요!
📚 오늘 배울 내용
1. 채팅방 목록 기능
- 여러 개의 채팅방을 만들고 선택할 수 있어요
- 롤러코스터, 관람차, 회전목마, 범퍼카 방이 있어요!
2. 메시지 입력 및 전송
- 텍스트를 입력하고 전송 버튼을 눌러 메시지를 보내요
- 엔터키로도 전송할 수 있어요
3. 실시간 메시지 표시
- 다른 사람이 보낸 메시지가 즉시 나타나요
- 내가 보낸 메시지는 오른쪽에, 다른 사람 메시지는 왼쪽에 표시돼요
4. 스크롤 자동 내리기
- 새 메시지가 올 때마다 자동으로 맨 아래로 스크롤되어요
5. 사용자 정보 표시
- 각 메시지마다 누가 보냈는지, 언제 보냈는지 표시돼요
🔧 주요 기능 설명
1️⃣ 채팅방 선택하기
javascript
// 채팅방을 클릭하면 이 함수가 실행돼요
function setupRoomListeners() {
roomList.addEventListener('click', (e) => {
// 클릭한 방을 활성화하고
// 그 방의 메시지들을 보여줘요
});
}
2️⃣ 메시지 보내기
javascript
// 메시지를 전송하는 함수예요
function sendMessage() {
const messageText = messageInput.value.trim();
if (!messageText) return; // 빈 메시지는 안 보내요
// 새 메시지 객체를 만들고
const newMessage = {
user: currentUser,
message: messageText,
timestamp: new Date()
};
// 메시지 목록에 추가해요
messages[currentRoom].push(newMessage);
}
3️⃣ 실시간 업데이트
javascript
// Firebase에서는 이런 식으로 실시간 업데이트를 받을 수 있어요
// onSnapshot(collection(db, 'messages'), (snapshot) => {
// // 새 메시지가 오면 자동으로 화면에 표시!
// });
4️⃣ 자동 스크롤
javascript
// 새 메시지가 올 때마다 맨 아래로 스크롤해요
function scrollToBottom() {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
🚀 Firebase 연결하기 (다음 단계)
실제로 Firebase를 연결하려면 이런 설정이 필요해요:
- Firebase 프로젝트 설정
- Firestore 규칙 설정
- 실시간 리스너 추가
- 메시지 저장 및 불러오기
💡 초등학생을 위한 쉬운 설명
채팅 앱이 어떻게 작동하나요?
- 편지함 같은 개념: 각 채팅방은 큰 편지함이에요
- 실시간 업데이트: 누군가 편지를 넣으면 모든 사람이 바로 볼 수 있어요
- 자동 정리: 새 편지가 오면 자동으로 맨 위로 올라와요
- 사용자 구분: 각 편지마다 누가 썼는지 이름표가 붙어있어요
전체 소스코드
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>놀이공원 실시간 채팅</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
width: 90%;
max-width: 800px;
height: 90vh;
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
display: flex;
overflow: hidden;
}
/* 채팅방 목록 영역 */
.chat-rooms {
width: 300px;
background: #f8f9ff;
border-right: 2px solid #e2e8f0;
display: flex;
flex-direction: column;
}
.rooms-header {
background: #4a5568;
color: white;
padding: 20px;
text-align: center;
font-size: 18px;
font-weight: bold;
}
.room-list {
flex: 1;
overflow-y: auto;
padding: 10px;
}
.room-item {
background: white;
margin-bottom: 10px;
padding: 15px;
border-radius: 10px;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s;
}
.room-item:hover {
background: #e2e8f0;
border-color: #4299e1;
}
.room-item.active {
background: #4299e1;
color: white;
border-color: #3182ce;
}
.room-name {
font-weight: bold;
margin-bottom: 5px;
}
.room-description {
font-size: 12px;
color: #666;
}
.room-item.active .room-description {
color: #e2e8f0;
}
/* 채팅 영역 */
.chat-area {
flex: 1;
display: flex;
flex-direction: column;
}
.chat-header {
background: #4299e1;
color: white;
padding: 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
.current-room {
font-size: 18px;
font-weight: bold;
}
.user-info {
display: flex;
align-items: center;
gap: 10px;
}
.user-avatar {
width: 35px;
height: 35px;
border-radius: 50%;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: #4299e1;
}
/* 메시지 표시 영역 */
.messages-container {
flex: 1;
overflow-y: auto;
padding: 20px;
background: #f7fafc;
}
.message {
margin-bottom: 15px;
display: flex;
align-items: flex-start;
gap: 10px;
}
.message.my-message {
flex-direction: row-reverse;
}
.message-avatar {
width: 30px;
height: 30px;
border-radius: 50%;
background: #4299e1;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: bold;
flex-shrink: 0;
}
.message-content {
max-width: 70%;
}
.message-info {
font-size: 10px;
color: #666;
margin-bottom: 3px;
}
.my-message .message-info {
text-align: right;
}
.message-bubble {
background: white;
padding: 10px 15px;
border-radius: 18px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.my-message .message-bubble {
background: #4299e1;
color: white;
}
/* 메시지 입력 영역 */
.message-input-area {
padding: 20px;
background: white;
border-top: 2px solid #e2e8f0;
display: flex;
gap: 10px;
align-items: center;
}
.message-input {
flex: 1;
padding: 12px 15px;
border: 2px solid #e2e8f0;
border-radius: 25px;
outline: none;
font-size: 14px;
}
.message-input:focus {
border-color: #4299e1;
}
.send-button {
width: 45px;
height: 45px;
background: #4299e1;
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
transition: all 0.3s;
}
.send-button:hover {
background: #3182ce;
transform: scale(1.05);
}
.send-button:disabled {
background: #a0aec0;
cursor: not-allowed;
transform: none;
}
/* 스크롤바 스타일링 */
.messages-container::-webkit-scrollbar,
.room-list::-webkit-scrollbar {
width: 8px;
}
.messages-container::-webkit-scrollbar-track,
.room-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.messages-container::-webkit-scrollbar-thumb,
.room-list::-webkit-scrollbar-thumb {
background: #cbd5e0;
border-radius: 10px;
}
.messages-container::-webkit-scrollbar-thumb:hover,
.room-list::-webkit-scrollbar-thumb:hover {
background: #a0aec0;
}
/* 반응형 디자인 */
@media (max-width: 768px) {
.container {
width: 100%;
height: 100vh;
border-radius: 0;
}
.chat-rooms {
width: 250px;
}
.message-content {
max-width: 85%;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 채팅방 목록 -->
<div class="chat-rooms">
<div class="rooms-header">
🎠 놀이공원 채팅방
</div>
<div class="room-list" id="roomList">
<div class="room-item active" data-room="roller-coaster">
<div class="room-name">🎢 롤러코스터</div>
<div class="room-description">스릴 넘치는 이야기를 나누어요!</div>
</div>
<div class="room-item" data-room="ferris-wheel">
<div class="room-name">🎡 관람차</div>
<div class="room-description">높은 곳에서 보는 경치 이야기</div>
</div>
<div class="room-item" data-room="carousel">
<div class="room-name">🎠 회전목마</div>
<div class="room-description">따뜻하고 평화로운 대화</div>
</div>
<div class="room-item" data-room="bumper-car">
<div class="room-name">🚗 범퍼카</div>
<div class="room-description">재미있는 게임과 놀이 이야기</div>
</div>
</div>
</div>
<!-- 채팅 영역 -->
<div class="chat-area">
<!-- 채팅 헤더 -->
<div class="chat-header">
<div class="current-room" id="currentRoomName">🎢 롤러코스터</div>
<div class="user-info">
<div class="user-avatar" id="userAvatar">🧑</div>
<span id="userName">놀이공원 친구</span>
</div>
</div>
<!-- 메시지 표시 영역 -->
<div class="messages-container" id="messagesContainer">
<div class="message">
<div class="message-avatar">🎪</div>
<div class="message-content">
<div class="message-info">놀이공원 가이드 • 방금전</div>
<div class="message-bubble">안녕하세요! 롤러코스터 채팅방에 오신 걸 환영해요! 🎉</div>
</div>
</div>
</div>
<!-- 메시지 입력 영역 -->
<div class="message-input-area">
<input type="text" class="message-input" id="messageInput"
placeholder="메시지를 입력하세요..." maxlength="500">
<button class="send-button" id="sendButton">📤</button>
</div>
</div>
</div>
<script>
// 현재 사용자 정보
const currentUser = {
id: 'user-' + Math.random().toString(36).substr(2, 9),
name: '놀이공원 친구',
avatar: '🧑'
};
// 현재 선택된 채팅방
let currentRoom = 'roller-coaster';
// 채팅방 정보
const chatRooms = {
'roller-coaster': { name: '🎢 롤러코스터', emoji: '🎢' },
'ferris-wheel': { name: '🎡 관람차', emoji: '🎡' },
'carousel': { name: '🎠 회전목마', emoji: '🎠' },
'bumper-car': { name: '🚗 범퍼카', emoji: '🚗' }
};
// 메시지 데이터 (실제로는 Firebase에서 가져올 데이터)
const messages = {
'roller-coaster': [
{
id: 1,
user: { id: 'guide', name: '놀이공원 가이드', avatar: '🎪' },
message: '안녕하세요! 롤러코스터 채팅방에 오신 걸 환영해요! 🎉',
timestamp: new Date(Date.now() - 60000)
}
],
'ferris-wheel': [
{
id: 1,
user: { id: 'guide', name: '놀이공원 가이드', avatar: '🎪' },
message: '관람차에서 보는 야경이 정말 아름다워요! ✨',
timestamp: new Date(Date.now() - 120000)
}
],
'carousel': [],
'bumper-car': []
};
// DOM 요소들
const roomList = document.getElementById('roomList');
const messagesContainer = document.getElementById('messagesContainer');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
const currentRoomName = document.getElementById('currentRoomName');
const userName = document.getElementById('userName');
const userAvatar = document.getElementById('userAvatar');
// 초기화
function init() {
// 사용자 정보 표시
userName.textContent = currentUser.name;
userAvatar.textContent = currentUser.avatar;
// 채팅방 목록 이벤트 설정
setupRoomListeners();
// 메시지 입력 이벤트 설정
setupMessageInput();
// 현재 채팅방 메시지 표시
displayMessages();
}
// 채팅방 목록 이벤트 설정
function setupRoomListeners() {
roomList.addEventListener('click', (e) => {
const roomItem = e.target.closest('.room-item');
if (roomItem) {
// 이전 활성화된 방 비활성화
document.querySelector('.room-item.active').classList.remove('active');
// 새 방 활성화
roomItem.classList.add('active');
// 현재 방 변경
currentRoom = roomItem.dataset.room;
currentRoomName.textContent = chatRooms[currentRoom].name;
// 메시지 표시
displayMessages();
console.log('채팅방 변경:', currentRoom);
}
});
}
// 메시지 입력 이벤트 설정
function setupMessageInput() {
// 전송 버튼 클릭
sendButton.addEventListener('click', sendMessage);
// 엔터키로 전송
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// 입력 내용에 따른 버튼 상태 변경
messageInput.addEventListener('input', () => {
const hasContent = messageInput.value.trim().length > 0;
sendButton.disabled = !hasContent;
sendButton.style.opacity = hasContent ? '1' : '0.5';
});
}
// 메시지 전송
function sendMessage() {
const messageText = messageInput.value.trim();
if (!messageText) return;
// 새 메시지 객체 생성
const newMessage = {
id: Date.now(),
user: currentUser,
message: messageText,
timestamp: new Date()
};
// 메시지 배열에 추가
if (!messages[currentRoom]) {
messages[currentRoom] = [];
}
messages[currentRoom].push(newMessage);
// 입력창 초기화
messageInput.value = '';
sendButton.disabled = true;
sendButton.style.opacity = '0.5';
// 메시지 표시 업데이트
displayMessages();
console.log('메시지 전송:', newMessage);
// 실제 Firebase에서는 여기서 데이터를 저장합니다
// addDoc(collection(db, 'messages'), newMessage);
}
// 메시지 표시
function displayMessages() {
const roomMessages = messages[currentRoom] || [];
messagesContainer.innerHTML = '';
if (roomMessages.length === 0) {
messagesContainer.innerHTML = `
<div style="text-align: center; color: #a0aec0; margin-top: 50px;">
<div style="font-size: 48px; margin-bottom: 10px;">${chatRooms[currentRoom].emoji}</div>
<div>첫 번째 메시지를 보내보세요!</div>
</div>
`;
return;
}
roomMessages.forEach(msg => {
const messageElement = createMessageElement(msg);
messagesContainer.appendChild(messageElement);
});
// 스크롤을 맨 아래로 이동 (자동 스크롤)
scrollToBottom();
}
// 메시지 요소 생성
function createMessageElement(msg) {
const messageDiv = document.createElement('div');
messageDiv.className = 'message';
// 내 메시지인지 확인
const isMyMessage = msg.user.id === currentUser.id;
if (isMyMessage) {
messageDiv.classList.add('my-message');
}
// 시간 포맷팅
const timeString = formatTime(msg.timestamp);
messageDiv.innerHTML = `
<div class="message-avatar">${msg.user.avatar}</div>
<div class="message-content">
<div class="message-info">${msg.user.name} • ${timeString}</div>
<div class="message-bubble">${escapeHtml(msg.message)}</div>
</div>
`;
return messageDiv;
}
// 시간 포맷팅
function formatTime(timestamp) {
const now = new Date();
const diff = now - timestamp;
if (diff < 60000) { // 1분 미만
return '방금전';
} else if (diff < 3600000) { // 1시간 미만
return Math.floor(diff / 60000) + '분 전';
} else if (diff < 86400000) { // 24시간 미만
return Math.floor(diff / 3600000) + '시간 전';
} else {
return timestamp.toLocaleDateString();
}
}
// HTML 이스케이프
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 스크롤을 맨 아래로 이동
function scrollToBottom() {
setTimeout(() => {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}, 100);
}
// 실시간 업데이트 시뮬레이션 (실제로는 Firebase 리스너 사용)
function simulateRealTimeUpdates() {
setInterval(() => {
// 랜덤하게 다른 사용자의 메시지 추가 (데모용)
if (Math.random() > 0.98) { // 2% 확률
const randomMessages = [
'와! 정말 재미있어요! 😄',
'다음에 또 타고 싶어요!',
'무서웠지만 스릴 넘쳤어요! 🎢',
'친구들과 함께 와서 더 재미있었어요!',
'사진 찍기 좋은 곳이에요! 📸'
];
const randomUsers = [
{ id: 'user1', name: '재미있는 친구', avatar: '😊' },
{ id: 'user2', name: '모험가', avatar: '🤠' },
{ id: 'user3', name: '카메라맨', avatar: '📷' }
];
const randomMessage = {
id: Date.now() + Math.random(),
user: randomUsers[Math.floor(Math.random() * randomUsers.length)],
message: randomMessages[Math.floor(Math.random() * randomMessages.length)],
timestamp: new Date()
};
if (!messages[currentRoom]) {
messages[currentRoom] = [];
}
messages[currentRoom].push(randomMessage);
displayMessages();
console.log('실시간 메시지 수신:', randomMessage);
}
}, 1000);
}
// 앱 시작
init();
simulateRealTimeUpdates();
console.log('놀이공원 채팅 앱이 시작되었습니다! 🎠');
console.log('Firebase를 연결하면 실제 실시간 채팅이 가능해요!');
</script>
</body>
</html>
미리 보기

반응형
'데이터베이스' 카테고리의 다른 글
| [Firebase] 4장 FirebaseStorage를 활용한 파일 업로드 실습 (3) | 2025.08.23 |
|---|---|
| [Firebase] 3장 Cloud firestore 데이터베이스 실습 (1) | 2025.08.23 |
| [Firebase] 2장 사용자 인증(Authentication) 실습 (0) | 2025.08.23 |
| [Firebase] 1장: Firebase 소개 및 개발 환경 설정 (0) | 2025.08.23 |