实现一个QQ与emoji表情选择器的npm组件

2,622 阅读3分钟

  先给大家看一下实现效果图,通过一个按钮来触发表情选择面板,点击表情选择面板中的任意表情,实现把表情添加到指定输入框。本文就是讲怎样来实现这样的一个组件。

  本文带大家实现一个QQ与emoji表情选择器组件,在之前有讲过如何制作一个npm组件,详情参考底部公众的文章《手摸手教你制作自己的vue插件(2)》。可以通过文章中介绍的方法按照步骤来制作npm插件,本次主要聚焦组件内部的实现。
  目前组件已经上传到了npm仓库,大家可以通过下面命令来安装这个组件。

npm install vue-qqemoji-picker

  git仓库在笔者的git仓库中,地址为

https://github.com/Peter1989/vue-qqemoji-picker

大家可以先从git仓库中下载下来代码,根据代码和本文的解析去理解。作者实现这个组件的思路也是借鉴了github上一位作者的代码:github.com/DCzajkowski…

  背景介绍到此,下面进入正题。
  首先看下组件如何使用:
  1)在main.js中引入EmojiPicker组件。

import EmojiPick from 'vue-qqemoji-picker'
Vue.use(EmojiPick)

  2)在模板中使用组件emoji-picker,在组件中需要定义一个具名插槽为emoji-invoker,emoji-invoker主要是为了激活表情选择框的一个按钮。

<template>
  <div class="hello">
    <emoji-pick
      @emoji="insert"
      :panelWidth="450"
      :panelHeight="170">
      <div slot="emoji-invoker" class="emojibutton" slot-scope="{ events: { click: clickEvent } }" @click.stop="clickEvent">
        <button type="button">emoji</button>
      </div>
    </emoji-pick>
    <div>
      <textarea v-model="input"></textarea>
    </div>
  </div>
</template>

<script>
export default {
  data(){
    return {
      input: ''
    }
  },
  methods: {
    insert(emoji) {
      this.input += emoji
    }
  }
}
</script>

  a.作用域插槽:
  如果不了解作用域插槽的概念可以先看这篇文章:segmentfault.com/a/119000001…
  1.emoji-picker组件中,定义了emoji事件,进而调用了父组件的insert方法。父组件中insert方法就是在消息输入框中加上emoji表情符号。在组件中通过参数来规定选择面板的宽和高。
  2.具名插槽emoji-invoker中通过作用域插槽从子组件往父组件中传递了clickEvent函数。当父组件点击emoji-invoker的时候就会调用子组件的clickEvent函数,这样子组件就可以将内部的状态变量和父组件解耦,方便自己控制状态。

下面我们来看下组件的内部实现:

<template>
  <div class="wrap">
    <slot
      name="emoji-invoker"
      :events="{ click: (e) => toggle(e) }"
    ></slot>
    <div
      v-if="display.visible"
      v-click-outside="hide">
    <div class="emojipanel" :style="`width:${panelWidth}px;height:${panelHeight}px;bottom:${display.bottom}px;left:${display.left}px`">
      <div
        v-for="(emojiGroup, category) in emojis"
        :key="category">
        <div v-if="category == 'QQ'">
          <span
            v-for="(item, index) in emojiGroup"
            :key="index"
            :style="`${item.position}`"
            class="qqface-item"
            @click="insert(item.code)"/>
        </div>
        <div v-if="category == 'emoji'">
          <span
            v-for="(emoji, emojiName) in emojiGroup"
            :key="emojiName"
            :title="emojiName"
            style="margin:0 4px 0 0;font-size: 18px;"
            @click="insert(emoji)"
          >{{ emoji }}</span>
        </div>
      </div>
    </div>
    </div>
  </div>
</template>

<script>
  import emojis from './emojis'

  export default {
    name: 'emoji-pick',
    props: {
      panelWidth:{
        type: Number,
        required:false,
        default(){
          return 400
        }
      },
      panelHeight:{
        type: Number,
        required: false,
        default(){
          return 170
        }
      }
    },
    data() {
      return {
        display: {
          left: 0,
          bottom: 0,
          visible: false,
        },
        emojis: emojis
      }
    },
    methods: {
      insert(emoji) {
        this.$emit('emoji', emoji)
      },
      toggle(e) {
        this.display.visible = ! this.display.visible
        this.display.left = e.toElement.offsetLeft
        this.display.bottom = e.toElement.offsetHeight
      },
      hide() {
        this.display.visible = false
      }
    }
  }
</script>

  首先可以看到作用域插槽中给父组件传的click事件,在methods中找到toggle方法,主要是控制表情选择面板的显示与否,并且获取激活按钮的横纵坐标,来控制选择面板的位置。 其中emojis数组分为两部分,即为QQ表情部分和系统emoji表情部分,这两种都是微信消息中所支持的。QQ表情的显示方式的通过传入的item的positioin,在雪碧图中定位,达到显示特定表情的效果。而系统emoji表情是直接通过系统表情显示出来的。   当用户点击表情的时候,就会调用子组件的insert函数,向父组件发送emoji事件,父组件中insert方法就是在消息输入框中加上emoji表情符号。

  b.自定义指令:
  官网介绍:cn.vuejs.org/v2/guide/cu…
在组件中定义了click-outside自定义指令,在bind和unbind钩子中通过给document绑定/解绑click事件的handler函数,如果点击emoji选择面板之外的元素,就触发自定义指令绑定的在上文的hide函数,来隐藏emoji选择面板。

directives: {
      'click-outside': {
        bind(el, binding) {
          if (typeof binding.value !== 'function') {
            return
          }
          const bubble = binding.modifiers.bubble
          const handler = (e) => {
            if (bubble || (! el.contains(e.target) && el !== e.target)) {
              binding.value(e)
            }
          }
          el.__vueClickOutside__ = handler
          document.addEventListener('click', handler)
        },
        unbind(el) {
          document.removeEventListener('click', el.__vueClickOutside__)
          el.__vueClickOutside__ = null
        },
      },
    },

更多精彩文章请关注: