最近折腾 Websocket,打算开发一个聊天室应用练练手。在应用开发的过程中发现可以插入 emoji ,粘贴图片的富文本输入框其实蕴含着许多有趣的知识,于是便打算记录下来和大家分享。

仓库地址:chat-input-box

预览地址:codepen

首先来看看 demo 效果:

是不是觉得很神奇?接下来我会一步步讲解这里面的功能都是如何实现的。

输入框富文本化

传统的输入框都是使用 来制作的,它的优势是非常简单,但最大的缺陷却是无法展示图片。为了能够让输入框能够展示图片(富文本化),我们可以采用设置了 contenteditable="true" 属性的

来实现这里面的功能。

简单创建一个 index.html 文件,然后写入如下内容:

class="editor" contenteditable="true">

src="https://static.easyicon.net/preview/121/1214124.gif" alt="">

打开浏览器,就能看到一个默认已经带了一张图片的输入框:

光标可以在图片前后移动,同时也可以输入内容,甚至通过退格键删除这张图片——换句话说,图片也是可编辑内容的一部分,也意味着输入框的富文本化已经体现出来了。

接下来的任务,就是思考如何直接通过 control+v 把图片粘贴进去了。

处理粘贴事件

任何通过“复制”或者 control+c 所复制的内容(包括屏幕截图)都会储存在剪贴板,在粘贴的时候可以在输入框的 onpaste 事件里面监听到。

document.querySelector('.editor').addEventListener('paste', (e) => {

console.log(e.clipboardData.items)

})

而剪贴板的的内容则存放在 DataTransferItemList 对象中,可以通过 e.clipboardData.items 访问到:

细心的读者会发现,如果直接在控制台点开 DataTransferItemList 前的小箭头,会发现对象的 length 属性为0。说好的剪贴板内容呢?其实这是 Chrome 调试的一个小坑。在开发者工具里面, console.log 出来的对象是一个引用,会随着原始数据的改变而改变。由于剪贴板的数据已经被“粘贴”进输入框了,所以展开小箭头以后看到的 DataTransferItemList 就变成空的了。为此,我们可以改用 console.table 来展示实时的结果。

在明白了剪贴板数据的存放位置以后,就可以编写代码来处理它们了。由于我们的富文本输入框比较简单,所以只需要处理两类数据即可,其一是普通的文本类型数据,包括 emoji 表情;其二则是图片类型数据。

新建 paste.js 文件:

const onPaste = (e) => {

// 如果剪贴板没有数据则直接返回

if (!(e.clipboardData && e.clipboardData.items)) {

return

}

// 用Promise封装便于将来使用

return new Promise((resolve, reject) => {

// 复制的内容在剪贴板里位置不确定,所以通过遍历来保证数据准确

for (let i = 0, len = e.clipboardData.items.length; i < len; i++) {

const item = e.clipboardData.items[i]

// 文本格式内容处理

if (item.kind === 'string') {

item.getAsString((str) => {

resolve(str)

})

// 图片格式内容处理

} else if (item.kind === 'file') {

const pasteFile = item.getAsFile()

// 处理pasteFile

// TODO(pasteFile)

} else {

reject(new Error('Not allow to paste this type!'))

}

}

})

}

export default onPaste

然后就可以在 onPaste 事件里面直接使用了:

document.querySelector('.editor').addEventListener('paste', async (e) => {

const result = await onPaste(e)

console.log(result)

})

上面的代码支持文本格式,接下来就要对图片格式进行处理了。玩过 type="file"> 的同学会知道,包括图片在内的所有文件格式内容都会储存在 File 对象里面,这在剪贴板里面也是一样的。于是我们可以编写一套通用的函数,专门来读取 File 对象里的图片内容,并把它转化成 base64 字符串。

粘贴图片

为了更好地在输入框里展示图片,必须限制图片的大小,所以这个图片处理函数不仅能够读取 File 对象里面的图片,还能够对其进行压缩。

新建一个 chooseImg.js 文件:

/**

* 预览函数

*

* @param {*} dataUrl base64字符串

* @param {*} cb 回调函数

*/

function toPreviewer (dataUrl, cb) {

cb && cb(dataUrl)

}

