<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DIJ Socket.io demo</title>
</head>
<body>
  <ul class="pages">
    <li class="chat page">
      <div class="chatArea">
        <ul class="messages"></ul>
      </div>
      <input class="inputMessage" placeholder="Type hier..."/>
    </li>
    <li class="login page">
      <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 463 327" class="dij_logo"><path d="M539.32 86.69H76.11v236.14h463.21V86.69z" transform="translate(-75.98 -86.69)"></path><path fill="#ee2d38" d="M307 261.23l-21.65-21.63L378 146.91l21.63 21.63z" transform="translate(-75.98 -86.69)"></path><path fill="#fff" d="M259.72 204a68.73 68.73 0 0 1-4.9 26.23A61.42 61.42 0 0 1 241 251a62.61 62.61 0 0 1-21.46 13.67 75.84 75.84 0 0 1-27.84 4.91h-50.91V138.5h50.92a75.43 75.43 0 0 1 27.84 4.94A63.54 63.54 0 0 1 241 157.12a60.92 60.92 0 0 1 13.81 20.73 68.49 68.49 0 0 1 4.91 26.15zm-31.12 0a57.11 57.11 0 0 0-2.52-17.54 37.48 37.48 0 0 0-7.25-13.27 31.44 31.44 0 0 0-11.56-8.37 39.18 39.18 0 0 0-15.56-2.92h-20.33v84.29h20.33a39.18 39.18 0 0 0 15.56-2.92 31.53 31.53 0 0 0 11.56-8.36 37.61 37.61 0 0 0 7.25-13.28A57.34 57.34 0 0 0 228.6 204zM444.23 135.05v84.21a50.41 50.41 0 0 1-1.08 11.33 19.41 19.41 0 0 1-3.24 7.42 11.91 11.91 0 0 1-5.44 4.05 22.12 22.12 0 0 1-7.69 1.22 29.63 29.63 0 0 1-3.71-.22v24.25c2.3.17 4.61.26 6.95.26a50.14 50.14 0 0 0 19.7-3.56 37.15 37.15 0 0 0 13.9-10 42.34 42.34 0 0 0 8.28-15.43 68.58 68.58 0 0 0 2.74-20v-83.53z" transform="translate(-75.98 -86.69)"></path><path fill="#ee2d38" d="M517.25 369.27l-5.07-5.08 21.75-21.75 5.07 5.08zM498.93 368.36l-5.07-5.07 21.75-21.75 5.07 5.08z" transform="translate(-75.98 -86.69)"></path><path d="M87 343.39c8.37 0 14.2 5.1 14.2 13s-5.87 13.09-14.2 13.09H76v-26.09zm0 19.26a6.22 6.22 0 1 0 0-12.43h-3.3v12.43zM113.34 353.37h10.75v6.21h-10.75V363h12.55v6.48h-20.26v-26.09h20.26v6.48h-12.55zM142 343.39c8.37 0 14.2 5.1 14.2 13s-5.83 13.08-14.2 13.08h-11v-26.08zm0 19.26a6.22 6.22 0 1 0 0-12.43h-3.26v12.43zM168.42 369.48h-7.72v-26.09h7.72zM192.47 358.24h7.95c-.46 6.95-5.8 11.82-13.4 11.82-8.25 0-14.08-5.79-14.08-13.62s5.83-13.63 14.08-13.63c7.6 0 12.94 4.88 13.4 11.82h-7.95a5.35 5.35 0 0 0-5.56-4.64c-3.76 0-6.1 2.88-6.1 6.45s2.34 6.44 6.1 6.44a5.35 5.35 0 0 0 5.56-4.64zM211.12 365.76l-1.35 3.72h-8.25l10.83-26.24h7.56l10.82 26.24h-8.21l-1.39-3.72zm5-13.7l-2.69 7.41h5.41zM244.88 350.1v19.38h-7.75V350.1h-7.75v-6.71h23.25v6.71zM263.83 353.37h10.74v6.21h-10.74V363h12.55v6.48h-20.26v-26.09h20.26v6.48h-12.55zM292.46 343.39c8.37 0 14.2 5.1 14.2 13s-5.83 13.08-14.2 13.08h-11v-26.08zm0 19.26a6.22 6.22 0 1 0 0-12.43h-3.26v12.43zM333.53 343.39c8.37 0 14.2 5.1 14.2 13s-5.83 13.08-14.2 13.08h-11v-26.08zm0 19.26a6.22 6.22 0 1 0 0-12.43h-3.26v12.43zM360 369.48h-7.71v-26.09H360zM392.28 369.48h-5.6l-.19-2.41a10.86 10.86 0 0 1-7.76 3c-8.44 0-14.23-5.68-14.23-13.51s5.87-13.74 14.16-13.74c7.21 0 12.74 4.19 13.35 10.1h-8c-.88-2.31-2.84-3.19-5.18-3.19a6.25 6.25 0 0 0-6.45 6.6 6.46 6.46 0 0 0 6.87 6.68 7.14 7.14 0 0 0 5.84-2.84l-7.18-.08v-5.45h14.39zM405.43 369.48h-7.72v-26.09h7.72zM424.35 350.1v19.38h-7.75V350.1h-7.76v-6.71h23.26v6.71zM440.32 365.76l-1.32 3.72h-8.25l10.82-26.24h7.56l10.82 26.24h-8.21l-1.38-3.72zm5-13.7l-2.69 7.41H448zM483.3 369.48h-20.11v-26.09h7.72v19.42h12.39zM87 387.74c8.37 0 14.2 5.11 14.2 13s-5.87 13.1-14.2 13.1H76v-26.1zM87 407a6.22 6.22 0 1 0 0-12.44h-3.3V407zM113.34 397.72h10.75v6.22h-10.75v3.41h12.55v6.49h-20.26v-26.1h20.26v6.49h-12.55zM147.82 414h-8.29l-10.86-26.25h8.56l6.45 16.16 6.48-16.16h8.52zM169.55 397.72h10.74v6.22h-10.74v3.41h12.55v6.49h-20.26v-26.1h20.26v6.49h-12.55zM207.32 413.84h-20.11v-26.1h7.71v19.42h12.4zM238 400.79c0 7.71-5.83 13.62-14.08 13.62s-14.08-5.91-14.08-13.62 5.83-13.62 14.08-13.62S238 393.08 238 400.79zm-7.86 0a6.22 6.22 0 1 0-6.22 6.37 6.23 6.23 0 0 0 6.21-6.37zM253.51 387.74c7.37 0 11.75 3.69 11.75 9.94s-4.45 10.13-12 10.13h-3v6h-7.71v-26.1zm-.07 13.51c2.49 0 4-1.34 4-3.57s-1.5-3.38-4-3.38h-3.19v6.95zM298.23 413.84h-7.75v-11.63l-4.8 7.25h-4.07v-.11l-4.83-7.26v11.71h-7.75v-26.1H275l8.64 12.17 8.63-12.17h6zM311.58 397.72h10.75v6.22h-10.75v3.41h12.55v6.49h-20.26v-26.1h20.26v6.49h-12.55zM353.88 413.84h-6L337 401.52v12.32h-7.72v-26.1h6l10.86 12.55v-12.55h7.75zM372.86 394.46v19.38h-7.75v-19.38h-7.75v-6.72h23.26v6.72zM411.13 394.46v19.38h-7.75v-19.38h-7.75v-6.72h23.25v6.72zM430.08 397.72h10.74v6.22h-10.74v3.41h12.55v6.49h-20.26v-26.1h20.26v6.49h-12.55zM455.12 410.11l-1.35 3.73h-8.25l10.82-26.25h7.56l10.83 26.25h-8.22l-1.38-3.73zm5-13.7l-2.68 7.41h5.41zM507.15 413.84h-7.75v-11.63l-4.79 7.25h-4.07v-.11l-4.84-7.26v11.71H478v-26.1h5.95l8.63 12.17 8.64-12.17h5.94zM535 396h-7.79a3 3 0 0 0-3.34-2.57c-1.53 0-2.64.61-2.64 1.76s.69 1.46 2.11 1.77l4.26 1c4.41 1.07 7.63 3 7.63 7.67 0 5.41-4.45 8.71-11.43 8.71-5.26 0-11.4-2.07-12.21-9.36h7.79c.39 1.92 2 2.92 4.53 2.92 2 0 2.8-.7 2.8-1.65 0-.66-.34-1.23-2-1.62l-4.38-1.07c-5.14-1.31-7.63-3.53-7.63-7.71 0-5.53 4.41-8.75 10.74-8.75 4.11.07 10.9 1.52 11.56 8.9z" transform="translate(-75.98 -86.69)"></path></svg>
      <div class="form">
        <h3 class="title">Wat wordt je gebruikers naam?</h3>
        <input class="usernameInput" type="text" maxlength="14" />
      </div>
    </li>
  </ul>
