头图

说在前面

📺在数字时代,表情符号已成为我们沟通的延伸。大部分同学在平时沟通聊天中都少不了使用emoji表情,所以今天我们一起来编写一个emoji选择器组件,可以一键引入为你的输入框添加emoji表情选择功能。

效果展示

体验地址

体验地址:http://jyeontu.xyz/jvuewheel/#/JEmojiPickerView

功能实现

获取emoji表情文件

这里推荐一个网站,可以快速查找需要的emoji,还支持自制emoji表情,平时我也会到这里查找自己需要的emoji表情。

https://www.emojiall.com/zh-hans/all-emojis

emoji表情数据我们便可以从这个网站中获取,简单写个脚本获取页面上的emoji表情包

const d = document.querySelectorAll(
  "body > div.body_warp.row.no-gutters >section > div > div > div > div > div > div > div > section > div > div > div >div.container-fluid.emoji_list_box > section.emoji_card_list.bg_none.box_wrap"
);
const jsonList = [];
for (let i = 0; i < d.length; i++) {
  const title = d[i]
    .querySelector("h2")
    .innerText.split("\n")[0]
    .replace("表情符号复制", "");
  const emojiList = d[i].querySelectorAll("ul > li");
  const list = [];
  for (let j = 0; j < emojiList.length; j++) {
    const font = emojiList[j].querySelector(".emoji_font").innerText;
    const name = emojiList[j].querySelector(".emoji_name").innerText;
    list.push({
      name,
      font,
    });
  }
  jsonList.push({
    title,
    list,
  });
}

组件配置 (props)

  • showFrequentlyUsedList:是否显示常用表情列表,默认为true。
  • emojiList:自定义的表情列表,默认为空数组。
  • mode:自定义列表的添加模式("unshift"、"append" 或 "replace"),默认为"append"。
  • hideList:需要隐藏的表情列表,默认为空数组。
  • inputId:关联的输入框ID,用于将选中的表情填入该输入框。

添加常用表情列表

将常用表情列表添加到表情选择器组件中。

  1. 检查显示条件

    • 方法首先检查 showFrequentlyUsedList 属性是否为 true。如果不是,方法将直接返回一个空数组,即不添加任何常用表情列表。
  2. 初始化列表

    • 它初始化一个空数组 list,用来存储常用的表情对象。
  3. 从本地存储中检索数据

    • 尝试使用 localStorage.getItem("JEmojiPickerFrequentlyUsedList") 从本地存储中检索常用表情的列表。如果未找到相应的存储项,则默认返回一个空数组 []
  4. 解析本地存储数据

    • 从本地存储中检索到的数据应该是一个JSON字符串,因此使用 JSON.parse() 将其转换回表情对象数组。
  5. 创建常用分类

    • 创建一个对象来表示常用表情的分类,其中包含 titleemojiTitletitle 是分类的显示名称,emojiTitle 是该分类的简短标题或图标。
    • 该对象的 list 属性被设置为解析后的表情列表。
  6. 添加到主列表

    • 将创建的分类对象添加到 list 数组中。
  7. 返回结果

    • 最后,方法返回 list 数组,其中现在包含了常用表情的分类。

以下是该方法的代码示例,包含一些额外的注释以提高清晰度:

addFrequentlyUsedList() {
    // 如果不显示常用表情列表,则提前返回
    if (!this.showFrequentlyUsedList) return [];

    // 初始化一个数组来保存常用表情
    const list = [];

    // 尝试从本地存储中检索常用表情列表
    const frequentlyUsedList = JSON.parse(
        localStorage.getItem("JEmojiPickerFrequentlyUsedList") || "[]"
    );

    // 创建一个对象表示常用表情的分类
    const frequentlyUsedCategory = {
        title: "⏳️最近常用",  // 分类的显示名称
        emojiTitle: "⏳️",  // 分类的简短标题或图标
        list: frequentlyUsedList,  // 表情列表
    };

    // 将常用分类添加到主列表
    list.push(frequentlyUsedCategory);

    // 返回包含常用表情分类的列表
    return list;
}

这个方法确保了表情选择器组件能够根据用户之前的使用情况动态显示常用表情列表,通过提供快速访问常用表情来增强用户体验。

初始化emoji列表

