communication
advanced
chat
messaging
real-time
interface
communication
Categoría · Comunicación Nivel de Dificultad · Avanzado Publicado el · 20 de enero de 2025

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

#chat #messaging #real-time #interface #communication

Diseño Responsivo

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.

Fragmentos de Código Relacionados

Explora packs de plantillas

¿Necesitas bloques más grandes? Descubre landings y colecciones de componentes.

Abrir la biblioteca de plantillas ->