概述

以我们写的这个emoji插件为例,网上已经有一些相关的插件了,但你总感觉有些部分的需求不能被满足(如:可以自行添加新的表情包而不用去改源代码等等)

详细

代码下载:http://www.demodashi.com/demo/10345.html

很久没有写文章了,说实话本人现在受困于五月病已经快变成一条死咸鱼了(T_T),本次就当写一个简单的js插件教程了。本项目的代码相对比较简单,至于里面有些变量命名的问题就请你们不要吐槽了Σ(゚д゚lll)(好的,我承认我英语就小学水平好吧。除了hello和goodbye其他的都不会了____orz)。 废话就讲到这里,下面开始正文。

一、事前准备

事实上在写一个插件前我们都需要事先想好你要实现哪些功能,怎么去实现,这些大方向的东西是需要事先考虑的,至于具体细节和优化选项我们可以在写代码的过程中再进行修改。

就以我们写的这个emoji插件为例,网上已经有一些相关的插件了,但你总感觉有些部分的需求不能被满足(如:可以自行添加新的表情包而不用去改源代码等等),这时我们就可以列出你想实现的功能项了:

  1. 需要满足基本的表情插件的需求,包括图片和对应code的相互转换

  2. 希望可以通过参数来调整每行以及每列表情图片的显示个数,并且可以针对不同表情包单独调整

  3. 希望用户可以在不了解源代码的情况下也能自行主动添加新的表情包

  4. 模板界面简单,可以进行自适应,并且兼容移动端

  5. 尽可能只提供简单的api接口和方法,避免内部涉及其他不是很相关的功能(如绑定某个特定的元素或者在内部进行数据传输等等),保持插件的灵活性等等

以上就是我们暂时能想到的功能和需求,下面就开始写一个完整的插件了(当然原生js插件某种程度来讲使用起来相对比较自由,因为不需要依赖某些特定库,而且也不需要按照某些库类的格式标准进行插件的编写,但少了一些封装好的方法也会使得插件写起来更费力,至于怎么取舍就需要看个人需求来定了)

二、进行结构划分

当我们正式开始代码编写的时候,当然想自己写出来的代码不敢说很强势,但至少结构清晰,易于读懂,而且代码的性能也需要保证。这时我们就需要回到前面的需求了,由上面列出的5点可以看出,大部分的功能需求都是在我们程序内部去实现的,唯一需要考虑的是上面的第3点。

这时我们可能已经想到办法了,比如说将新的表情包填好相关的参数后由接口传入程序内部去作处理。当然这是一个合理的选择,但考虑到代码的复杂度和使用的简易度,我们最好还是建立一个对应config文件。因为首先这样我们可以提供一些默认的表情包,并且配置好相关的参数并注释,后面的使用者只需要按照相关的格式复制然后修改就行了。而且将一些非逻辑性的数据单独隔离开来有利于维持清晰的代码结构,增加代码的易读性。所以到这里已经可以基本上确定我们需要的文件了:

一个模板css文件; 2. 一个数据配置文件config.js; 3. 一个逻辑实现文件js;

三、填写配置文件

