Add flying emoji reactions to a custom Daily video call

2022-04-07: The code snippets in this post reference an older version of the examples repo prior to the release of Daily React Hooks. Any code not using Daily React Hooks is still valid and will work; however, we strongly suggest used Daily React Hooks in any React apps using Daily. To see the pre-Daily hooks version of the examples repo, please refer to the pre-daily-hooks branch. To learn more about Daily React Hooks, read our announcement post.

If a picture is worth a thousand words, what does that mean for emoji? For decades they’ve been used to add color to all sorts of written communications, from text messages to entire translations of Moby Dick to — most relevant to this blog post — video calls.

We build developer tools at Daily that enable new ways to communicate online. Adding emoji reactions to video chats gives participants a familiar (and fun!) way to express themselves.

In this tutorial we’ll add a set of flying emoji reactions to a custom video call built on the Daily call object.

Participant on video call clicks star icon and then a Squid that flies across screen

You might’ve seen similar emoji overlays in Instagram Live broadcasts, Twitter Periscope live streams, or Twitch “emote walls” that fill an entire screen on a live stream, for example. We’ll be making a similar wall of reaction emoji for our WebRTC video calls with a little React, CSS, and some Daily methods.

To achieve this we will:

  • Create a button that sends an emoji of our choice flying on click
  • Send our emoji reaction to all other participants using the Daily sendAppMessage() method
  • Render the emoji for both the local participant who sent it and the remote participants who receive it

We will do all of these things in a Next.js demo app that we built in a previous post. Reference that tutorial for details on the foundation of the app, like participant, device, and track management. This post just focuses on the emoji 😎

The demo and code snippets are React-based, but you can still apply the essence of the steps outlined in the tutorial to work with other frameworks.

To run the demo locally:

  1. Fork and clone the daily-demos/examples repository
  2. cd examples/custom/flying-emojis
  3. Set your DAILY_API_KEY and DAILY_DOMAIN env variables (see env.example)
  4. yarn
  5. yarn workspace @custom/flying-emojis dev

With that, our emoji are ready to fly.

Witch waves at flying monkeys and says fly my pretties fly

Create a button that sends an emoji flying

The star icon, labeled "Emoji" in the call tray component (Tray.js), reveals available emoji reactions, and allows participants to pick one to send.

Icons at the bottom of a video call for call controls, Star icon on click reveals three emoji

Here’s that component’s structure, with tangential elements removed:

// Tray.js 
 
<div>
  {showEmojis && (
    <div className="emojis">
      <Button
        onClick={() => sendEmoji('fire')}
      >
        🔥
      </Button>
      <Button
        onClick={() => sendEmoji('squid')}
      >
        🦑
      </Button>
      <Button
        onClick={() => sendEmoji('laugh')}
      >
        🤣
      </Button>
    </div>
  )}
  <TrayButton
    label="Emoji"
    onClick={() => setShowEmojis(!showEmojis)}
  >
    <IconStar />
  </TrayButton>
</div>

When the star icon is clicked, it displays the available emoji. When a participant selects an emoji, the component calls sendEmoji() and passes a string representing the selection. For example, after clicking on "🦑" onClick={() => sendEmoji('squid')} is called.

Let’s look at sendEmoji().

// Tray.js 

function sendEmoji(emoji) {
  window.dispatchEvent(
   new CustomEvent('reaction_added', { detail: { emoji } })
 );
 setShowEmojis(false);
}

In sendEmoji() we created a CustomEvent named reaction_added. A CustomEvent allows an application to create its own events that behave like standard browser events. For the CustomEvent.detail, we pass the string representing the selected emoji.

We’ll listen for the reaction_added event in FlyingEmojisOverlay.js, via window.addEventListener('reaction_added', handleSendFlyingEmoji);.

Use sendAppMessage() to broadcast the emoji to other call participants

handleSendFlyingEmoji() gets the string representing the emoji from CustomEvent.detail, and broadcasts it to all other call participants using the Daily sendAppMessage() method:

// FlyingEmojiOverlay.js
 
function handleSendFlyingEmoji(e) {
   const { emoji } = e.detail;
 
   if (emoji) {
     callObject.sendAppMessage({ message: `${emoji}` }, '*');
     handleDisplayFlyingEmoji(emoji);
   }
}

sendAppMessage() emits a corresponding app-message event that all remote participants receive. The <FlyingEmojiOverlay /> component listens for the event and calls handleReceiveFlyingEmoji() when a message is received: callObject.on('app-message', handleReceiveFlyingEmoji);.