初始化emoji表情数据列表。

  1. 开始初始化过程

    • 方法开始时,首先通过调用 addFrequentlyUsedList() 方法并展开其返回值来初始化一个新列表 list,这个列表将包含常用表情(如果 showFrequentlyUsedListtrue)。
  2. 遍历表情数据

    • 使用 emojiJson.forEach 遍历引入的表情数据配置。
  3. 过滤和添加表情

    • 对于 emojiJson 中的每个表情项,首先检查它是否在 hideList 中被列出为需要隐藏的项。
    • 如果没有被隐藏,使用 list.push() 方法将该表情添加到 list 中。
    • 在添加之前,创建一个新的对象,其中 emojiTitle 属性是原 title 的前两个字符的简化版本(去除中文字符)。
  4. 处理自定义表情列表

    • 如果 emojiList 属性有值(即有传入的自定义表情列表),根据 mode 属性的值来决定如何处理这个列表:

      • "unshift":将自定义表情列表中的元素添加到 list 的开头。
      • "replace":用 emojiList 替换整个 list
      • 默认("append"):将自定义表情列表中的元素添加到 list 的末尾。
  5. 更新组件数据

    • 将最终的 list 赋值给组件的 emojiJson 数据属性,这样组件就有了完整的、经过处理的表情数据。
  6. 设置激活的标签页

    • list 的第一个元素设置为激活的标签页 activatedTab
init() {
    const list = [...this.addFrequentlyUsedList()];
    emojiJson.forEach((item) => {
        if (!this.hideList.includes(item.title)) {
            list.push({
                emojiTitle: item.title.slice(0, 2).replace(/[\u4e00-\u9fff]/g, ""),
                ...item,
            });
        }
    });
    if (this.emojiList.length) {
        switch (this.mode) {
            case "unshift":
                list.unshift(...this.emojiList);
                break;
            case "replace":
                list = [...this.emojiList];
                break;
            default:
                list.push(...this.emojiList);
                break;
        }
    }
    this.emojiJson = list;
    this.activatedTab = list[0];
}

init 方法确保了组件在创建时能够根据配置和用户自定义的选项来加载和展示正确的表情数据。

处理标签点击事件

处理标签点击事件,更新激活的标签页,并重置滚动位置。

tabClick(tab) {
    this.activatedTab = tab;
    this.$refs.emojiPickerContent.scrollTop = 0;
},

处理表情点击事件

处理用户点击表情时的逻辑。

  1. 获取常用表情列表

    • 使用 find 方法在 emojiJson 中查找标题为 "⏳️最近常用" 的项,这通常表示常用表情的分类。如果没有找到,就使用空对象 {}
  2. 初始化表情列表

    • 从找到的常用表情分类中获取 list 属性,如果没有 list 属性或没有找到常用分类,则默认为一个空数组 []
  3. 添加新点击的表情到列表

    • 创建一个新数组 newList,首先将被点击的表情 emoji 添加到数组的开头。
  4. 更新常用表情列表

    • 遍历原始的 list 数组,对于数组中的每个表情项,如果它的名字 (name) 与被点击的表情的名字不同,则将其添加到 newList 中。这样保证了被点击的表情会显示在常用列表的顶部。
  5. 存储更新后的列表

    • 将更新后的 newList 赋值回 frequentlyUsedList.list,然后使用 localStorage.setItem 方法将新列表存储到本地存储中,使用 JSON 字符串化后保存。
  6. 将表情填入选定的输入框

    • 调用 fillBackInput 方法,传入被点击的表情,将表情插入到与 inputId 属性关联的 HTML 输入框中。
  7. 触发选择事件

    • 使用 $emit 方法发出一个名为 "select" 的事件,传递被点击的表情作为事件的参数。这允许父组件监听这个事件并根据需要进行处理。
emojiClick(emoji) {
    // 获取常用表情分类,如果没有则默认为一个空对象
    const frequentlyUsedList =
        this.emojiJson.find((item) => item.title === "⏳️最近常用") || {};

    // 获取当前的常用表情列表,如果没有则默认为空数组
    const list = frequentlyUsedList.list || [];
    const newList = [emoji]; // 新列表以被点击的表情开始

    // 遍历当前列表,排除重复的表情,并添加到新列表
    for (const item of list) {
        if (item.name !== emoji.name) {
            newList.push(item);
        }
    }

    // 更新常用表情列表并存储到本地存储
    frequentlyUsedList.list = newList;
    localStorage.setItem(
        "JEmojiPickerFrequentlyUsedList",
        JSON.stringify(newList)
    );

    // 将被点击的表情填入选定的输入框
    this.fillBackInput(emoji);

    // 触发选择事件,通知父组件表情已被选中
    this.$emit("select", emoji);
}

