概述

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

详细

很久没有写文章了,说实话本人现在受困于五月病已经快变成一条死咸鱼了(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-content
this.navRow = null; //.wEmoji-row
this.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-more
if(this.count > this.eachPartsNum)
this.wrapper.className += " wEmoji wEmoji-more";
else
this.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大师发表,拒绝转载,转载需要作者授权

用javascript写一个emoji表情插件的更多相关文章

  1. 给Ionic写一个cordova(PhoneGap)插件

    给Ionic写一个cordova(PhoneGap)插件 之前由javaWeb转html5开发,由于面临新技术,遂在适应的过程中极为挣扎,不过还好~,这个过程也极为短暂:现如今面临一些较为复杂的需求还 ...

  2. 如何写一个Js上传图片插件。

    项目里面需要一个上传图片的插件,找了半天没有找到满意的,算了 不找了,自己写一个吧,顺便复习一下js方面的知识.完成之后效果还不错,当然还要继续优化,源码在最后. 介绍一种常见的js插件的写法 ; ( ...

  3. 怎么用JavaScript写一个区块链?

    几乎所有语言都可以编写区块链开发程序.那么如何用JavaScript写一个区块链?以下我将要用JavaScript来创建1个简单的区块链来演示它们的内部到底是怎样工作的.我将会称作SavjeeCoin ...

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

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

  5. JavaScript写一个连连看的游戏

    天天看到别人玩连连看, 表示没有认真玩过, 不就把两个一样的图片连接在一起么, 我自己写一个都可以呢. 使用Javascript写了一个, 托管到github, 在线DEMO地址查看:打开 最终的效果 ...

  6. 写一个Vue loading 插件

    什么是vue插件? 从功能上说,插件是为Vue添加全局功能的一种机制,比如给Vue添加一个全局组件,全局指令等: 从代码结构上说,插件就是一个必须拥有install方法的对象,这个方法的接收的第一个参 ...

  7. 用JavaScript写一个区块链

    几乎每个人都听说过像比特币和以太币这样的加密货币,但是只有极少数人懂得隐藏在它们背后的技术.在这篇博客中,我将会用JavaScript来创建一个简单的区块链来演示它们的内部究竟是如何工作的.我将会称之 ...

  8. 改变滚动条的原始样式: chrome 可以改变, IE只能变相关颜色,firfox好像也不好改。最好是自己写一个或是用插件

    相关作者链接地址: https://www.lyblog.net/detail/314.html 问题: 1.我在项目中遇到的问题: 在设置了::-webkit-scrollbar 后,滚动条不见了! ...

  9. 用javascript写一个前端等待控件

    前端等待控件有啥新奇的?什么jquery啦,第三方控件啦,好多好多,信手拈来. 因为项目使用了bootstrap的原因,不想轻易使用第三方,怕不兼容.自己写一个. 技术点包括动态加载CSS,javas ...

随机推荐

  1. 构建一个高可扩展性javabean和jsp连接数据库操作

    1. 我们先在Tomcat 中创建一个DataSource- jdbc/Panabia,然后再创建一个java“基类”,这个类封装了数据库连接和连接的释放. package Panabia.db; i ...

  2. Mac 在启动eclipse时 Failed to load JavaHL Library解决方法

    在Mac 10.9.1系统里, 在Eclipse中安装svn的插件,出现如下提示 方法一: 1.根据提示进入链接 http://subclipse.tigris.org/wiki/JavaHL 2. ...

  3. [Python爬虫]煎蛋网OOXX妹子图爬虫(1)——解密图片地址

    之前在鱼C论坛的时候,看到很多人都在用Python写爬虫爬煎蛋网的妹子图,当时我也写过,爬了很多的妹子图片.后来煎蛋网把妹子图的网页改进了,对图片的地址进行了加密,所以论坛里面的人经常有人问怎么请求的 ...

  4. [asp.net入门]利用ADO.NET处理数据的简单之处

    由于项目需要,要往数据库中导入一些历史数据,而这些历史数据都是线下人工记录的,所以有很多不规范的地方,比如:同一个公司的名称在不同的记录中可能相差那么几个字,而且每条数据不是每个字段都是完整的,等等诸 ...

  5. 【Django】Django如何保证并发操作数据一致性问题

    代码示例: 使用 select for update 数据库查询 select ... for update 是数据库层面上专门用来解决并发取数据后再修改的场景的,主流的关系数据库 比如mysql.p ...

  6. 如何将数据转换libsvm格式文件

    原文:http://blog.sina.com.cn/s/blog_5c2f929b0100qse8.html 有三种工具可用1.网上有一个xls文FormatDataLibsvm.xls具有宏命令, ...

  7. OpenNMS编译,打包并在Windows下启动

    1.Download Opennms latest source code 2.Download latest Java JDK and install it. Set JAVA_HOME path ...

  8. 【Nodejs】使用put方式向后端查询数据并在页面显示

    前端代码: <!DOCTYPE html> <html lang="utf-8"> <meta http-equiv="Content-Ty ...

  9. 相似qq的IM聊天应用源代码

    这个是IM聊天应用源代码,该应用IM支持实现XMPP,以及图片和表情,语音.消息回执等功能,基本覆盖了常见的im应用的功能了,大家能够參考一下吧. 源代码下载:http://code.662p.com ...

  10. MFC画图总结-DIB图形绘制

    參考文档: http://blog.csdn.net/hnust_xiehonghao/article/details/37652927 http://blog.sina.com.cn/s/blog_ ...