LY Front-end Dev Engineer

如何开发一个textarea文本域emoji插件

2017-07-10
LY

做前端的人在面临页面需要某种效果或插件的时候,都会想,是从网上找呢,还是自己造轮子开发。

如果在需求比较紧张的时候我们可能会选择从网上找相关的插件,然后学习它的用法。但是从网上找的通常功能太大太全,而且存在一个学习成本,还有可能不完全符合我们项目的需求,这样改起来也并不轻松。

所以在需求不是很紧张的时候,我们还是自己慢慢造轮子吧,这样也知根知底,容易修改。

如何开发一个轻量级比较适用的插件

  1. 减少对UI的限制
  2. 提供api,易于扩展

例如,我们要做一个textarea输入文本域支持输入emoji的插件。

确定需求

  1. 点击表情选择按钮,输入框支持展示emoji表情
  2. 复制粘贴文本自动清除格式

默认设置和选项

根据需求,我们需要一个图片的路径path参数,需要一个emoji文件名数组icons参数,还需要一个触发emoji的dom节点button, 代码如下:

(function($, window, document) {

  $.fn.emoji = function(options) {
    var settings = $.extend({}, {
      path: '',
      icons: {},
      button: null
    }, options)
    return this.each(function() {
      // emoji plugin code here
    })
  }
})

插件原理和可用性

emoji插件的原理:大家都知道textarea是不支持html的,而我们展示emoji是通过转换成html格式来实现的。那么,怎么才能既支持文本输入又支持html呢。html5来拯救世界了!

contenteditable 属性是 HTML5 中的新属性。

语法:

<element contenteditable="true|false">

经验告诉我们,在使用新属性的时候不要盲目,不要像发现新大陆一样兴奋地立即使用,稳妥些我得看下它的兼容性,can i use contenteditable ?

contenteditable

主流的浏览器大部分还是支持的,ie11以下就不支持了,为了向下兼容,我们得判断一下,采取两种机制,保证在不支持的情况下不出问题。

(function($, window, document) {

  $.fn.emoji = function(options) {
    var settings = $.extend({}, {
      path: '',
      icons: {},
      button: null
    }, options)
    return this.each(function() {
      var $textarea = $(this)
      if ('contentEditable' in document.body) {
        new Emoji_Content($textarea, settings);
      } else {
        new EmojiArea_Textarea($textarea, settings);
      }
    })
  }
})

虽然现在解决了编辑问题,但是编辑的过程中会有很多操作,比如光标的定位,复制文本区域等各种问题,这里需要乃们对window.getSelection有一定的了解。这篇文章对他的属性和方法有详细的解释javascript标准selection操作

插入一个表情符:

  1. 获取当前的range对象
  2. 将range添加到selection对象中
  3. 插入表情符,从当前的selection对象中获得某个range对象,删除range区域的文本内容,创建并插入img节点,设置img节点在range范围的开始点

Method

根据上面的思路,构成下面的四个方法:

var methods = {
  // 获取选中区域
  saveSelection: function() {
    // 非ie下
    if (window.getSelection) {
      var sel = window.getSelection()
      // 选中区域的数组,支持多选
      var ranges = []
      if (sel.rangeCount) {
        for (var i = 0; i < sel.rangeCount; i++) {
          ranges.push(sel.getRangeAt(i))
        }
      }
      return ranges
    } else {
      console.log('暂不支持ie浏览器!')
    }
  },
  pureSelection: function(saveSelection) {
    // 非ie下
    if (window.getSelection) {
      var sel = window.getSelection()
      // 先移除所有的range对象,保证range是来自插入符号的,避免插入符号的时候输入多余的文本
      sel.removeAllRanges()
      for (var i = 0; i < saveSelection.length; i++) {
        // 将range添加到selection中,在谷歌中不允许同时存在多个range
        sel.addRange(saveSelection[i])
      }
    }
  },
  replaceSelection: function(content) {
    var sel = window.getSelection()
    var range = null
    var node = typeof content === 'string' ? document.createTextNode(content) : content
    if (sel.getRangeAt && sel.rangeCount) {
      range = sel.getRangeAt(0)
      // 删除range区域应该选中的文本
      range.deleteContents()
      // 往range区域插入一个空的文本节点
      range.insertNode(document.createTextNode(' '))
      range.insertNode(node)
      // 设置插入符号后的位置为起始点
      range.setStart(node, 0)
    }
  },
  // 把符号插入textarea中
  insertAtCursor: function(text, el) {
    var content = ' ' + text
    var val = el.value
    var selectionStart = el.selectionStart
    var selectionEnd = el.selectionEnd
    var endIndex = 0
    var startIndex = 0
    var range = null
    if (typeof selectionStart != 'undefined' && typeof selectionEnd != 'undefined') {
      startIndex = selectionStart
      endIndex = selectionEnd
      el.value = val.substring(0, startIndex) + text + val.substring(endIndex)
      // 重置插入符号后输入元素selection的起始和终止位置
      el.selectionStart = el.selectionEnd = startIndex + text.length
    }
  }
}

未完~


Similar Posts

Comments