基于vue-simplemde实现图片拖拽、粘贴功能


Posted in Javascript onApril 12, 2018

项目使用的是vue框架,需要一个markdown的编辑框,就在npm上找了一下,发现simplemde挺不错的,由于我比较懒,就顺便在npm又搜了一下,找到了vue-simplemde这个 package ,那就开始使用它吧。

但是这个 vue-simplemde 不支持图片拖拽上传、粘贴上传,也不能说是因为这个 vue-simplemde ,因为 vue-simplemde 只是对 simplemde 的基础上封装成一个Vue插件。所以最后还是由于 simplemde 没有提供相关的功能,但是为了用户体验考虑,这个功能时必要的,除非不使用markdown编辑器。而去使用富文本编辑器,那样的话,项目很多的代码都要进行更改。所以就在网上查了文章,及在github上查了一些代码。下面将进行分析

拖拽

拖拽的API核心是 drop 这个事件,就是当我们从桌面拖动一个文件到浏览器里时,松开的时候,而触发的事件名。

我们都知道,你随便拖动一个图片到浏览器里,会直接打开这个图片,这是因为浏览器默认你拖动文件到浏览器里时,将打开这个文件,所以,我们需要阻止原生的操作。

我们现在先写一段代码,让其屏蔽掉默认事件

window.addEventListener("drop", e => {
 e = e || event
 if (e.target.className === 'CodeMirror-scroll') { // 如果进入到编辑器的话,将阻止默认事件
 e.preventDefault()
 }
}, false)

CodeMirror-scroll 这个Class就是 simplemde 编辑框的Class名称。

现在我们拖拽文件到这个编辑框,然后松掉,不会出现任何反应。如果在编辑框之外的地方,还是会继续触发默认事件。

下面就是获取 simplemde 方法,给他 drop 事件处理方法。

// 假设页面一共有三个编辑窗口,所以需要循环监听事件
[ this.$refs.simplemde1,
 this.$refs.simplemde2,
 this.$refs.simplemde3
].map(({simplemde}) => {
 simplemde.codemirror.on('drop', (editor, e) => {
 if (!(e.dataTransfer && e.dataTransfer.files)) {
  // 弹窗说明,此浏览器不支持此操作
  return
 }

 let dataList = e.dataTransfer.files
 let imageFiles = [] // 要上传的文件实例数组

 // 循环,是因为可能会同时拖动几个图片文件
 for (let i = 0; i < dataList.length; i++) {
 // 如果不是图片,则弹窗警告 仅支持拖拽图片文件
  if (dataList[i].type.indexOf('image') === -1) {
  // 下面的continue,作用是,如果用户同时拖动2个图片和一个文档,那么文档不给于上传,图片照常上传。
  continue
  }
  imageFiles.push(dataList[i]) // 先把当前的文件push进数组里,等for循环结束之后,统一上传。
 }
 // uploadImagesFile方法是上传图片的方法
 // simplemde.codemirror的作用是用于区分当前的图片上传是处于哪个编辑框
 this.uploadImagesFile(simplemde.codemirror, imageFiles)
 // 因为已经有了下面这段代码,所以上面的屏蔽默认事件代码就不用写了
 e.preventDefault()
 })
})

诈一看,代码好像有点多,那是因为注释的原因,下面是没有注释的代码。你可以根据下面的代码,有自己的见解和理解:

[ this.$refs.simplemde1,
 this.$refs.simplemde2,
 this.$refs.simplemde3
].map(({simplemde}) => {
 simplemde.codemirror.on('drop', (editor, e) => {
 if (!(e.dataTransfer && e.dataTransfer.files)) {
  return
 }
 let dataList = e.dataTransfer.files
 let imageFiles = []
 for (let i = 0; i < dataList.length; i++) {
  if (dataList[i].type.indexOf('image') === -1) {
  continue
  }
  imageFiles.push(dataList[i])
 }
 this.uploadImagesFile(simplemde.codemirror, imageFiles)
 e.preventDefault()
 })
})

粘贴

