Interfaz de Chat Moderna
Una interfaz de chat moderna con mensajería en tiempo real, carga de archivos, selector de emojis e indicadores de escritura
Diseño Responsivo
Sí
Soporte para Modo Oscuro
No
líneas
—
Compatibilidad del Navegador
No
Interfaz de Chat Moderna
Una interfaz de chat integral con capacidades de mensajería en tiempo real, carga de archivos, selector de emojis, indicadores de escritura y seguimiento del estado de mensajes.
Características
- Mensajería en Tiempo Real: Entrega y visualización instantánea de mensajes
- Carga de Archivos: Soporte para imágenes, documentos y otros archivos
- Selector de Emojis: Selección de emojis integrada con categorías
- Indicadores de Escritura: Muestra cuando los usuarios están escribiendo
- Estado de Mensajes: Confirmaciones de lectura y estado de entrega
- Presencia de Usuario: Indicadores de estado en línea/fuera de línea
- Búsqueda de Mensajes: Buscar en el historial de chat
- Diseño Responsivo: Funciona en todos los tamaños de dispositivo
Estructura HTML
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interfaz de Chat Moderna</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<div class="chat-info">
<div class="avatar">
<img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?w=40&h=40&fit=crop&crop=face" alt="Usuario">
<span class="status-indicator online"></span>
</div>
<div class="user-details">
<h3>Sarah Johnson</h3>
<span class="status-text">En línea</span>
</div>
</div>
<div class="chat-actions">
<button class="action-btn" id="searchBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
</button>
<button class="action-btn" id="callBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
</svg>
</button>
<button class="action-btn" id="moreBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="12" cy="5" r="1"></circle>
<circle cx="12" cy="19" r="1"></circle>
</svg>
</button>
</div>
</div>
<div class="search-bar" id="searchBar">
<input type="text" placeholder="Buscar mensajes..." id="searchInput">
<button class="close-search" id="closeSearch">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="messages-container" id="messagesContainer">
<div class="message received">
<div class="message-avatar">
<img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?w=32&h=32&fit=crop&crop=face" alt="Sarah">
</div>
<div class="message-content">
<div class="message-bubble">
<p>¡Hola! ¿Cómo estás hoy?</p>
</div>
<div class="message-info">
<span class="message-time">10:30 AM</span>
</div>
</div>
</div>
<div class="message sent">
<div class="message-content">
<div class="message-bubble">
<p>¡Estoy genial! Acabo de terminar un nuevo proyecto. ¿Y tú?</p>
</div>
<div class="message-info">
<span class="message-time">10:32 AM</span>
<span class="message-status read">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="20,6 9,17 4,12"></polyline>
</svg>
</span>
</div>
</div>
</div>
<div class="message received">
<div class="message-avatar">
<img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?w=32&h=32&fit=crop&crop=face" alt="Sarah">
</div>
<div class="message-content">
<div class="message-bubble image-message">
<img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=300&h=200&fit=crop" alt="Imagen compartida">
<p>¡Mira esta increíble puesta de sol que capturé!</p>
</div>
<div class="message-info">
<span class="message-time">10:35 AM</span>
</div>
</div>
</div>
<div class="typing-indicator" id="typingIndicator">
<div class="message-avatar">
<img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?w=32&h=32&fit=crop&crop=face" alt="Sarah">
</div>
<div class="typing-bubble">
<div class="typing-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<div class="message-input-container">
<div class="input-actions">
<button class="input-btn" id="attachBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66L9.64 16.2a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
</svg>
</button>
<button class="input-btn" id="emojiBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="12" cy="12" r="10"></circle>
<path d="8 14s1.5 2 4 2 4-2 4-2"></path>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</svg>
</button>
</div>
<div class="input-wrapper">
<textarea
id="messageInput"
placeholder="Escribe un mensaje..."
rows="1"
></textarea>
<button class="send-btn" id="sendBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22,2 15,22 11,13 2,9"></polygon>
</svg>
</button>
</div>
</div>
<div class="modal" id="fileModal">
<div class="modal-content">
<div class="modal-header">
<h3>Compartir Archivo</h3>
<button class="close-modal" id="closeFileModal">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="file-upload-area" id="fileUploadArea">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14,2 14,8 20,8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10,9 9,9 8,9"></polyline>
</svg>
<p>Arrastra y suelta archivos aquí o haz clic para explorar</p>
<input type="file" id="fileInput" multiple accept="image.chat-header {
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: space-between;
}
.chat-info {
display: flex;
align-items: center;
gap: 12px;
}
.avatar {
position: relative;
}
.avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.status-indicator {
position: absolute;
bottom: 0;
right: 0;
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid white;
}
.status-indicator.online {
background: #4ade80;
}
.status-indicator.offline {
background: #94a3b8;
}
.user-details h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 2px;
}
.status-text {
font-size: 12px;
opacity: 0.8;
}
.chat-actions {
display: flex;
gap: 8px;
}
.action-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
width: 36px;
height: 36px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.action-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-1px);
}.search-bar {
padding: 12px 20px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
display: none;
align-items: center;
gap: 12px;
}
.search-bar.active {
display: flex;
}
.search-bar input {
flex: 1;
padding: 8px 12px;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 14px;
outline: none;
}
.search-bar input:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.close-search {
background: none;
border: none;
color: #64748b;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s ease;
}
.close-search:hover {
background: #e2e8f0;
}.messages-container {
flex: 1;
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
background: #f8fafc;
}
.message {
display: flex;
gap: 8px;
max-width: 70%;
animation: messageSlide 0.3s ease;
}
.message.sent {
align-self: flex-end;
flex-direction: row-reverse;
}
.message-avatar img {
width: 32px;
height: 32px;
border-radius: 50%;
}
.message-content {
display: flex;
flex-direction: column;
gap: 4px;
}
.message.sent .message-content {
align-items: flex-end;
}
.message-bubble {
padding: 12px 16px;
border-radius: 18px;
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
position: relative;
}
.message.sent .message-bubble {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.message-bubble p {
margin: 0;
line-height: 1.4;
font-size: 14px;
}
.image-message img {
width: 100%;
max-width: 300px;
border-radius: 12px;
margin-bottom: 8px;
}
.message-info {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: #64748b;
}
.message.sent .message-info {
flex-direction: row-reverse;
}
.message-status {
display: flex;
align-items: center;
}
.message-status.read {
color: #667eea;
}.typing-indicator {
display: flex;
gap: 8px;
align-items: flex-end;
opacity: 0;
transform: translateY(10px);
transition: all 0.3s ease;
}
.typing-indicator.active {
opacity: 1;
transform: translateY(0);
}
.typing-bubble {
background: white;
padding: 12px 16px;
border-radius: 18px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.typing-dots {
display: flex;
gap: 4px;
}
.typing-dots span {
width: 6px;
height: 6px;
background: #94a3b8;
border-radius: 50%;
animation: typingDot 1.4s infinite;
}
.typing-dots span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-dots span:nth-child(3) {
animation-delay: 0.4s;
}.message-input-container {
padding: 20px;
background: white;
border-top: 1px solid #e2e8f0;
display: flex;
gap: 12px;
align-items: flex-end;
}
.input-actions {
display: flex;
gap: 8px;
}
.input-btn {
background: #f1f5f9;
border: none;
color: #64748b;
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.input-btn:hover {
background: #e2e8f0;
color: #475569;
}
.input-wrapper {
flex: 1;
display: flex;
gap: 8px;
align-items: flex-end;
}
#messageInput {
flex: 1;
padding: 12px 16px;
border: 1px solid #e2e8f0;
border-radius: 12px;
font-size: 14px;
font-family: inherit;
resize: none;
outline: none;
min-height: 40px;
max-height: 120px;
line-height: 1.4;
}
#messageInput:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.send-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.send-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
box-shadow: none;
}.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
border-radius: 16px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow: hidden;
animation: modalSlide 0.3s ease;
}
.modal-header {
padding: 20px;
border-bottom: 1px solid #e2e8f0;
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-header h3 {
font-size: 18px;
font-weight: 600;
}
.close-modal {
background: none;
border: none;
color: #64748b;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s ease;
}
.close-modal:hover {
background: #f1f5f9;
}
.file-upload-area {
padding: 40px 20px;
text-align: center;
border: 2px dashed #e2e8f0;
margin: 20px;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.file-upload-area:hover {
border-color: #667eea;
background: #f8fafc;
}
.file-upload-area svg {
color: #94a3b8;
margin-bottom: 12px;
}
.file-upload-area p {
color: #64748b;
font-size: 14px;
}
#fileInput {
display: none;
}
.file-preview {
padding: 20px;
border-top: 1px solid #e2e8f0;
display: none;
}
.file-preview.active {
display: block;
}
.file-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: #f8fafc;
border-radius: 8px;
margin-bottom: 8px;
}
.file-item img {
width: 40px;
height: 40px;
object-fit: cover;
border-radius: 4px;
}
.file-info {
flex: 1;
}
.file-name {
font-weight: 500;
font-size: 14px;
margin-bottom: 2px;
}
.file-size {
font-size: 12px;
color: #64748b;
}.emoji-picker {
position: absolute;
bottom: 80px;
left: 20px;
width: 300px;
height: 350px;
background: white;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
display: none;
flex-direction: column;
z-index: 100;
}
.emoji-picker.active {
display: flex;
}
.emoji-categories {
display: flex;
padding: 12px;
border-bottom: 1px solid #e2e8f0;
gap: 4px;
}
.emoji-category {
background: none;
border: none;
padding: 8px;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
transition: all 0.2s ease;
}
.emoji-category:hover,
.emoji-category.active {
background: #f1f5f9;
}
.emoji-grid {
flex: 1;
padding: 12px;
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 4px;
overflow-y: auto;
}
.emoji-item {
background: none;
border: none;
padding: 8px;
border-radius: 6px;
cursor: pointer;
font-size: 18px;
transition: all 0.2s ease;
}
.emoji-item:hover {
background: #f1f5f9;
transform: scale(1.2);
}@keyframes messageSlide {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes typingDot {
0%, 60%, 100% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
}
@keyframes modalSlide {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}@media (max-width: 768px) {
body {
padding: 0;
}
.chat-container {
height: 100vh;
border-radius: 0;
max-width: none;
}
.message {
max-width: 85%;
}
.emoji-picker {
left: 10px;
right: 10px;
width: auto;
}
.modal-content {
width: 95%;
margin: 20px;
}
}
@media (max-width: 480px) {
.chat-header {
padding: 16px;
}
.messages-container {
padding: 16px;
}
.message-input-container {
padding: 16px;
}
.user-details h3 {
font-size: 14px;
}
.status-text {
font-size: 11px;
}
}
Funcionalidad JavaScript
class ChatInterface {
constructor() {
this.messages = [];
this.isTyping = false;
this.currentUser = 'usuario';
this.otherUser = 'Sarah Johnson';
this.initializeElements();
this.initializeEventListeners();
this.initializeEmojis();
this.autoResizeTextarea();
this.simulateTyping();
}
initializeElements() {
this.messagesContainer = document.getElementById('messagesContainer');
this.messageInput = document.getElementById('messageInput');
this.sendBtn = document.getElementById('sendBtn');
this.searchBtn = document.getElementById('searchBtn');
this.searchBar = document.getElementById('searchBar');
this.searchInput = document.getElementById('searchInput');
this.closeSearch = document.getElementById('closeSearch');
this.attachBtn = document.getElementById('attachBtn');
this.emojiBtn = document.getElementById('emojiBtn');
this.fileModal = document.getElementById('fileModal');
this.closeFileModal = document.getElementById('closeFileModal');
this.fileUploadArea = document.getElementById('fileUploadArea');
this.fileInput = document.getElementById('fileInput');
this.filePreview = document.getElementById('filePreview');
this.emojiPicker = document.getElementById('emojiPicker');
this.emojiGrid = document.getElementById('emojiGrid');
this.typingIndicator = document.getElementById('typingIndicator');
}
initializeEventListeners() {
this.sendBtn.addEventListener('click', () => this.sendMessage());
this.messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
this.searchBtn.addEventListener('click', () => this.toggleSearch());
this.closeSearch.addEventListener('click', () => this.toggleSearch());
this.searchInput.addEventListener('input', (e) => this.searchMessages(e.target.value));
this.attachBtn.addEventListener('click', () => this.openFileModal());
this.closeFileModal.addEventListener('click', () => this.closeFileModalHandler());
this.fileUploadArea.addEventListener('click', () => this.fileInput.click());
this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
this.fileUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
this.fileUploadArea.style.borderColor = '#667eea';
});
this.fileUploadArea.addEventListener('dragleave', () => {
this.fileUploadArea.style.borderColor = '#e2e8f0';
});
this.fileUploadArea.addEventListener('drop', (e) => {
e.preventDefault();
this.fileUploadArea.style.borderColor = '#e2e8f0';
this.handleFileSelect({ target: { files: e.dataTransfer.files } });
});
this.emojiBtn.addEventListener('click', () => this.toggleEmojiPicker());
document.addEventListener('click', (e) => {
if (!this.emojiPicker.contains(e.target) && !this.emojiBtn.contains(e.target)) {
this.emojiPicker.classList.remove('active');
}
});
this.messageInput.addEventListener('input', () => {
this.updateSendButton();
this.simulateUserTyping();
});
}
sendMessage() {
const text = this.messageInput.value.trim();
if (!text) return;
const message = {
id: Date.now(),
text: text,
sender: this.currentUser,
timestamp: new Date(),
status: 'enviado'
};
this.addMessage(message);
this.messageInput.value = '';
this.updateSendButton();
this.autoResizeTextarea();
setTimeout(() => {
this.simulateResponse();
}, 1000 + Math.random() * 2000);
}
addMessage(message) {
this.messages.push(message);
const messageElement = document.createElement('div');
messageElement.className = `message ${message.sender === this.currentUser ? 'sent' : 'received'}`;
if (message.sender !== this.currentUser) {
messageElement.innerHTML = `
<div class="message-avatar">
<img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?w=32&h=32&fit=crop&crop=face" alt="${this.otherUser}">
</div>
<div class="message-content">
<div class="message-bubble">
${message.image ? `<img src="${message.image}" alt="Imagen compartida">` : ''}
<p>${this.escapeHtml(message.text)}</p>
</div>
<div class="message-info">
<span class="message-time">${this.formatTime(message.timestamp)}</span>
</div>
</div>
`;
} else {
messageElement.innerHTML = `
<div class="message-content">
<div class="message-bubble">
${message.image ? `<img src="${message.image}" alt="Imagen compartida">` : ''}
<p>${this.escapeHtml(message.text)}</p>
</div>
<div class="message-info">
<span class="message-time">${this.formatTime(message.timestamp)}</span>
<span class="message-status ${message.status}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="20,6 9,17 4,12"></polyline>
</svg>
</span>
</div>
</div>
`;
}
this.messagesContainer.insertBefore(messageElement, this.typingIndicator);
this.scrollToBottom();
if (message.sender === this.currentUser) {
setTimeout(() => {
const statusElement = messageElement.querySelector('.message-status');
if (statusElement) {
statusElement.classList.add('read');
}
}, 1000);
}
}
simulateResponse() {
const responses = [
"¡Eso es interesante! Cuéntame más.",
"Estoy completamente de acuerdo contigo.",
"¡Gracias por compartir eso!",
"¡Eso suena increíble!",
"Definitivamente lo revisaré.",
"¡Gran idea! Hagámoslo.",
"Eso me hizo sonreír 😊",
"¡Estoy muy emocionada por esto!"
];
const response = {
id: Date.now(),
text: responses[Math.floor(Math.random() * responses.length)],
sender: this.otherUser,
timestamp: new Date()
};
this.addMessage(response);
}
simulateTyping() {
setInterval(() => {
if (Math.random() < 0.1 && !this.isTyping) {
this.showTypingIndicator();
setTimeout(() => {
this.hideTypingIndicator();
if (Math.random() < 0.7) {
this.simulateResponse();
}
}, 2000 + Math.random() * 3000);
}
}, 5000);
}
simulateUserTyping() {
console.log('El usuario está escribiendo...');
}
showTypingIndicator() {
this.isTyping = true;
this.typingIndicator.classList.add('active');
this.scrollToBottom();
}
hideTypingIndicator() {
this.isTyping = false;
this.typingIndicator.classList.remove('active');
}
toggleSearch() {
this.searchBar.classList.toggle('active');
if (this.searchBar.classList.contains('active')) {
this.searchInput.focus();
} else {
this.searchInput.value = '';
this.clearSearchHighlights();
}
}
searchMessages(query) {
this.clearSearchHighlights();
if (!query.trim()) return;
const messages = this.messagesContainer.querySelectorAll('.message');
messages.forEach(message => {
const text = message.textContent.toLowerCase();
if (text.includes(query.toLowerCase())) {
message.style.backgroundColor = '#fef3c7';
message.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
}
clearSearchHighlights() {
const messages = this.messagesContainer.querySelectorAll('.message');
messages.forEach(message => {
message.style.backgroundColor = '';
});
}
openFileModal() {
this.fileModal.classList.add('active');
}
closeFileModalHandler() {
this.fileModal.classList.remove('active');
this.filePreview.classList.remove('active');
this.filePreview.innerHTML = '';
this.fileInput.value = '';
}
handleFileSelect(event) {
const files = Array.from(event.target.files);
if (files.length === 0) return;
this.filePreview.innerHTML = '';
this.filePreview.classList.add('active');
files.forEach(file => {
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
fileItem.innerHTML = `
<img src="${e.target.result}" alt="${file.name}">
<div class="file-info">
<div class="file-name">${file.name}</div>
<div class="file-size">${this.formatFileSize(file.size)}</div>
</div>
<button class="send-file-btn" onclick="chatInterface.sendFile('${e.target.result}', '${file.name}')">
Enviar
</button>
`;
};
reader.readAsDataURL(file);
} else {
fileItem.innerHTML = `
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14,2 14,8 20,8"></polyline>
</svg>
<div class="file-info">
<div class="file-name">${file.name}</div>
<div class="file-size">${this.formatFileSize(file.size)}</div>
</div>
<button class="send-file-btn" onclick="chatInterface.sendFile(null, '${file.name}')">
Enviar
</button>
`;
}
this.filePreview.appendChild(fileItem);
});
}
sendFile(imageData, fileName) {
const message = {
id: Date.now(),
text: imageData ? `Compartido: ${fileName}` : `Archivo compartido: ${fileName}`,
sender: this.currentUser,
timestamp: new Date(),
status: 'enviado',
image: imageData
};
this.addMessage(message);
this.closeFileModalHandler();
setTimeout(() => {
const response = {
id: Date.now(),
text: imageData ? "¡Bonita foto! 📸" : "¡Gracias por compartir el archivo!",
sender: this.otherUser,
timestamp: new Date()
};
this.addMessage(response);
}, 1500);
}
toggleEmojiPicker() {
this.emojiPicker.classList.toggle('active');
}
initializeEmojis() {
const emojiCategories = {
smileys: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳'],
people: ['👶', '🧒', '👦', '👧', '🧑', '👱', '👨', '🧔', '👩', '🧓', '👴', '👵', '🙍', '🙎', '🙅', '🙆', '💁', '🙋', '🧏', '🙇', '🤦', '🤷', '👮', '🕵️', '💂', '👷', '🤴', '👸', '👳', '👲'],
nature: ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐨', '🐯', '🦁', '🐮', '🐷', '🐽', '🐸', '🐵', '🙈', '🙉', '🙊', '🐒', '🐔', '🐧', '🐦', '🐤', '🐣', '🐥', '🦆', '🦅', '🦉', '🦇'],
food: ['🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🍈', '🍒', '🍑', '🥭', '🍍', '🥥', '🥝', '🍅', '🍆', '🥑', '🥦', '🥬', '🥒', '🌶️', '🌽', '🥕', '🧄', '🧅', '🥔', '🍠', '🥐', '🍞'],
activities: ['⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🎱', '🪀', '🏓', '🏸', '🏒', '🏑', '🥍', '🏏', '🪃', '🥅', '⛳', '🪁', '🏹', '🎣', '🤿', '🥊', '🥋', '🎽', '🛹', '🛷', '⛸️'],
travel: ['🚗', '🚕', '🚙', '🚌', '🚎', '🏎️', '🚓', '🚑', '🚒', '🚐', '🛻', '🚚', '🚛', '🚜', '🏍️', '🛵', '🚲', '🛴', '🛺', '🚨', '🚔', '🚍', '🚘', '🚖', '🚡', '🚠', '🚟', '🚃', '🚋', '🚞'],
objects: ['💡', '🔦', '🕯️', '🪔', '🧯', '🛢️', '💸', '💵', '💴', '💶', '💷', '💰', '💳', '💎', '⚖️', '🧰', '🔧', '🔨', '⚒️', '🛠️', '⛏️', '🔩', '⚙️', '🧱', '⛓️', '🧲', '🔫', '💣', '🧨', '🪓'],
symbols: ['❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '💟', '☮️', '✝️', '☪️', '🕉️', '☸️', '✡️', '🔯', '🕎', '☯️', '☦️', '🛐']
};
const categoryButtons = document.querySelectorAll('.emoji-category');
categoryButtons.forEach(button => {
button.addEventListener('click', () => {
categoryButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
this.loadEmojiCategory(button.dataset.category, emojiCategories);
});
});
this.loadEmojiCategory('smileys', emojiCategories);
}
loadEmojiCategory(category, emojiCategories) {
this.emojiGrid.innerHTML = '';
emojiCategories[category].forEach(emoji => {
const emojiButton = document.createElement('button');
emojiButton.className = 'emoji-item';
emojiButton.textContent = emoji;
emojiButton.addEventListener('click', () => {
this.insertEmoji(emoji);
});
this.emojiGrid.appendChild(emojiButton);
});
}
insertEmoji(emoji) {
const cursorPos = this.messageInput.selectionStart;
const textBefore = this.messageInput.value.substring(0, cursorPos);
const textAfter = this.messageInput.value.substring(cursorPos);
this.messageInput.value = textBefore + emoji + textAfter;
this.messageInput.focus();
this.messageInput.setSelectionRange(cursorPos + emoji.length, cursorPos + emoji.length);
this.updateSendButton();
this.emojiPicker.classList.remove('active');
}
autoResizeTextarea() {
this.messageInput.style.height = 'auto';
this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';
}
updateSendButton() {
const hasText = this.messageInput.value.trim().length > 0;
this.sendBtn.disabled = !hasText;
}
scrollToBottom() {
this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
}
formatTime(date) {
return date.toLocaleTimeString('es-ES', {
hour: 'numeric',
minute: '2-digit',
hour12: true
});
}
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
const chatInterface = new ChatInterface();
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
const welcomeMessage = {
id: Date.now(),
text: "¡Bienvenido a nuestro chat! Siéntete libre de enviar mensajes, compartir archivos o usar emojis. 🎉",
sender: 'Sarah Johnson',
timestamp: new Date()
};
chatInterface.addMessage(welcomeMessage);
}, 1000);
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
chatInterface.toggleSearch();
}
if (e.key === 'Escape') {
chatInterface.emojiPicker.classList.remove('active');
chatInterface.fileModal.classList.remove('active');
if (chatInterface.searchBar.classList.contains('active')) {
chatInterface.toggleSearch();
}
}
});
});
Ejemplos de Uso
Implementación Básica
const chat = new ChatInterface();
chat.addMessage({
id: Date.now(),
text: "¡Hola, mundo!",
sender: "usuario",
timestamp: new Date(),
status: "enviado"
});
Configuración Personalizada
const chat = new ChatInterface({
currentUser: 'juan_perez',
otherUser: 'María García',
autoScroll: true,
enableTypingIndicator: true,
maxFileSize: 10 * 1024 * 1024, // 10MB
allowedFileTypes: ['image/*', 'application/pdf']
});
Integración con Servicios en Tiempo Real
const socket = new WebSocket('ws://localhost:8080');
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
chat.addMessage(message);
};
chat.sendMessage = function() {
const text = this.messageInput.value.trim();
if (!text) return;
const message = {
id: Date.now(),
text: text,
sender: this.currentUser,
timestamp: new Date()
};
socket.send(JSON.stringify(message));
this.addMessage(message);
this.messageInput.value = '';
};
Compatibilidad de Navegadores
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
Características Incluidas
✅ Interfaz de mensajería en tiempo real
✅ Carga de archivos con arrastrar y soltar
✅ Selector de emojis con categorías
✅ Indicadores de escritura
✅ Estado de mensajes (enviado/leído)
✅ Indicadores de presencia de usuario
✅ Funcionalidad de búsqueda de mensajes
✅ Diseño responsivo
✅ Atajos de teclado
✅ Entrada de texto auto-redimensionable
✅ Animaciones suaves
✅ Soporte de accesibilidad
Opciones de Personalización
- Colores: Modificar propiedades CSS personalizadas para temas
- Diseño: Ajustar dimensiones y posicionamiento del contenedor
- Características: Habilitar/deshabilitar funcionalidad específica
- Integraciones: Conectar a servicios en tiempo real
- Tipos de Archivo: Configurar formatos de carga permitidos
- Conjuntos de Emojis: Personalizar categorías de emojis disponibles
Esta interfaz de chat proporciona una base sólida para construir aplicaciones de mensajería modernas con todas las características esenciales que los usuarios esperan.