Building An Emoji Picker in React 🤖
How Nylas built an emoji picker in React JS 💌
Jackie Luo | January 13, 2017
It’s been a big year for emoji. A new career was introduced to the world:”emoji translator/specialist.” Celebrity-specific “Taymojis” (Taylor Swift emoji) are in production. Apple published teasers of new gender-neutral emojis and (finally!) added a woman in a headscarf. We engaged in heated debates about the grammatical accuracy of emoji vs. emojis.
What’s the story behind emoji? 🤔
Emoji were originally created by Japanese mobile carrier Docomo in the late 1990s, taking advantage of an unused part of the Japanese encoding charset. These original emoji were so influential that they’ve even been added to the MOMA’s permanent collection.
The original Docomo emoji set
Email, meet emoji 💘
After using apps that offer emoji like Slack and Snapchat, our team was inspired to add emoji features to our app as well. We often demo our main product (the Nylas APIs for email, calendar, and contacts) with Nylas Mail, our open-source email client for Mac, Windows, and Linux. Naturally, Nylas Mail needed emoji capability.
— Jackie Luo (@jackiehluo) December 23, 2016
This post is about overcoming the technical challenges of building our emoji picker, along with some fun facts we learned about the world of emoji. 😉🌈⭐️
Rendering with React 🔫
The first major issue we discovered while building the emoji picker was rendering performance. There are two ways to insert emoji in Nylas Mail: autocompletion triggered from the colon character (like Slack/GitHub) or manual selection via a popover that shows all possible emoji in a scrolling list.
Rendering emoji for autocompletion was fast, since it only shows a short list of matches at any given time. But when we tried to render thousands of emoji in the full list, we hit a performance bottleneck.
The initial strategy was to create each emoji character as its own React element. (All of Nylas Mail is built in React.js!) This React element would be a button, so it would be responsible for handling the click event to insert the character into the message.
But that implementation of the popover was slow! It took several seconds just to open, which was pretty much unacceptable from a user’s standpoint. What was happening in the time between clicking the button and seeing the popover? 💤
A need for speed 💨
To investigate, we ran a simple CPU profiling analysis and realized that that the problem was… React. Even though React is designed for speed, we support 1,600 different emoji—which meant React was attempting to render 1,600 React elements at the same time. Clearly, that strategy wasn’t going to work.
So how else could we get these characters to render in HTML? After some experimentation, we discovered a solution: use the HTML canvas element.
Which might seem hacky—but we found that it was the most elegant and performant solution. The React component takes the click event from the canvas element and compares the click coordinates with the expected position of each emoji character. Check out the code.
Faster and faster 🚀
Even after this change, opening the canvas-drawn popover still took several hundreds of milliseconds—too long for a great user experience. To solve this issue, we introduced one last performance optimization using selective loading.
Our new loading strategy means that instead of drawing the entire canvas—with all 1,600 emoji—or even an entire category, we instead render a blank canvas on open and then start drawing the emoji after it’s been displayed. That change meant that the picker wasn’t initially “finished” on open, but we thought it was worth it to remove the blocking UI and make the picker feel lightning-quick and responsive. ⚡️
Cross-platform emoji support 📱
As you’ve probably noticed, emoji can look different depending on your platform, device, and version. There are custom emoji sets for Mac, Windows, Linux, iOS, Android, Facebook Messenger, Twitter, and others, and they can differ pretty dramatically.
Although Unicode is a standard for character encoding and decoding, the decision for how pictographs will look is up to the platform developer. Occasionally, that’s even led to confusions in conversations, like when Apple’s 😃 (Grinning Face with Smiling Eyes) used to look like 😬 (Grimacing Face).
For our team building Nylas Mail, this inconsistency led to a worry that using emoji Unicode characters directly would have cross-platform rendering issues.
Older versions of macOS (i.e., Yosemite) don’t support hundreds of the new emoji we wanted to include. Windows was worse, and Linux often doesn’t come bundled with support for emoji at all. These characters would just display in the emoji picker as black boxes, the default Unicode decoding error character.
For us, that meant that if we used the Unicode characters directly, there was a good chance that a lot of platforms wouldn’t be able to render them properly.
Instead, we decided to ship our emoji feature with two full sets of images. We used the Apple emoji set for Mac and the Twitter emoji set for Windows and Linux, increasing our total binary download size by 3.6 MB—a small price to pay for beautiful cross-platform emoji rendering.
We send emails with the Unicode characters, since we wanted to preserve the native feel of the emoji on other devices and email clients often block images, so the PNGs are converted back to emoji on send. But for Nylas Mail users, the emoji picker renders consistently on every platform.
Want to see it in action? Download Nylas Mail and start sending your friends and work colleagues beautiful emoji-filled messages! ✨💯🔥
Psst, have an idea for a new emoji? 💡
Pro tip: Anyone can submit proposals for new emoji. The Unicode Consortium has a whole subcommittee to discuss emoji, including members from Apple and Google. They review factors like compatibility, usage, distinctiveness, completeness, and request frequency. So if you feel strongly that an emoji should exist (and it doesn’t)… it could be your time to shine.
For now, I’m just hoping to see Shocked Face With Exploding Head, Hedgehog, Face With One Eyebrow Raised, and Dumpling before the end of the year.