/**

* 图片压缩函数

*

* @param {*} img 图片对象

* @param {*} fileType 图片类型

* @param {*} maxWidth 图片最大宽度

* @returns base64字符串

*/

function compress (img, fileType, maxWidth) {

let canvas = document.createElement('canvas')

let ctx = canvas.getContext('2d')

const proportion = img.width / img.height

const width = maxWidth

const height = maxWidth / proportion

canvas.width = width

canvas.height = height

ctx.fillStyle = '#fff'

ctx.fillRect(0, 0, canvas.width, canvas.height)

ctx.drawImage(img, 0, 0, width, height)

const base64data = canvas.toDataURL(fileType, 0.75)

canvas = ctx = null

return base64data

}

/**

* 选择图片函数

*

* @param {*} e input.onchange事件对象

* @param {*} cb 回调函数

* @param {number} [maxsize=200 * 1024] 图片最大体积

*/

function chooseImg (e, cb, maxsize = 200 * 1024) {

const file = e.target.files[0]

if (!file || !/\/(?:jpeg|jpg|png)/i.test(file.type)) {

return

}

const reader = new FileReader()

reader.onload = function () {

const result = this.result

let img = new Image()

if (result.length <= maxsize) {

toPreviewer(result, cb)

return

}

img.onload = function () {

const compressedDataUrl = compress(img, file.type, maxsize / 1024)

toPreviewer(compressedDataUrl, cb)

img = null

}

img.src = result

}

reader.readAsDataURL(file)

}

export default chooseImg

关于使用 canvas 压缩图片和使用 FileReader 读取文件的内容在这里就不赘述了,感兴趣的读者可以自行查阅。

回到上一步的 paste.js 函数,把其中的 TODO() 改写成 chooseImg() 即可:

const imgEvent = {

target: {

files: [pasteFile]

}

}

chooseImg(imgEvent, (url) => {

resolve(url)

})

回到浏览器,如果我们复制一张图片并在输入框中执行粘贴的动作,将可以在控制台看到打印出了以 data:image/png;base64 开头的图片地址。

输入框中插入内容

经过前面两个步骤,我们后已经可以读取剪贴板中的文本内容和图片内容了,接下来就是把它们正确的插入输入框的光标位置当中。

对于插入内容,我们可以直接通过 document.execCommand 方法进行。关于这个方法详细用法可以在MDN文档里面找到,在这里我们只需要使用 insertTextinsertImage 即可。

document.querySelector('.editor').addEventListener('paste', async (e) => {

const result = await onPaste(e)

const imgRegx = /^data:image\/png;base64,/

const command = imgRegx.test(result) ? 'insertImage': 'insertText'

document.execCommand(command, false, result)

})

但是在某些版本的 Chrome 浏览器下, insertImage 方法可能会失效,这时候便可以采用另外一种方法,利用 Selection 来实现。而之后选择并插入 emoji 的操作也会用到它,因此不妨先来了解一下。

当我们在代码中调用 window.getSelection() 后会获得一个 Selection 对象。如果在页面中选中一些文字,然后在控制台执行 window.getSelection().toString(),就会看到输出是你所选择的那部分文字。

与这部分区域文字相对应的,是一个 range 对象,使用 window.getSelection().getRangeAt(0) 即可以访问它。 range 不仅包含了选中区域文字的内容,还包括了区域的起点位置 startOffset 和终点位置 endOffset

我们也可以通过 document.createRange() 的办法手动创建一个 range,往它里面写入内容并展示在输入框中。

对于插入图片来说,要先从 window.getSelection() 获取 range ,然后往里面插入图片。

document.querySelector('.editor').addEventListener('paste', async (e) => {

// 读取剪贴板的内容

const result = await onPaste(e)

const imgRegx = /^data:image\/png;base64,/

// 如果是图片格式(base64),则通过构造range的办法把标签插入正确的位置

// 如果是文本格式,则通过document.execCommand('insertText')方法把文本插入

if (imgRegx.test(result)) {

const sel = window.getSelection()

if (sel && sel.rangeCount === 1 && sel.isCollapsed) {

const range = sel.getRangeAt(0)

const img = new Image()

img.src = result

range.insertNode(img)

range.collapse(false)

sel.removeAllRanges()

sel.addRange(range)

}

} else {

document.execCommand('insertText', false, result)

}

})