</body>
</html>
* {
  box-sizing: border-box;
}

html {
  font-weight: 300;
  -webkit-font-smoothing: antialiased;
}

html, input {
  font-family:
    "Montserrat",
    sans-serif;
}

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
}

ul {
  list-style: none;
  word-wrap: break-word;
}

/* Pages */

.pages {
  height: 100%;
  margin: 0;
  padding: 0;
  width: 100%;
}

.page {
  height: 100%;
  position: absolute;
  width: 100%;
}

/* Login Page */

.login.page {
  display: flex;
  align-items: center;
  flex-direction: column;
  justify-content: center;
  background-color: #FFF;
  padding: 20px;
}
.login.page .dij_logo {
  max-width: 400px;
}

.login.page .form {
  text-align: center;
  width: 100%;
}

.login.page .form .usernameInput {
  background-color: transparent;
  border: none;
  border-bottom: 5px solid #000;
  outline: none;
  padding-bottom: 15px;
  text-align: center;
  width: 400px;
}

.login.page .title {
  font-size: 150%;
}

.login.page .usernameInput {
  font-size: 200%;
  letter-spacing: 3px;
}

.login.page .title, .login.page .usernameInput {
  color: #000;
  font-weight: 100;
}

/* Chat page */

.chat.page {
  display: none;
}

