博客表情的实现方法

Author: Satori Satori

Posted: 2017-12-10 11:22:13

Category: CSS Django HTML JavaScript 技术

Views: 875 Comments: 0

## 一、背景 在博客和评论里面插入各种表情是非常常见的,虽然网上有各种各样的包,但是普遍都不能自主添加表情以及动态添加分组。因此我需要自己实现一个表情系统。 表情系统的实现还是比较复杂的,难点在于我想要实现一个组件一样的表情栏,在网页相应的地方只需要简单的引用这个组件就能实现在这个组件所在的表单里面的输入框插入表情。 ## 二、总体思路 ### 1. 总体流程 实现表情功能主要分为两步,第一步是实现一个表情组件,按这个组件的某一个表情能插入一段对应该表情的文字,第二步是前端将这个表情文字进行转化,转换成表情图片。 ### 2. 设计方法 对于表情组件而言,需要让它处在一个表单之中,而且这个表单有且仅有一个`textarea`元素,当表情组件某个表情被点击的时候,自动向这个`textarea`光标处插入对应该表情的文字。 对于前端转化而言,需要正则提取表情文字,并将它替换成对应的`img`标签图片。 ## 三、具体实现 ### 1. 表情组件的实现 使用纯后端的方法来实现。首先需要考虑表情的动态添加以及表情组的动态添加问题,因此需要把一类表情放到表情组文件夹里面,然后在每个文件夹添加对应的表情。后端只需要读取对应目录的文件就能动态地得到表情组以及每个表情。 而要实现处处能引用这个组件,我选择的是使用`template tag`来返回组件的html代码。这样每次只需要在其他template引用这个tag就可以得到表情组件了。 接着只需要拼接好html代码,将读到的文件及文件夹名字进行解析,就可以得到表情组名以及每个表情组下每个表情的名字。然后封装成`img`标签,同时为了方便之后插入表情文字,将表情所属的组名以及表情的名字装入`img`标签的数据段里面。 具体代码如下。 ``` @register.simple_tag def emoticon_bar(): cur_dir = os.path.join(settings.MEDIA_ROOT, "emoticon") emoticon_set_names = [x for x in os.listdir(cur_dir) if os.path.isdir(os.path.join(cur_dir, x))] # sort by the ascii code emoticon_set_names = sorted(emoticon_set_names) tab_list = "" img_list = "" for set_ind, emoticon_set_name in enumerate(emoticon_set_names): emoticon_dir = os.path.join(cur_dir, emoticon_set_name) files = [x for x in os.listdir(emoticon_dir) if os.path.isfile(os.path.join(emoticon_dir, x))] # sort the file by ascii code files = sorted(files) urls = [settings.MEDIA_URL + "emoticon/{}/{}".format(emoticon_set_name, x) for x in files] cur_img = "" for ind, url in enumerate(urls): cur_img += """<img src="{}" data-filename={}>""".format(url, files[ind]) cur_set = """ <div class="emoticon-set" data-emoticon_set_name="{emoticon_set_name}"> {img_str} </div> """.format(emoticon_set_name=emoticon_set_name, img_str=cur_img) img_list += cur_set tab_list += """ <a class="btn btn-default" data-ind={set_ind}>{set_name}</a> """.format(set_ind=set_ind, set_name=emoticon_set_name) string = """ <p>Emoticons:</p> <div class="emoticon-div"> <div class="emoticon-tab"> {tab_list} </div> <div class="emoticon-img"> {img_list} </div> </div> """.format(tab_list=tab_list, img_list=img_list) return mark_safe(string) ``` 这样在后面的模板里面,只要引用`{% emoticon_bar %}`就能得到表情组件了。 但只靠后端也不能实现组件的功能。因为前端需要控制按下表情后向`textarea`插入相应表情文字的工作,这需要用js实现,定义一个函数,传入一个textarea的jquery对象,再传入需要插入的文字。实现方法主要是先得到`textarea`光标的起始位置和终止位置,将这个位置的所有字符替换为text即可,同时还需要还原插入后光标的位置,只需要让新光标移动text的长度即可。代码如下。 ``` function make_text(textarea, text) { var s = textarea.get(0).selectionStart; var e = textarea.get(0).selectionEnd; var val = textarea.get(0).value; textarea.get(0).value = val.substring(0, s) + text + val.substring(e); textarea.get(0).selectionStart = e + text.length; textarea.get(0).selectionEnd = e + text.length; textarea.focus(); } ``` 接下来是定义每个表情被按下的操作,主要是找到是哪个图片被按下来确定插入的表情文字,以及找到需要插入的`textarea`的位置。找插入文字可以利用之前后端传过来的`emoticon-set`的`data-emoticon_set_name`属性来确定类别,再用该图片的`data-filename`属性得到名称。再进行拼接。我使用的表情文字定义是`[[set_name空格file_name]]`,原因是尽量避免与markdown的`[][]`的冲突。然后是找到`textarea`的位置,具体做法是找到这个表情组件所属的form,再在form的后代里面找`textarea`。最后调用`make_text`函数即可实现文字的添加。代码如下。 ``` $(".emoticon-set img").click(function(){ var set_name = $(this).parent().attr("data-emoticon_set_name"); var file_name = $(this).attr("data-filename"); var string = "[[" + set_name + " " + file_name + "]]"; // find the nearest ``textarea`` above the current bar var textarea = $(this).closest("form").find("textarea"); make_text(textarea, string); }); ``` 至于控制组件的其他点击,比如单击不同组,表情的切换控制不是非常核心的代码,简要思路就是使用一个data标签来确定哪个组被选定了,判断是第一次点击标签还是标签跳转还是点击同样标签,这三种情况对应处理一下即可。 关于css美化简要说要让用户快速得知自己正在点击的按钮,让鼠标悬停的时候将图片的不透明度改变,并调整大小,代码如下。 ``` .emoticon-set img { cursor: pointer; transition: transform 0.5s; opacity: 0.6; } .emoticon-set img:hover { transform: scale(1.2, 1.2) rotate(18deg); opacity: 1; } ``` ### 2. 表情转换的实现 这一部分相对就简单很多了,其实只需要将所有的字串正则替换为img标签,代码如下。 ``` html = html.replace(/\[\[([^ \[\]]+?) ([^ \[\]]+?)]]/g, '<img src="/media/emoticon/$1/$2">'); ``` 同时注意为了跟原有markdown协同工作,需要抽象出一个统一的render接口,首先渲染markdown,然后渲染表情,函数如下。 ``` function render_content(text) { // first default markdown render var renderer = new marked.Renderer(); renderer.code = function (code, language) { return '<pre class="prettyprint"><code class="hljs">' + hljs.highlightAuto(code).value + '</code></pre>'; }; var html = marked(text, { renderer: renderer }); // render the emoji html = html.replace(/\[\[([^ \[\]]+?) ([^ \[\]]+?)]]/g, '<img src="/media/emoticon/$1/$2">'); return html; } ``` ## 三、总结 表情这个功能总体实现难度还是不小的,要做到鲁棒性以及可重用性才是真正的难点。这种设计缺陷在于需要让组件置于一个form内,而且form内还需要只有一个textarea才能正常工作,因此还需要考虑一下更好的方法。

LOGIN TO LEAVE A COMMENT