这里先填写配置文件是为了有一个更明确的需求,以及防止在coding过程中忘记了某些需求(像我一样,老了,脑袋不好使゚゚(゚´Д`゚)゚),当然并不是所有插件都用配置文件比较好,新手请务必不要有这样的误区,下面是我写的配置代码:

 var path = "http://localhost/wantEmoji/",  //项目所在的根地址emojis = {"paopao" : {"name" : "泡泡", //名字"col" : 10, //每一行最大的表情个数(建议填选的时候值不要太大或太小)"path" : path+"emojiSources/paopao/", //相对于项目根地址的路径"enable" : true, //是否启用本表情包"sources" : ["1.jpg"] //中间的值也支持{title:"笑",url:"1.jpg"}的形式,且可单独设置}}

这部分代码考虑了几个点:

  • 一是考虑到可能会在不同路径的文件中调用同一个配置文件,所以为了保证路径不出错,需要确定每个包的绝对路径值。

  • 二是考虑到某些表情包现在可能并不想用,但代码删来删去可能会很麻烦,所以提供了一个是否启用的接口。

  • 三是考虑到不同表情包的图片尺寸可能不同,为了让每张图片尽可能清晰我们允许调整每行显示的图片个数(在程序中每个单项的size都是自动计算的)

  • 四是考虑到每张表情图片可能有的需要设置title来提示用户这个表情是什么意思,所以允许sources项数组中的值可以为string也可以为object

最后也是主要考虑的问题,我们希望每个表情对应的code值能够自动生成而不是人为的对每个图片去进行单独设置,所以需要保证每个code的值都是唯一的,而且是容易被解析的。

这里emojis变量不是数组而是对象就是基于这个原因。 (我们最终生成的code值为[wem:emojis的key值_图片名_图片类型:wem]这种形式,如[wem:paopao_1_jpg:wem],表示的是paopao表情包里面的1.jpg)

四、插件开写

前面的准备工作都做好后,现在我们终于可以开始写真正的代码了。虽然前面的内容不怎么多,但对于一个插件乃至一个项目来说都是必不可少的一个步骤,特别是初学者,开始动手写自己的插件时多想想该怎么做总是没错的。

首先我们需要创建一个对象(当然你通过闭包来写也是可以的),明确好哪些数据和函数是可以共用的,哪些是不能共用的。就我个人的经验来讲,一般对于用来保存数据用的变量,最好都放在函数体内,而方法则都放在原型上。

var wantEmoji = function(options){options = options || {};var selector = options.wrapper || "body";  this.wrapper = document.querySelector(selector);    //包裹元素this.row = options.row || 4;                          //每页表情的行数this.callback = options.callback || function(){};     //当表情被点击时的回调,返回表情的code值this.emojis = window.emojis || emojis;        //加载表情包配置this.content = null;                   //.wEmoji-contentthis.navRow = null;                    //.wEmoji-rowthis.currentWrapper = null;         //.wEmoji-wrapper[data-choose="true"]this.activePage = 0;this.totalPage = 0;this.eachPartsNum = 4;                 //每一批显示的表情包数(导航栏的表情包的最大显示个数)this.wrapWidth = 0;this.count = this.getEMJPackageCount();if(options.autoInit) //当设置了autoInit之后会自动调用init函数,默认不会this.init();
};

上面的代码我都加了注释就不做细说了,下面是各个功能部分的实现(马上就可以看到我英语捉急的地方了(`・ω・´))。

首先是init(): 完成某些数据的获取以及确认进入哪种情况

init : function(){//当表情包的实际启用个数大于设定值时,启用.wEmoji-moreif(this.count > this.eachPartsNum)this.wrapper.className += " wEmoji wEmoji-more";elsethis.wrapper.className += " wEmoji";this.wrapWidth = this.wrapper.clientWidth;this.initTemplete();
},

initTemplete(): 初始化模板,更新某些数据变量,并执行接下来的工作

initTemplete : function(){var wrapper = this.wrapper,tpl = '<div class="wEmoji-header">'+'<div class="wEmoji-prev-btn">&lt;</div>'+'<div class="wEmoji-nav">'+'<div class="wEmoji-row"></div>'+'</div>'+'<div class="wEmoji-next-btn">&gt;</div>'+'</div>'+'<div class="wEmoji-container">'+'<div class="wEmoji-content"></div>'+'<div class="wEmoji-pages"></div>'+'</div>';wrapper.innerHTML = tpl;this.content = wrapper.querySelector(".wEmoji-content");this.navRow = wrapper.querySelector(".wEmoji-row");this.__initData();this.__bindEvent();
},