这种办法也能很好地完成粘贴图片的功能,并且通用性会更好。接下来我们还会利用 Selection,来完成 emoji 的插入。

插入 emoji

不管是粘贴文本也好,还是图片也好,我们的输入框始终是处于聚焦(focus)状态。而当我们从表情面板里选择 emoji 表情的时候,输入框会先失焦(blur),然后再重新聚焦。由于 document.execCommand 方法必须在输入框聚焦状态下才能触发,所以对于处理 emoji 插入来说就无法使用了。

上一小节讲过, Selection 可以让我们拿到聚焦状态下所选文本的起点位置 startOffset 和终点位置 endOffset,如果没有选择文本而仅仅处于聚焦状态,那么这两个位置的值相等(相当于选择文本为空),也就是光标的位置。只要我们能够在失焦前记录下这个位置,那么就能够通过 range 把 emoji 插入正确的地方了。

首先编写两个工具方法。新建一个 cursorPosition.js 文件:

/**

* 获取光标位置

* @param {DOMElement} element 输入框的dom节点

* @return {Number} 光标位置

*/

export const getCursorPosition = (element) => {

let caretOffset = 0

const doc = element.ownerDocument || element.document

const win = doc.defaultView || doc.parentWindow

const sel = win.getSelection()

if (sel.rangeCount > 0) {

const range = win.getSelection().getRangeAt(0)

const preCaretRange = range.cloneRange()

preCaretRange.selectNodeContents(element)

preCaretRange.setEnd(range.endContainer, range.endOffset)

caretOffset = preCaretRange.toString().length

}

return caretOffset

}

/**

* 设置光标位置

* @param {DOMElement} element 输入框的dom节点

* @param {Number} cursorPosition 光标位置的值

*/

export const setCursorPosition = (element, cursorPosition) => {

const range = document.createRange()

range.setStart(element.firstChild, cursorPosition)

range.setEnd(element.firstChild, cursorPosition)

const sel = window.getSelection()

sel.removeAllRanges()

sel.addRange(range)

}

有了这两个方法以后,就可以放入 editor 节点里面使用了。首先在节点的 keyupclick 事件里记录光标位置:

let cursorPosition = 0

const editor = document.querySelector('.editor')

editor.addEventListener('click', async (e) => {

cursorPosition = getCursorPosition(editor)

})

editor.addEventListener('keyup', async (e) => {

cursorPosition = getCursorPosition(editor)

})

记录下光标位置后,便可通过调用 insertEmoji() 方法插入 emoji 字符了。

insertEmoji (emoji) {

const text = editor.innerHTML

// 插入 emoji

editor.innerHTML = text.slice(0, cursorPosition) + emoji + text.slice(cursorPosition, text.length)

// 光标位置后挪一位,以保证在刚插入的 emoji 后面

setCursorPosition(editor, this.cursorPosition + 1)

// 更新本地保存的光标位置变量(注意 emoji 占两个字节大小,所以要加1)

cursorPosition = getCursorPosition(editor) + 1 // emoji 占两位

}

尾声

文章涉及的代码已经上传到仓库,为了简便起见采用 VueJS 处理了一下,不影响阅读。最后想说的是,这个 Demo 仅仅完成了输入框最基础的部分,关于复制粘贴还有很多细节要处理(比如把别处的行内样式也复制了进来等等),在这里就不一一展开了,感兴趣的读者可以自行研究,更欢迎和我留言交流~

来都来了,点个在看再走吧~~~