emojiClick 方法确保了当用户选择一个表情时,它会成为常用表情,并更新本地存储和输入框中的内容,同时通知父组件用户已选择特定的表情。

将选中的表情填入关联的输入框

将选中的表情插入到关联的输入框中。

  1. 获取输入框元素

    • 使用 document.getElementById(this.inputId) 根据组件的 inputId 属性获取对应的输入框元素。如果输入框不存在(inputnullundefined),则直接返回。
  2. 记录光标位置

    • 在进行任何编辑之前,记录输入框当前的 selectionStartselectionEnd,这两个属性分别表示光标开始和结束的位置。
  3. 构造新的输入值

    • 构造一个新的字符串 resultText,将输入框中光标前的内容、选中的表情的 font 属性值(即表情符号本身),以及光标后的内容连接起来。
  4. 更新输入框的值

    • 将构造好的新字符串赋值给输入框的 value 属性,这样输入框中的内容就更新为插入了表情符号的状态。
  5. 设置输入焦点

    • 调用 input.focus() 将输入焦点设置回输入框,以便用户继续输入。
  6. 更新光标位置

    • 更新 selectionStartselectionEnd 属性,将光标移动到新插入的表情符号的末尾,以便用户可以继续在表情后输入。
fillBackInput(emoji) {
    // 根据 inputId 获取输入框元素
    const input = document.getElementById(this.inputId);
    if (!input) return;  // 如果输入框不存在,则退出函数

    // 记录光标开始和结束位置
    let startPos = input.selectionStart;
    let endPos = input.selectionEnd;

    // 构造新的输入值,将选中的表情插入到光标位置
    let resultText =
        input.value.substring(0, startPos) +  // 光标前的内容
        emoji.font +  // 被点击的表情符号
        input.value.substring(endPos);  // 光标后的内容

    // 更新输入框的值
    input.value = resultText;

    // 将输入焦点设置回输入框
    input.focus();

    // 更新光标位置到表情符号的末尾
    input.selectionStart = startPos + emoji.font.length;
    input.selectionEnd = startPos + emoji.font.length;
}

fillBackInput 方法确保了用户在表情选择器中选中一个表情后,该表情能够被插入到指定输入框的当前光标位置,并保持用户能够继续在表情后进行输入。这是一个提升用户体验的重要环节。

处理鼠标、触屏交互事件

这段代码是表情选择器组件中用于处理滚动逻辑的事件处理函数集合。它包括鼠标和触摸事件的处理,以实现组件内容的滚动功能。以下是每个函数的中文解释和逐行分析:

handleMouseDown

  • 当鼠标按下时触发此函数。
  • 初始化 scrollDistance 为 0,用于记录滚动距离。
  • 记录鼠标按下时的 Y 坐标(clientY)和当前滚动容器的滚动位置(scrollTop)。
  • isScrolling 设置为 true,表示开始滚动。
  • 监听 mousemovemouseup 事件,用于后续的滚动和滚动结束处理。

    handleMouseDown(event) {
      this.scrollDistance = 0;
      // 记录鼠标按下时的位置和滚动位置
      this.startY = event.clientY;
      this.currentScrollTop = this.$refs.emojiPickerContent.scrollTop;
      this.isScrolling = true;
    
      // 监听mousemove和mouseup事件
      window.addEventListener("mousemove", this.handleMouseMove);
      window.addEventListener("mouseup", this.handleMouseUp);
    }

    handleMouseMove

  • 当鼠标移动且已经按下时触发此函数。
  • 如果没有在滚动状态(isScrollingfalse),则返回。
  • 计算鼠标移动的距离(deltaY),并根据此距离更新滚动容器的滚动位置(newScrollTop)。
  • 更新 scrollDistance 为新滚动位置的绝对值。
  • 设置滚动容器的 scrollTopnewScrollTop,实现滚动效果。

    handleMouseMove(event) {
      if (!this.isScrolling) return;
    
      // 计算鼠标移动的距离,并更新滚动位置
      const deltaY = event.clientY - this.startY;
      const newScrollTop = this.currentScrollTop - deltaY;
      this.scrollDistance = Math.abs(newScrollTop);
      this.$refs.emojiPickerContent.scrollTop = newScrollTop;
    }

    handleMouseUp

  • 当鼠标释放时触发此函数。
  • 如果没有在滚动状态(isScrollingfalse),则返回。
  • isScrolling 设置为 false,表示滚动结束。
  • 阻止默认事件行为(如页面滚动)。
  • 移除之前添加的 mousemovemouseup 事件监听器。

    handleMouseUp(event) {
      if (!this.isScrolling) return;
      // 清理工作
      this.isScrolling = false;
      event.preventDefault();
      window.removeEventListener("mousemove", this.handleMouseMove);
      window.removeEventListener("mouseup", this.handleMouseUp);
    }

