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
3
Compatibilidad del Navegador
No
Vista Previa en Vivo
Interactúa con el componente sin salir de la página.
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">
<!-- Encabezado del Chat -->
<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>
<!-- Barra de Búsqueda -->
<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>
<!-- Contenedor de Mensajes -->
<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>
<!-- Indicador de Escritura -->
<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>
<!-- Entrada de Mensaje -->
<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>
<!-- Modal de Carga de Archivos -->
<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/*,application/pdf,.doc,.docx">
</div>
<div class="file-preview" id="filePreview"></div>
</div>
</div>
<!-- Selector de Emojis -->
<div class="emoji-picker" id="emojiPicker">
<div class="emoji-categories">
<button class="emoji-category active" data-category="smileys">😀</button>
<button class="emoji-category" data-category="people">👤</button>
<button class="emoji-category" data-category="nature">🌿</button>
<button class="emoji-category" data-category="food">🍎</button>
<button class="emoji-category" data-category="activities">⚽</button>
<button class="emoji-category" data-category="travel">🚗</button>
<button class="emoji-category" data-category="objects">💡</button>
<button class="emoji-category" data-category="symbols">❤️</button>
</div>
<div class="emoji-grid" id="emojiGrid">
<!-- Los emojis serán poblados por JavaScript -->
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
Estilos CSS
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.chat-container {
width: 100%;
max-width: 800px;
height: 600px;
background: white;
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
/* Encabezado del Chat */
.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);
}
/* Barra de Búsqueda */
.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;
}
/* Contenedor de Mensajes */
.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;
}
/* Indicador de Escritura */
.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;
}
/* Entrada de Mensaje */
.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 */
.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;
}
/* Selector de Emojis */
.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);
}
/* Animaciones */
@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);
}
}
/* Diseño Responsivo */
@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();
// Simular indicador de escritura
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() {
// Enviar mensaje
this.sendBtn.addEventListener('click', () => this.sendMessage());
this.messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
// Funcionalidad de búsqueda
this.searchBtn.addEventListener('click', () => this.toggleSearch());
this.closeSearch.addEventListener('click', () => this.toggleSearch());
this.searchInput.addEventListener('input', (e) => this.searchMessages(e.target.value));
// Carga de archivos
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));
// Arrastrar y soltar
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 } });
});
// Selector de emojis
this.emojiBtn.addEventListener('click', () => this.toggleEmojiPicker());
// Cerrar modales al hacer clic fuera
document.addEventListener('click', (e) => {
if (!this.emojiPicker.contains(e.target) && !this.emojiBtn.contains(e.target)) {
this.emojiPicker.classList.remove('active');
}
});
// Simulación de escritura
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();
// Simular respuesta
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();
// Marcar como leído después de un retraso
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() {
// En una aplicación real, esto enviaría el estado de escritura a otros usuarios
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();
// Simular respuesta
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: ['❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '💟', '☮️', '✝️', '☪️', '🕉️', '☸️', '✡️', '🔯', '🕎', '☯️', '☦️', '🛐']
};
// Inicializar categorías de emojis
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);
});
});
// Cargar categoría por defecto
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;
}
}
// Inicializar interfaz de chat
const chatInterface = new ChatInterface();
// Agregar funcionalidad de demostración
document.addEventListener('DOMContentLoaded', () => {
// Simular mensajes iniciales
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);
// Agregar atajos de teclado
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + K para enfocar búsqueda
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
chatInterface.toggleSearch();
}
// Escape para cerrar modales
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
// Inicializar la interfaz de chat
const chat = new ChatInterface();
// Enviar un mensaje programáticamente
chat.addMessage({
id: Date.now(),
text: "¡Hola, mundo!",
sender: "usuario",
timestamp: new Date(),
status: "enviado"
});
Configuración Personalizada
// Personalizar la interfaz de chat
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
// Ejemplo de integración con WebSocket
const socket = new WebSocket('ws://localhost:8080');
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
chat.addMessage(message);
};
// Sobrescribir envío de mensaje para usar WebSocket
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.
HTML
1
líneas
CSS
1
líneas
JavaScript
1
líneas
<!-- HTML content will be added here -->