/* Font */

.messages {
  font-size: 150%;
}

.inputMessage {
  font-size: 100%;
}

.log {
  color: gray;
  font-size: 70%;
  margin: 5px;
  text-align: center;
}

/* Messages */

.chatArea {
  height: 100%;
  padding-bottom: 60px;
}

.messages {
  height: 100%;
  margin: 0;
  overflow-y: scroll;
  padding: 10px 20px 10px 20px;
}

.message.typing .messageBody {
  color: gray;
}

.username {
  font-weight: 700;
  overflow: hidden;
  padding-right: 15px;
  text-align: right;
}

/* Input */

.inputMessage {
  border: 10px solid #000;
  bottom: 0;
  height: 60px;
  left: 0;
  outline: none;
  padding-left: 10px;
  position: absolute;
  right: 0;
  width: 100%;
}
  var FADE_TIME = 150; // ms
  var TYPING_TIMER_LENGTH = 400; // ms
  var COLORS = [
    '#e21400', '#91580f', '#f8a700', '#f78b00',
    '#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
    '#3b88eb', '#3824aa', '#a700ff', '#d300e7'
  ];

  // Initialize variables
  var $window = $(window);
  var $usernameInput = $('.usernameInput'); // Input for username
  var $messages = $('.messages'); // Messages area
  var $inputMessage = $('.inputMessage'); // Input message input box

  var $loginPage = $('.login.page'); // The login page
  var $chatPage = $('.chat.page'); // The chatroom page

  // Prompt for setting a username
  var username;
  var connected = false;
  var typing = false;
  var lastTypingTime;
  var $currentInput = $usernameInput.focus();

  var socket = io('https://socket-io-chat.now.sh/');

  const addParticipantsMessage = (data) => {
    var message = '';
    if (data.numUsers === 1) {
      message += "Er is 1 persoon online!";
    } else {
      message += "Er zijn " + data.numUsers + " personen online!";
    }
    log(message);
  }

  // Sets the client's username
  const setUsername = () => {
    username = 'DIJ_' + cleanInput($usernameInput.val().trim());

    // If the username is valid
    if (username) {
      $loginPage.fadeOut();
      $chatPage.show();
      $loginPage.off('click');
      $currentInput = $inputMessage.focus();

      // Tell the server your username
      socket.emit('add user', username);
    }
  }

  // Sends a chat message
  const sendMessage = () => {
    var message = $inputMessage.val();
    // Prevent markup from being injected into the message
    message = cleanInput(message);
    // if there is a non-empty message and a socket connection
    if (message && connected) {
      $inputMessage.val('');
      addChatMessage({
        username: username,
        message: message
      });
      // tell server to execute 'new message' and send along one parameter
      socket.emit('new message', message);
    }
  }

  // Log a message
    const log = (message, options) => {
    var $el = $('<li>').addClass('log').text(message);
    addMessageElement($el, options);
  }

  // Adds the visual chat message to the message list
  const addChatMessage = (data, options) => {
    // Don't fade the message in if there is an 'X was typing'
    var $typingMessages = getTypingMessages(data);
    options = options || {};
    if ($typingMessages.length !== 0) {
      options.fade = false;
      $typingMessages.remove();
    }

    var $usernameDiv = $('<span class="username"/>')
      .text(data.username)
      .css('color', getUsernameColor(data.username));
    var $messageBodyDiv = $('<span class="messageBody">')
      .text(data.message);

    var typingClass = data.typing ? 'typing' : '';
    var $messageDiv = $('<li class="message"/>')
      .data('username', data.username)
      .addClass(typingClass)
      .append($usernameDiv, $messageBodyDiv);

    addMessageElement($messageDiv, options);
  }

  // Adds the visual chat typing message
  const addChatTyping = (data) => {
    data.typing = true;
    data.message = 'is aan het typen';
    addChatMessage(data);
  }

  // Removes the visual chat typing message
  const removeChatTyping = (data) => {
    getTypingMessages(data).fadeOut(function () {
      $(this).remove();
    });
  }

  // Adds a message element to the messages and scrolls to the bottom
  // el - The element to add as a message
  // options.fade - If the element should fade-in (default = true)
  // options.prepend - If the element should prepend
  //   all other messages (default = false)
  const addMessageElement = (el, options) => {
    var $el = $(el);

    // Setup default options
    if (!options) {
      options = {};
    }
    if (typeof options.fade === 'undefined') {
      options.fade = true;
    }
    if (typeof options.prepend === 'undefined') {
      options.prepend = false;
    }

    // Apply options
    if (options.fade) {
      $el.hide().fadeIn(FADE_TIME);
    }
    if (options.prepend) {
      $messages.prepend($el);
    } else {
      $messages.append($el);
    }
    $messages[0].scrollTop = $messages[0].scrollHeight;
  }

  // Prevents input from having injected markup
  const cleanInput = (input) => {
    return $('<div/>').text(input).html();
  }

  // Updates the typing event
  const updateTyping = () => {
    if (connected) {
      if (!typing) {
        typing = true;
        socket.emit('typing');
      }
      lastTypingTime = (new Date()).getTime();

      setTimeout(() => {
        var typingTimer = (new Date()).getTime();
        var timeDiff = typingTimer - lastTypingTime;
        if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
          socket.emit('stop typing');
          typing = false;
        }
      }, TYPING_TIMER_LENGTH);
    }
  }

  // Gets the 'X is typing' messages of a user
  const getTypingMessages = (data) => {
    return $('.typing.message').filter(function (i) {
      return $(this).data('username') === data.username;
    });
  }

  // Gets the color of a username through our hash function
  const getUsernameColor = (username) => {
    // Compute hash code
    var hash = 7;
    for (var i = 0; i < username.length; i++) {
       hash = username.charCodeAt(i) + (hash << 5) - hash;
    }
    // Calculate color
    var index = Math.abs(hash % COLORS.length);
    return COLORS[index];
  }

  // Keyboard events

  $window.keydown(event => {
    // Auto-focus the current input when a key is typed
    if (!(event.ctrlKey || event.metaKey || event.altKey)) {
      $currentInput.focus();
    }
    // When the client hits ENTER on their keyboard
    if (event.which === 13) {
      if (username) {
        sendMessage();
        socket.emit('stop typing');
        typing = false;
      } else {
        setUsername();
      }
    }
  });

  $inputMessage.on('input', () => {
    updateTyping();
  });

  // Click events

  // Focus input when clicking anywhere on login page
  $loginPage.click(() => {
    $currentInput.focus();
  });

  // Focus input when clicking on the message input's border
  $inputMessage.click(() => {
    $inputMessage.focus();
  });

  // Socket events

  // Whenever the server emits 'login', log the login message
  socket.on('login', (data) => {
    connected = true;
    // Display the welcome message
    var message = "Welkom in de Socket.IO chat";
    log(message, {
      prepend: true
    });
    addParticipantsMessage(data);
  });

  // Whenever the server emits 'new message', update the chat body
  socket.on('new message', (data) => {
    addChatMessage(data);
  });

  // Whenever the server emits 'user joined', log it in the chat body
  socket.on('user joined', (data) => {
    log(data.username + ' is nu online');
    addParticipantsMessage(data);
  });

  // Whenever the server emits 'user left', log it in the chat body
  socket.on('user left', (data) => {
    log(data.username + ' is ofline');
    addParticipantsMessage(data);
    removeChatTyping(data);
  });

  // Whenever the server emits 'typing', show the typing message
  socket.on('typing', (data) => {
    addChatTyping(data);
  });

  // Whenever the server emits 'stop typing', kill the typing message
  socket.on('stop typing', (data) => {
    removeChatTyping(data);
  });

  socket.on('disconnect', () => {
    log('you have been disconnected');
  });

  socket.on('reconnect', () => {
    log('you have been reconnected');
    if (username) {
      socket.emit('add user', username);
    }
  });

  socket.on('reconnect_error', () => {
    log('attempt to reconnect has failed');
  });
Run Pen

External CSS

  1. https://fonts.googleapis.com/css?family=Montserrat&amp;display=swap

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js
  2. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js