接下来是__initData():生成具体的表情图片和导航等,这里需要注意的是进行dom操作时不要让重排发生多次,使需要操作的dom元素脱离文档流是减少重排的方法之一。另外这里还将许多属性保存为临时变量是为了提高程序性能(至于代码优化需要自己去找资料看,这里就简单提一下)。

__initData : function(){        var emojis = this.emojis,wrapper = this.wrapper,navRow = this.navRow,content = this.content,rowWidth = navRow.clientWidth,count = this.count;        //减少重排wrapper.style.display = "none";content.innerHTML = "";navRow.style.width = count / this.eachPartsNum * 100 + "%";        for( var key in emojis ){            var emj = emojis[key];            if(!emj.enable)            continue;            //将每个生成的表情包的容器放入content中content.appendChild(this.__initContent(key,emj)); navRow.innerHTML += '<div class="wEmoji-list" data-eid="'+key+'" style="width:'+(1/count*100)+'%;">'+emj.name+'</div>';}        this.__initStyle();        this.wrapper.style.display = "block";
},

事件绑定:正常流程来走就行,注意某些地方需要用事件委托来提升性能,而这里没用addEventListener是为了防止多次初始化init的时候导致事件重复绑定,on+“event”事实上已经够用了。

__bindEvent : function(){var _self = this,wrapper = this.wrapper,row = this.navRow,pageBox = wrapper.querySelector('.wEmoji-pages'),prev = wrapper.querySelector('.wEmoji-prev-btn'),next = wrapper.querySelector('.wEmoji-next-btn'),content = this.content,down = "ontouchstart" in document ? "touchstart" : "mousedown",up = "ontouchend" in document ? "touchend" : "mouseup",move = "ontouchmove" in document ? "touchmove" : "mousemove",drag = false,x = 0;pageBox.onclick = function(e){e = e || event;var target = e.target || e.srcElement,idx = target.getAttribute("data-pageIdx");if(target.tagName.toLowerCase() != "li" || !idx){return false;}_self.showPage(idx-1);};row.onclick = function(e){e = e || event;var target = e.target || e.srcElement,eid = target.getAttribute("data-eid");if( eid && _self.emojis[eid] ){_self.chooseEmoji(eid);_self.showPage(0);}};var parts = Math.ceil(this.count / this.eachPartsNum), //可以将表情包数分为N批(默认4个一批)partsIdx = 0,navWidth = wrapper.querySelector(".wEmoji-nav").clientWidth;prev.onclick = function(e){partsIdx = partsIdx - 1 < 0 ? 0 : partsIdx - 1;row.style.webkitTransform = "translateX("+(-partsIdx * navWidth)+"px) translateZ(0px)";row.style.transform = "translateX("+(-partsIdx * navWidth)+"px) translateZ(0px)";};next.onclick = function(e){partsIdx = partsIdx + 1 >= parts ? partsIdx : partsIdx + 1;row.style.webkitTransform = "translateX("+(-partsIdx * navWidth)+"px) translateZ(0px)";row.style.transform = "translateX("+(-partsIdx * navWidth)+"px) translateZ(0px)";};content.onclick = function(e){e = e || event;var target = e.target || e.srcElement,trueTarget = getTargetNode(target,".wEmoji-item"),emjCode;if(trueTarget)emjCode = trueTarget.getAttribute("data-emj");if(!emjCode)return false;_self.callback.call(_self,emjCode);console.log(emjCode);};content["on"+down] = function(e){e = e || event;drag = true;x = e.pageX || e.touches[0].pageX;};content["on"+move] = function(e){e = e || event;e.stopPropagation();e.preventDefault();};content["on"+up] = function(e){e = e || event;if(drag){drag = false;var endX = e.pageX || e.changedTouches[0].pageX,dis = endX - x,idx;if(dis > 50){idx = Math.max(_self.activePage - 1,0);_self.showPage(idx);} else if (dis < -50){idx = Math.min(_self.activePage + 1,_self.totalPage - 1);_self.showPage(idx);}x = 0;}};},

下面是选择表情包的功能chooseEmoji():封装好后只需要调用接口即可,不管是初始化的时候还是事件触发的时候,将表情包改变时会发生操作全都放一起,因为大部分操作都是同时变化的,所以没必要继续细分了。

chooseEmoji : function(eid){var navRow = this.navRow,content = this.content,targetWrapper = content.querySelector(".wEmoji-wrapper[data-eid='"+eid+"']"),targetList = navRow.querySelector(".wEmoji-list[data-eid='"+eid+"']"),chooseWrapper = content.querySelector(".wEmoji-wrapper[data-choose='true']"),chooseList = navRow.querySelector(".wEmoji-list[data-choose='true']");if(chooseWrapper){chooseList.setAttribute("data-choose","false");chooseWrapper.setAttribute("data-choose","false");}targetWrapper.setAttribute("data-choose","true");targetList.setAttribute("data-choose","true");this.currentWrapper = targetWrapper;this.__createPageList();
},

下面是页面的切换showPage():完成初始化和事件触发时页面的切换

showPage : function(idx){this.activePage = idx;var wrapper = this.wrapper,currentWrapper = this.currentWrapper,pageTargetList = wrapper.querySelector(".wEmoji-page-list[data-pageIdx='"+(idx+1)+"']"),pageChoose = wrapper.querySelector(".wEmoji-page-list[data-choose='true']");if(pageChoose)pageChoose.setAttribute("data-choose","false");pageTargetList.setAttribute("data-choose","true");currentWrapper.style.webkitTransform = "translateX("+(-this.wrapWidth*idx)+"px) translateZ(0px)";currentWrapper.style.transform = "translateX("+(-this.wrapWidth*idx)+"px) translateZ(0px)";
}

最后一个是将code解释成img的功能函数explain(): 大家通过前面的介绍可以知道code的生成规则

explain : function(str){var reg = /\[wem:(\w+):wem\]/g,_self = this;return str.replace(reg,function(str,target){var tempArr = target.split("_"),eid = tempArr.shift(),type = tempArr.pop(),name = tempArr.join("_");path = _self.emojis[eid].path;url = name+"."+type;return '<img src="'+path+url+'" />';});
},

基本上主要代码就这么多了,还有一部分代码可以看源代码来了解,因为我基本上都有写注释所以应该不怎么难理解。

五、演示效果

演示demo效果

六、文件截图以及运行操作

1、文件截图

2、运行操作:

双击index.html即可看到效果。

六、文件截图以及运行操作

1、目前只兼容ie8+,firefox,360浏览器等主流浏览器

注:本文著作权归作者,由demo大师(http://www.demodashi.com)宣传,拒绝转载,转载需要作者授权

用javascript写一个emoji表情插件相关推荐

  1. Skywalking-02:如何写一个Skywalking trace插件

    如何写一个Skywalking trace插件 javaagent 原理 美团技术团队-Java 动态调试技术原理及实践 类图 实现 ConsumeMessageConcurrentlyInstrum ...

  2. JavaScript写一个虚拟软键盘,可拼音输入

    在某些业务场景中,无法使用外接实体键盘,也不方便调出windows自带的虚拟键盘,这时候我们可以利用JavaScript写一个虚拟键盘,实现点击输入框即可唤醒,大小写中.英文及数字的输入.可满足实际使 ...

  3. php开发俄罗斯方块,用JavaScript写一个俄罗斯方块

    曾经用 Turbo C++ 3.0 写过 DOS 下的俄罗斯方块,不久之后又用 VB 写了另一个版本.这次决定用 JavaScript 再写一个并非完全心血来潮,从技术上来说,主要是想尝试使用 web ...

  4. 用JavaScript写一个正则表达式测试工具

    用JavaScript写一个正则表达式测试工具 <!DOCTYPE html> <html><head><meta charset="utf-8&q ...

  5. 使用JavaScript写一个三级下拉框联动

    使用JavaScript写一个三级下拉框联动 首先我们需要确定思路,定义三个下拉框: 1.从后台获取包含的数组1.1定义数组2.将数组中的信息添加到下拉框中2.1遍历数组,将数组信息添加下拉框2.2每 ...

  6. 用JavaScript写一个可以聊天的桌面宠物

    好的,我们可以用 JavaScript 写一个聊天桌面宠物.首先,我们需要使用 JavaScript 的 GUI 库,比如 Electron 或 NW.js 来创建一个桌面应用程序.然后,我们可以使用 ...

  7. 手把手写一个vscode翻译插件

    1. 背景 写这篇文章的初衷是看到vscode市场上的中英翻译插件都是将翻译结果以弹窗的形式做的,体验感非常不好.如果有像有道字典那种打开一个弹窗或者新tab的翻译面板来进行使用就好了.但是找了很久都 ...

  8. python写chrome插件_用VueJS写一个Chrome浏览器插件

    浏览器基本已经天下大统了,放眼望去都是Chromium的天下.那么,能写一个浏览器插件也算是一种回报率不错的技能. 基本知识 浏览器插件官方的说法叫扩展程序,允许你为浏览器增加各种功能,但不需要深入研 ...

  9. 2019年写一个Cloud Studio插件吧

    纳尼?2019年?其实一开始我的内心是拒绝的,一转眼就到了2018年的尾声,再不写个[2019年]的标题定好文章的保质期,感觉刚写这篇文章就要过期了!然而,回顾2018年,每每想起2018年的收获,我 ...

最新文章

  1. Science Advances: 中科院微生物所叶健团队揭示双生病毒调控植物免疫平衡制约机制...
  2. 修改注册表添加信任站点及启用Activex控件(转载)
  3. mysql中case when then 的使用
  4. solr创建索引_Solr:创建拼写检查器
  5. 秒传 php,网站图片上传可以实现秒传功能吗?
  6. TensorFlow2.0:单层感知机梯度计算
  7. PyTorch——解决报错“RuntimeError: running_mean should contain *** elements not ***”
  8. WPF: 共享Grid宽度或高度的方法
  9. java完成一个学生信息调查程序_利用Java设计一个简单的学生信息管理程序
  10. BCD码和ASCII码的区别
  11. python随机森林回归数据实战
  12. loadrunner支持的浏览器版本
  13. java算法——通过身份证号获取出生的年月日
  14. HTML5简单实战--休假申请单
  15. 30天自制操作系统——第八天鼠标控制与32位模式切换
  16. iOS 页面的卡顿的原因以及如何解决. 如何优化app的启动速度
  17. 985本科大二,计算机专业,为什么很多普通一本甚至二本三本的都比自己懂得多?
  18. 第四届河南省程序设计大赛D nyoj306-走迷宫【二分DFS、最短路Dijkstra】
  19. python自动买股票_用python可以做哪些有趣的事--我:选股票
  20. EE专业申请计算机工程,美国大学EE专业分支方向介绍及申请难度分析

热门文章

  1. Liunx静态库和动态库
  2. java servlet 入门_servlet 入门详解
  3. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之第一个驱动
  4. Linux内核分析 - 网络[八]:IP协议
  5. Leetcode - 142. Linked List Cycle II
  6. 在c语言中下面的运算符中优先级最高的是,C语言习题请高手解答急急急1.下列运算符中优先级最高的运算符 爱问知识人...
  7. 令牌桶算法和漏桶算法python_图解Python算法
  8. android 沉浸式状态栏 兼容低版本,详解Android沉浸式实现兼容解决办法
  9. c语言ics什么意思,[转载]C网来话筛选(ICS)业务及实现
  10. php域名墙检测,php 网站域名被墙判断请求方法