alert获取输入框内容_实用开源:Web 聊天工具的富文本输入框相关推荐

  1. 一款实用的web截图工具(一)

    码云地址: 码云 github地址: github kscreenshot 介绍 web截图工具的功能实现基于Canvas技术.其功能主要包括截图,下载,复制以及在截图过程中通过工具栏对截图进行绘制. ...

  2. 开源报表工具python_开源web报表工具哪家强?5款最优软件

    阅读提示: 文章中与FineReport软件使用的相关内容,基于软件的V7.0旧版本编写,不代表软件最新的使用方式. FineReport最新版免费试用:https://www.finereport. ...

  3. 14款web前端常用的富文本编辑器插件

    富文本编辑器是一种可内嵌于浏览器,所见即所得的文本编辑器.它提供类似于Office Word 的编辑功能,方便那些不太懂html用户使用,富文本编辑器的应用非常广泛,它的历史与图文网页诞生的历史几乎一 ...

  4. Drupal笔记之富文本输入框配置文件上传功能

    现在Drupal中的富文本输入框用的是默认的CKEditor,默认是没有文件上传功能的,只有上传图片.我现在需要用到这个功能,来额外配置下. 我这里用的是Editor File Upload模块. 先 ...

  5. 开源 软件测试自动化工具,开源Web自动化测试工具Selenium IDE

    Selenium IDE(也有简写SIDE的)是一款开源的Web自动化测试工具,支持测试用例的录制与回放. 只要在浏览器里装一下插件,就可以开始使用,简直是"开箱即用".我们相信测 ...

  6. web mysql报表工具_新一代Java web报表工具—Smartbi电子表格的优势

    Smartbi电子表格作为新一代的Java web报表工具,它具有什么功能?有哪些特点?相对其他报表产品相比有什么优势?通过下面的初步体验,帮助大家认识电子表格产品. 软件的安装 Smartbi电子表 ...

  7. 五款高效的开源Web性能测试工具

    专业的软件测试工程师至少要掌握一到两种测试工具,而作为普通软件开发者,或多或少掌握一些测试方法和技巧.随着用户对科技产品用户体验度的上升,产品发布前的测试工作变得尤为重要. 工欲善其事必先利其器,下面 ...

  8. 微信小程序解析渲染Web App中的富文本内容

    前提:微信小程序里面没有DOM对象,不能直接操作DOM. 问题:在日常的web前端开发中,必然会接触到富文本编辑器,如何在小程序里展示渲染富文本编辑器中的HTML元素呢? 测试内容:wangEdito ...

  9. 五款资深高效的开源Web性能测试工具

    专业的软件测试工程师至少要掌握一到两种测试工具,而作为普通软件开发者,或多或少掌握一些测试方法和技巧.随着用户对科技产品用户体验度的上升,产品发布前的测试工作变得尤为重要. 工欲善其事必先利其器,下面 ...

最新文章

  1. 还是贪心(结构体排序)
  2. 牛客练习赛26B 烟花 (概率DP)
  3. 依赖插件版本冲突问题
  4. Linux找最大最小值的命令,Linux中awk命令正确的求最大值、最小值、平均值、总和...
  5. mybatis处理集合、循环、数组和in等语句的使用
  6. 对自己有用的VS调试技巧
  7. 最近在群里┏━━━━━━━━━飞鸽传书━━━━━━━━━━┓
  8. LayoutInflater.inflate()方法两个参数和三个参数
  9. windows 编程 —— 消息与参数(滚动条、键盘、鼠标)
  10. 案例:对比使用Java代码与EL表达式获取信息
  11. java.lang.Object android.content.Context.getSystemService(java.lang.String)‘ on a null object
  12. python android自动化测试框架_appium+python搭建自动化测试框架_Tools安装(一)
  13. paip.提升效率----几款任务栏软件
  14. 网络安全实验---防火墙实验
  15. 印刷汉字识别方法综述
  16. 阵列信号处理 窄带信号与包络
  17. JAVA面试题及解答
  18. 15 条实用 Linux/Unix 磁带管理命令
  19. ExtJs之Text文本框Text和文本域TextArea
  20. pytorch入门笔记

热门文章

  1. python中用于标识字符串的定界符_Python合集之Python数据类型(二)
  2. 怎么查看自己电脑的配置_怎么查看自己网卡是千兆网卡还是百兆网卡
  3. php项目webpack打包,Vue项目webpack打包部署时Tomcat刷新报404错误问题如何处理
  4. linux怎么安装java环境变量_linux怎么配置java环境变量
  5. oracle 11g 大小,修改oracle 11GR2归档模式和归档目录及大小-Oracle
  6. windows 导出 oracle,windows 环境下oracle导入导出
  7. $cfg_dbtype = mysql_多库操作2:终于实现多个数据库操作
  8. mysql5.7gtid_MySQL5.7 GTID 运维实战
  9. mysql 按重复排序_php-按日期排序并允许重复的日期时,获取MySQL中的上一个和下一个记录...
  10. 【深度学习】模型训练过程可视化思路(可视化工具TensorBoard)