CSS video tutorial: Create a custom twitch chat with StreamElements

In this tutorial I suggest you discover how to create a personalized chat for Twitch in CSS.

OBS integrates a web browser

To stream on twitch most of the time we use the Open Broadcaster Software which allows you to manage scenes with different layers. It is possible to use different sources on these layers and it is even possible to use a web page as a source. To create a personalized chat, all we need to do is create a simple web page that retrieves the latest messages sent on the chat.

To retrieve these messages it is possible to use the IRC version of the twitch chat with tmi.js for example or to use a third party service like StreamElements.

Why StreamElements?

StreamElements allows you to manage an overlay visually and generates a web page that can then be used as an OBS layer. It already has a chat widget but it is not customizable enough but it is possible to create a custom HTML widget in which you can add HTML, CSS and JavaScript.

Within the framework of these widgets, it will be necessary to listen to the event onEventReceived which will be called during various events related to twitch (and other supported services). In our case we are only interested in the messages.

window.addEventListener('onEventReceived', function (obj) {
    // On gère la suppression de messages
    if (obj.detail.listener === "delete-message") {
      return removeFromDom(`#msg-${obj.detail.event.msgId}`);
    } else if (obj.detail.listener === "delete-messages") {
      const sender = obj.detail.event.userId;
      return removeFromDom(`.message[data-sender=${sender}]`);
    }

    // On filtre certains message
    if (obj.detail.listener !== "message") return;
    let data = obj.detail.event.data;
    if (data.text.startsWith("!") && hideCommands) {
      return;
    }

    // On ajoute le message au DOM
    addMessage(data, obj.detail.event.renderedText)
})

The rest of the code is to manipulate the DOM to inject our messages as we go.

const limit = 20
function addMessage (data, html) {
  const userColor = data.displayColor || `#${md5(data.displayName).substr(26)}`
  const badges = data.badges.reduce((acc, badge) =>  acc + `<img alt="" src="https://grafikart.fr/tutoriels/${badge.url}" class="badge">`,'')
  // const html = attachEmotes(data)
  const message= `<div data-sender="${data.userId}" id="msg-${data.msgId}" class="message">
    <div class="meta">
      <span class="badges">${badges}</span>
      <span class="name" style="--color: ${userColor}">${data.displayName}</span>
    </div>
    <div class="content">
      ${html}
    </div>
  </div>`
  chatBox.insertAdjacentHTML('beforeend', message)

  // On limite le nbre de message pour ne pas surcharger le rendu
  const messages = document.querySelectorAll('.message')
  const messageCount = messages.length
  if (messageCount > limit) {
    Array.from(messages).slice(0, messageCount - limit).forEach((el) => {
      el.remove()
    })
  }
}

Finally, the CSS part will depend on the style you want to achieve. To make sure to always have the last message at the bottom of the screen we will use an absolute position and a grid to space each message.

.messages {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.5rem;
}
.message {
  /* A vous de jouer ;) */
}