粘贴的API是 paste 方法,这个不像上面一样,粘贴不需要禁止默认事件,因为我们可以看到,你复制一个图片,到浏览器里按下 ctrl+v 的时候,是不会发生任何变化的,所以没用必要禁止默认事件。

下面是代码:

simplemde.codemirror.on('paste', (editor, e) => { // 粘贴图片的触发函数
 if (!(e.clipboardData && e.clipboardData.items)) {
 // 弹窗说明,此浏览器不支持此操作
 return
 }
 try {
 let dataList = e.clipboardData.items
 if (dataList[0].kind === 'file' && dataList[0].getAsFile().type.indexOf('image') !== -1) {
  this.uploadImagesFile(simplemde.codemirror, [dataList[0].getAsFile()])
 }
 } catch (e) {
 // 弹窗说明,只能粘贴图片
 }
})

之所以这里写上 try...catch 方法,是因为如果你粘贴的时候,如果是一个文件, items 将是空的,而在下面的if循环里,使用 dataList[0].kind 。也就是 e.clipboardData.items[0].kind 。当 item 为空时,还去访问一个不存的 kind 属性时,就会报错了。所以这里需要使用 try...catch 方法进行判断。

dataList[0].getAsFile().type.indexOf('image') !== -1 这个句话是判断,粘贴的东西确认是图片,而不是其他东西。

if 里的上传图片,不一样的地方是 [dataList[0].getAsFile()] ,因为为了统一格式,方便 uploadImagesFile 函数进行处理,我加上了 [] ,使之成为数组。 dataList[0].getAsFile() 就是获取文件实例了。

上传

上传就有一点麻烦了:

uploadImagesFile (simplemde, files) {
 // 把每个文件实例使用FormData进行包装一下,然后返回一个数组
 let params = files.map(file => {
 let param = new FormData()
 param.append('file', file, file.name)
 return param
 })

 let makeRequest = params => {
 return this.$http.post('/Api/upload', params)
 }
 let requests = params.map(makeRequest)

 this.$http.spread = callback => {
 return arr => {
  return callback.apply(null, arr)
 }
 }

 // 服务端返回的格式是{state: Boolean, data: String}
 // state为false时,data就是返回的错误信息
 // state为true时,data是图片上传后url地址,这个地址是针对网站的绝对路径。如下:
 // /static/upload/2cfd6a50-3d30-11e8-b351-0d25ce9162a3.png
 Promise.all(requests)
 .then(this.$http.spread((...resps) => {
  for (let i = 0; i < resps.length; i++) {
  let {state, data} = resps[i].data
  if (!state) {
   // 弹窗显示data的错误信息
   continue
  }
  let url = `![](${location.origin + data})` // 拼接成markdown语法
  let content = simplemde.getValue()
  simplemde.setValue(content + url + '\n') // 和编辑框之前的内容进行拼接
  }
 }))
}

因为我是把 axiox 封装成vue插件来使用,这样会导致, this.$http 是实例化后的,而不是他本身。 axios 维护者说的解决方案是,重新引入 axios 包,来使用。但是我觉得没有必要。 axios.all 内部是 Promise.all 。 axios.spread 实现代码比较少,就直接拿过来,重新赋值给 axios 就好了

所以上面有段代码是

