Taha

How to Add Emoji Picker in Text Fields

Emojis have become a basic part of any writing these days. And I have no doubt that, sooner or later, you'll need to have them supported in one of your applications.

I was looking, the other day, for a package to add a slack-like emoji picker in one of my textarea fields. But, unfortunately, I didn't find a nice one that integrates easily with VueJS. However, I did find a package that provides a component that displays only the emoji picker and give me the needed information about the emoji to add.

So I decided to create a simple component that uses that picker to add the clicked emojis in the textarea field. And in this tutorial I'm going to show you how I did it.

Preparing the component

In this step, we're going to create our component and add the textarea into it along with the necessary bindings.

We'll name the component TextareaEmojiPicker. So create TextareaEmojiPicker.vue in your components directory and put this into it:

<template>
  <div class="textarea-emoji-picker">
    <textarea
      ref="textarea"
      class="textarea"
      :value="value"
      @input="$emit('input', $event.target.value)"
    ></textarea>
  </div>
</template>

<script>
  export default {
    props: {
      value: {
        type: String,
        default: ''
      }
    }
  }
</script>

<style scoped>
  * {
    box-sizing: border-box;
  }

  .textarea-emoji-picker {
    position: relative;
    width: 400px;
    margin: 0 auto;
  }

  .textarea {
    width: 100%;
    min-height: 300px;
    outline: none;
    box-shadow: none;
    padding: 10px 28px 10px 10px;
    font-size: 15px;
    border: 1px solid #bababa;
    color: #333;
    border-radius: 2px;
    box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.1) inset;
    resize: vertical;
  }

  .emoji-mart {
    position: absolute;
    top: 33px;
    right: 10px;
  }

  .emoji-trigger {
    position: absolute;
    top: 10px;
    right: 10px;
    cursor: pointer;
    height: 20px;
  }

  .emoji-trigger path {
    transition: 0.1s all;
  }

  .emoji-trigger:hover path {
    fill: #000000;
  }

  .emoji-trigger.triggered path {
    fill: darken(#fec84a, 15%);
  }
</style>

For now, we only have the textarea element in this component. To allow for v-model on this component, we made it accept value prop and emit an input event.

Try to use that component in your page, like this:

<template>
  <div class="home-page">
    <textarea-emoji-picker v-model="text" />
  </div>
</template>

<script>
  import TextareaEmojiPicker from './TextareaEmojiPicker'
  export default {
    name: 'HomePage',
    components: { TextareaEmojiPicker },
    data() {
      return {
        text: ''
      }
    }
  }
</script>

<style scoped>
  .home-page {
    padding-top: 50px;
  }
</style>

Adding the emoji picker

There's an excellent emoji picker component called Emoji Mart. The main job of this component is to display a nice picker (similar to slack) and return an emoji object when an emoji is clicked. This emoji object contains, among other things, the unicode value that represents it — we'll use this value to display them in our textarea.

This component was originally created in React, but since we're using Vue here, we'll install the vue version of it:

npm install --save emoji-mart-vue

Now include that component in TextareaEmojiPicker component:

<script>
import { Picker } from 'emoji-mart-vue'

export default {
  components: { Picker },
  // ...
}

Then, use it at the top of our component:

<div class="textarea-emoji-picker">
  <picker title="Pick your emoji..." emoji="point_up" />
</div>

If you check the browser now, you'll see the picker open — which does nothing at the moment.

Adding a trigger button

Obviously, we shouldn't keep the picker open by default. We should instead have a button to toggle it.

Before we add that button, we need to have a flag to determine if the picker should open or not. Define it in data(), like this:

data () {
  return {
    showEmojiPicker: false
  }
}

Now let's add the trigger button below <picker/>:

<span
  class="emoji-trigger"
  :class="{ 'triggered': showEmojiPicker }"
  @mousedown.prevent="toggleEmojiPicker"
>
  <svg style="width:20px;height:20px" viewBox="0 0 24 24">
    <path
      fill="#888888"
      d="M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M10,9.5C10,10.3 9.3,11 8.5,11C7.7,11 7,10.3 7,9.5C7,8.7 7.7,8 8.5,8C9.3,8 10,8.7 10,9.5M17,9.5C17,10.3 16.3,11 15.5,11C14.7,11 14,10.3 14,9.5C14,8.7 14.7,8 15.5,8C16.3,8 17,8.7 17,9.5M12,17.23C10.25,17.23 8.71,16.5 7.81,15.42L9.23,14C9.68,14.72 10.75,15.23 12,15.23C13.25,15.23 14.32,14.72 14.77,14L16.19,15.42C15.29,16.5 13.75,17.23 12,17.23Z"
    />
  </svg>
</span>

Note how we attached a mousedown.prevent event listener to that button instead of click. We did this to prevent the textarea from losing its focus when the picker opens.

Now let's define that method, like this:

methods: {
  toggleEmojiPicker () {
    this.showEmojiPicker = !this.showEmojiPicker
  }
}

Lastly, we should use that flag on our picker via v-show:

<picker v-show="showEmojiPicker" title="Pick your emoji..." emoji="point_up" />

Just in case you're wondering, we used v-show instead of v-if to avoid instantiating the picker each time it's toggled. So we just instantiate it once and then keep toggling it using css display:none, which is clearly faster.

Adding emojis

Now we get to the last piece of our component. As I mentioned earlier, we need to read the unicode value of the clicked emoji. And to get that value, we have to listen to @select event on <picker> component:

<picker
  v-show="showEmojiPicker"
  title="Pick your emoji..."
  emoji="point_up"
  @select="addEmoji"
/>

Now let me show you how to define that method and then explain it:

addEmoji (emoji) {
  const textarea = this.$refs.textarea
  const cursorPosition = textarea.selectionEnd
  const start = this.value.substring(0, textarea.selectionStart)
  const end = this.value.substring(textarea.selectionStart)
  const text = start + emoji.native + end
  this.$emit('input', text)
  textarea.focus()
  this.$nextTick(() => {
    textarea.selectionEnd = cursorPosition + emoji.native.length
  })
}

We add the emoji in three steps. First, we split the text in the textarea into two pieces: from the beginning to the current cursor position and from the current cursor position to the end. We named them start and end, respectively.

Second, we concatenate the first part, the emoji, and the second part together and store them in text constant. And then we use that value to update the current text in the textarea.

Third, we re-focus on the textarea, and then move the cursor after the added emoji.

Note how we needed to reposition the cursor in the $nextTick callback to wait until the focusing is done in the previous tick.

That's it! Now you should have an emoji picker in your textarea.

Note: the code of this demo can be found on GitHub.

Taha Shashtari

I'm Taha Shashtari, a full-stack web developer. Building for the web is my passion. Teaching people how to do that is what I like the most. I like to explore new techniques and tools to help me and others write better code.

Subscribe to get latest updates about my work
©2024 Taha Shashtari. All rights reserved.