// FlyingEmojisOverlay.js 
 
const handleReceiveFlyingEmoji = useCallback(
   (e) => {
     if (!overlayRef.current) {
       return;
     }
     handleDisplayFlyingEmoji(e.data.message);
   },
   [handleDisplayFlyingEmoji]
);

handleReceiveFlyingEmoji() passes the message data from e.data.message along to handleDisplayFlyingEmoji().

Render the emoji for both the local sender and the remote recipient

handleDisplayFlyingEmoji() is called both on sending, in handleSendFlyingEmoji() and upon receiving in handleReceiveFlyingEmoji(). That’s because app-message only fires for remote participants, but we want the local participant to see their own emoji reaction as well.

The handleDisplayFlyingEmoji() function takes a string as a parameter. handleSendFlyingEmoji() passes the display handler a string from the CustomEvent.detail from the window event, while handleReceiveFlyingEmoji() passes a string from the app-message event object, e.data.message.

Now that we know how and when handleDisplayFlyingEmoji() is executed, let’s have a look at its definition:

// FlyingEmojisOverlay.js 
 
const handleDisplayFlyingEmoji = useCallback(
  (emoji) => {
    if (!overlayRef.current) {
      return;
    }
 
    const node = document.createElement('div');
    node.appendChild(document.createTextNode(EMOJI_MAP[emoji]));
    node.className =
      Math.random() * 1 > 0.5 ? 'emoji wiggle-1' : 'emoji wiggle-2';
    node.style.transform = `rotate(${-30 + Math.random() * 60}deg)`;
    node.style.left = `${Math.random() * 100}%`;
    node.src = '';
    overlayRef.current.appendChild(node);
 
    node.addEventListener('animationend', (e) =>
      handleRemoveFlyingEmoji(e.target)
    );
  },
  [handleRemoveFlyingEmoji]
);

Let’s break it all down.

First, it creates a new <div>, and appends the selected emoji in a text node to that div.

// FlyingEmojiOverlay.js 
 
const node = document.createElement('div');
node.appendChild(document.createTextNode(EMOJI_MAP[emoji]));

It gets the emoji by referencing a CONSTANT EMOJI_MAP object whose keys map to emoji:

// FlyingEmojisOverlay.js 
 
const EMOJI_MAP = {
 fire: '🔥',
 squid: '🦑',
 laugh: '🤣',
};

Once the emoji is added, the function applies styles. Math.random() sets the className to either 'emoji wiggle-1' or 'emoji wiggle-2'.

// FlyingEmojisOverlay.js
 
@keyframes wiggle-1 {
  from {
    margin-left: -50px;
  }
  to {
    margin-left: 50px;
  }
}
 
@keyframes wiggle-2 {
  from {
    margin-left: 50px;
  }
  to {
    margin-left: -50px;
  }
}

These classes determine where the emoji starts wiggling on the screen. Math.random() also determines the degree to which the emoji rotates, and its left position.

 // FlyingEmojiOverlay.js 
 
 node.className =
       Math.random() * 1 > 0.5 ? 'emoji wiggle-1' : 'emoji wiggle-2';
 node.style.transform = `rotate(${-30 + Math.random() * 60}deg)`;
 node.style.left = `${Math.random() * 100}%`;

With styling set, the emoji is ready to be added to overlayRef:

// FlyingEmojisOverlay.js

overlayRef.current.appendChild(node);

Finally, handleDisplayFlyingEmoji() listens for the emoji animation to end, node.addEventListener('animationend', (e) => handleRemoveFlyingEmoji(e.target)); and then removes the appended child:

const handleRemoveFlyingEmoji = useCallback((node) => {
   if (!overlayRef.current) return;
   overlayRef.current.removeChild(node);
 }, []);

What’s next ❓

We hope this tutorial has helped you add personality to your video calls. To build on this demo, you could: experiment with emoji that multiply and burst more quickly in a "particle effect" (instead of a gentle float, maybe they bounce around the video window); generate random emoji; add reactions to a webinar app, or explore libraries like confetti.

To keep reading for more inspiration, Butter, a web events facilitation platform, has a writeup on on their engineering blog on how they implemented floating emojis for their video chats with Framer Motion and Lottie Web.

The world is your oyster, 🌍🦪.

Never miss a story

Get the latest direct to your inbox.