handleTouchStart

  • 当触摸开始时触发此函数。
  • 初始化 scrollDistance 为 0。
  • 记录触摸点的 Y 坐标(clientY)和当前滚动容器的滚动位置。
  • scrolling 设置为 true,表示开始触摸滚动。

    handleTouchStart(event) {
      this.scrollDistance = 0;
      this.startTouchY = event.touches[0].clientY;
      this.scrollTopStart = this.$refs.emojiPickerContent.scrollTop;
      this.scrolling = true;
    }

handleTouchMove

  • 当触摸点移动时触发此函数。
  • 如果没有在触摸滚动状态(scrollingfalse),则返回。
  • 获取当前触摸点的 Y 坐标,并计算与触摸开始时的 Y 坐标的差值(deltaY)。
  • 根据差值更新滚动容器的滚动位置(newScrollTop)。
  • 更新 scrollDistance 为新滚动位置的绝对值。
  • 设置滚动容器的 scrollTopnewScrollTop,实现滚动效果。
  • 阻止默认事件行为,如页面滚动。

    handleTouchMove(event) {
      if (!this.scrolling) return;
      const touchY = event.touches[0].clientY;
      const deltaY = touchY - this.startTouchY;
      const newScrollTop = this.scrollTopStart - deltaY;
      this.scrollDistance = Math.abs(newScrollTop);
      this.$refs.emojiPickerContent.scrollTop = newScrollTop;
      // 阻止默认滚动行为
      event.preventDefault();
    }

handleTouchEnd

  • 当触摸结束时触发此函数。
  • scrolling 设置为 false,表示滚动结束。
  • 阻止默认事件行为。

    handleTouchEnd(event) {
      this.scrolling = false;
      event.preventDefault();
    }

这些函数共同工作,确保当用户通过鼠标或触摸与表情选择器组件交互时,组件内容可以相应地滚动。同时,它们还处理了滚动的开始和结束,以及滚动过程中的动态更新。通过这种方式,组件提供了流畅的滚动体验,使用户可以轻松地在不同的表情类别之间切换。

组件使用

<textarea
    id="JEmojiPickerViewTextArea"
    v-model="inputValue"
    style="width: 100%; height: 60px"
></textarea>
<JEmojiPicker
    @select="selectEmoji"
    inputId="JEmojiPickerViewTextArea"
></JEmojiPicker>

组件库

组件文档

目前该组件也已经收录到我的组件库,组件文档地址如下:
http://jyeontu.xyz/jvuewheel/#/JMouseMenu

组件内容

组件库中还有许多好玩有趣的组件,如:

  • 悬浮按钮
  • 评论组件
  • 词云
  • 瀑布流照片容器
  • 视频动态封面
  • 3D轮播图
  • web桌宠
  • 贡献度面板
  • 拖拽上传
  • 自动补全输入框
  • 图片滑块验证

等等……

组件库源码

组件库已开源到gitee,有兴趣的也可以到这里看看:https://gitee.com/zheng_yongtao/jyeontu-component-warehouse

觉得有帮助的可以点个star~🌟

有什么问题或错误可以指出,欢迎pr~🥰

有什么想要实现的组件或想法可以联系我~🛎

公众号

关注公众号『前端也能这么有趣』,获取更多有趣内容。

发送『组件库』获取源码

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

JYeontu
15 声望0 粉丝