Promise.all(requests)
 .then(this.$http.spread((...resps) => {
 // code
 })

把这段代码翻译一下就是

axios.all(requests)
 .then(axios.spread((...resps) => {
 // code
 })

关于这个问题,请看下官方的解释:axios-all-is-not-a-function-inside-vue-component 。也可以看下 axios 的代码: axios.js#L45-L48

这个问题,暂时就不深究了,我们回到刚刚的话题上。

上面我说到当state为true时,data是文件相对于网站的绝对路径,如: /static/upload/2cfd6a50-3d30-11e8-b351-0d25ce9162a3.png

如果我们需要进行拼接一下,所以就有了 ![](${location.origin + data}) 这段代码进行拼接。最后的两行是获取指的获取之前的内容,然后在追加url地址。

Javascript 相关文章推荐
Javascript实例教程(19) 使用HoTMetal(7)
Dec 23 Javascript
理解JavaScript的caller,callee,call,apply
Apr 28 Javascript
JS控制日期显示的小例子
Nov 23 Javascript
jquery实现的省市区三级联动
Apr 02 Javascript
jQuery Easyui Tabs扩展根据自定义属性打开页签
Aug 15 Javascript
JS排序之选择排序详解
Apr 08 Javascript
ES6学习教程之对象的扩展详解
May 02 Javascript
JavaScript实现一个简易的计算器实例代码
May 10 Javascript
Angular网络请求的封装方法
May 22 Javascript
微信小程序内拖动图片实现移动、放大、旋转的方法
Sep 04 Javascript
详解angular2如何手动点击特定元素上的点击事件
Oct 16 Javascript
微信小程序选择图片控件
Jan 19 Javascript
基于angular6.0实现的一个组件懒加载功能示例
Apr 12 #Javascript
JavaScript实现简单的文本逐字打印效果示例
Apr 12 #Javascript
jQuery实现鼠标点击处心形漂浮的炫酷效果示例
Apr 12 #jQuery
Koa项目搭建过程详细记录
Apr 12 #Javascript
React props和state属性的具体使用方法
Apr 12 #Javascript
react-native 圆弧拖动进度条实现的示例代码
Apr 12 #Javascript
关于vue中 $emit的用法详解
Apr 12 #Javascript
You might like
php知道与问问的采集插件代码
2010/10/12 PHP
php 操作数组(合并,拆分,追加,查找,删除等)
2012/07/20 PHP
php数组中删除元素之重新索引的方法
2014/09/16 PHP
AJAX的跨域与JSONP(为文章自动添加短址的功能)
2010/01/17 Javascript
JavaScript之appendChild、insertBefore和insertAfter使用说明
2010/12/30 Javascript
jQuery 源码分析笔记(3) Deferred机制
2011/06/19 Javascript
jQuery基本选择器选择元素使用介绍
2013/04/18 Javascript
解析JavaScript中的不可见数据类型
2013/12/02 Javascript
js返回上一页并刷新的多种实现方法
2014/02/26 Javascript
JS实现的页面自定义滚动条效果
2015/10/26 Javascript
Vue开发中整合axios的文件整理
2017/04/29 Javascript
JavaScript之filter_动力节点Java学院整理
2017/06/28 Javascript
javascript 面向对象实战思想分享
2017/09/07 Javascript
Angular2监听页面大小变化的解决方法
2017/10/09 Javascript
JS 正则表达式验证密码、邮箱格式的实例代码
2018/10/28 Javascript
Vue实现拖放排序功能的实例代码
2019/07/08 Javascript
JS实现图片切换特效
2019/12/23 Javascript
JavaScript回调函数callback用法解析
2020/01/14 Javascript
python算法学习之桶排序算法实例(分块排序)
2013/12/18 Python
在Mac OS系统上安装Python的Pillow库的教程
2015/11/20 Python
Ruby元编程基础学习笔记整理
2016/07/02 Python
Python无损音乐搜索引擎实现代码
2018/02/02 Python
Python实现多条件筛选目标数据功能【测试可用】
2018/06/13 Python
python django框架中使用FastDFS分布式文件系统的安装方法
2019/06/10 Python
python加密解密库cryptography使用openSSL生成的密匙加密解密
2020/02/11 Python
Python常用扩展插件使用教程解析
2020/11/02 Python
美国巧克力喷泉品牌:Sephra
2019/05/05 全球购物
网络编程中设计并发服务器,使用多进程与多线程,请问有什么区别?
2016/03/27 面试题
一道Delphi面试题
2016/10/28 面试题
鉴定评语大全
2014/05/05 职场文书
大型主题婚礼活动策划方案
2014/09/15 职场文书
2014年行政后勤工作总结
2014/12/06 职场文书
2015年学校心理健康教育工作总结
2015/05/11 职场文书
老人与海读书笔记
2015/06/26 职场文书
Python list列表删除元素的4种方法
2021/11/01 Python
 Redis 串行生成顺序编码的方法实现
2022/04/03 Redis