用javascript写一个emoji脸色插件

良久没有写文章了,说实话本人如今受困于五月病已快变成一条死咸鱼了(T_T),本次就当写一个简朴的js插件教程了。本项目标代码相对比较简朴,至于内里有些变量定名的题目就请你们不要吐槽了Σ(゚д゚lll)(好的,我认可我英语就小学程度好吧。除了hello和goodbye其他的都不会了____orz)。 空话就讲到这里,下面最先正文。

demo: 我是demo
git : 我是项目git
下载地点: 点我下载

1. 事前预备

事实上在写一个插件前我们都须要事前想好你要完成哪些功用,怎样去完成,这些大方向的东西是须要事前斟酌的,至于详细细节和优化选项我们可以在写代码的过程当中再举行修正。

就以我们写的这个emoji插件为例,网上已有一些相干的插件了,但你总觉得有些部份的需求不能被满足(如:可以自行增添新的脸色包而不用去改源代码等等),这时刻我们就可以列出你想完成的功用项了:

  1. 须要满足基础的脸色插件的需求,包含图片和对应code的互相转换

  2. 愿望可以经由过程参数来调解每行以及每列脸色图片的显现个数,而且可以针对差别脸色包零丁调解

  3. 愿望用户可以在不相识源代码的状况下也能自行主动增添新的脸色包

  4. 模板界面简朴,可以举行自适应,而且兼容挪动端

  5. 尽量只供应简朴的api接口和要领,防备内部触及其他不是很相干的功用(如绑定某个特定的元素或许在内部举行数据传输等等),坚持插件的灵活性等等

以上就是我们暂时能想到的功用和需求,下面就最先写一个完整的插件了(固然原生js插件某种程度来讲运用起来相对比较自在,由于不须要依靠某些特定库,而且也不须要根据某些库类的花样规范举行插件的编写,但少了一些封装好的要领也会使得插件写起来更辛苦,至于怎样弃取就须要看个人需求来定了)

2. 举行构造分别

当我们正式最先代码编写的时刻,固然想本身写出来的代码不敢说很强势,但至少构造清楚,易于读懂,而且代码的机能也须要保证。这时刻我们就须要回到前面的需求了,由上面列出的5点可以看出,大部份的功用需求都是在我们顺序内部去完成的,唯一须要斟酌的是上面的第3点。

这时刻我们可以已想到方法了,比如说将新的脸色包填好相干的参数后由接口传入顺序内部去作处置惩罚。固然这是一个合理的挑选,但斟酌到代码的复杂度和运用的浅易度,我们最好照样竖立一个对应config文件。由于起首如许我们可以供应一些默许的脸色包,而且设置好相干的参数并诠释,背面的运用者只须要根据相干的花样复制然后修正就好了。而且将一些非逻辑性的数据零丁断绝开来有利于保持清楚的代码构造,增添代码的易读性。所以到这里已可以基础上肯定我们须要的文件了:

  1. 一个模板css文件; 2. 一个数据设置文件config.js; 3. 一个逻辑完成文件js;

3. 填写设置文件

这里先填写设置文件是为了有一个更明白的需求,以及防备在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)

4. 插件开写

前面的预备事情都做好后,如今我们终究可以最先写真正的代码了。虽然前面的内容不怎样多,但关于一个插件以致一个项目来讲都是必不可少的一个步骤,特别是初学者,最先动手写本身的插件时多想一想该怎样做老是没错的。

起首我们须要建立一个对象(固然你经由过程闭包来写也是可以的),明白好哪些数据和函数是可以共用的,哪些是不能共用的。就我个人的履历来讲,平常关于用来保留数据用的变量,最好都放在函数体内,而要领则都放在原型上。

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+'" />';
        });
},

基础上重要代码就这么多了,另有一部份代码可以看源代码来相识,由于我基础上都有写诠释所以应当不怎样难明白。

5. 结语

虽然我很想进一步把教程写完整,但基于本人身材已被掏空的现实状况斟酌,就不做打算了,结果的话可以点开上面的demo去看,人人有什么题目迎接留言发问,以后会不定时写一些插件,到时刻也迎接人人来恭维,以上(写完要死了(ง ° ͜ °)ง)。

    原文作者:孤月
    原文地址: https://segmentfault.com/a/1190000009455711
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