基于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 处理事件绑定的一些兼容写法
Dec 24 Javascript
jquery插件制作 自增长输入框实现代码
Aug 17 jQuery
JS获取URL中参数值(QueryString)的4种方法分享
Apr 12 Javascript
当某个文本框成为焦点时即清除文本框内容
Apr 28 Javascript
5个可以帮你理解JavaScript核心闭包和作用域的小例子
Oct 08 Javascript
js和jquery实现监听键盘事件示例代码
Jun 24 Javascript
jQuery页面加载初始化的3种方法(推荐)
Jun 02 Javascript
使用AngularJS 跨站请求如何解决jsonp请求问题
Jan 16 Javascript
原生js实现弹出层效果
Jan 20 Javascript
微信小程序 滚动到某个位置添加class效果实现代码
Apr 19 Javascript
基于layui的下拉列表的数据回显方法
Sep 24 Javascript
vue请求数据的三种方式
Mar 04 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中的加密功能
2006/10/09 PHP
PHP写MySQL数据 实现代码
2009/06/15 PHP
php实现购物车功能(上)
2020/07/23 PHP
php远程下载类分享
2016/04/13 PHP
thinkPHP多表查询及分页功能实现方法示例
2017/07/03 PHP
jquery实现的带缩略图的焦点图片切换(自动播放/响应鼠标动作)
2013/01/23 Javascript
分享Javascript中最常用的55个经典小技巧
2013/11/29 Javascript
jquery+css3打造一款ajax分页插件(自写)
2014/06/18 Javascript
javascipt:filter过滤介绍及使用
2014/09/10 Javascript
JavaScript 动态加载脚本和样式的方法
2015/04/13 Javascript
jquery图片滚动放大代码分享(1)
2015/08/25 Javascript
js+css实现的圆角边框TAB选项卡滑动门代码分享(2款)
2015/08/26 Javascript
jquery实现邮箱自动填充提示功能
2015/11/17 Javascript
BootStrap实用代码片段之一
2016/03/22 Javascript
JS如何设置cookie有效期为当天24点并弹出欢迎登陆界面
2016/08/04 Javascript
Vue学习笔记之表单输入控件绑定
2017/09/05 Javascript
vue中的provide/inject的学习使用
2018/05/09 Javascript
解决淘宝cnpm 安装后cnpm不是内部或外部命令的问题
2018/05/17 Javascript
vue2.0+vuex+localStorage代办事项应用实现详解
2018/05/31 Javascript
VueCli3.0中集成MockApi的方法示例
2019/07/05 Javascript
python支持断点续传的多线程下载示例
2014/01/16 Python
python处理二进制数据的方法
2015/06/03 Python
改进Django中的表单的简单方法
2015/07/17 Python
python logging 日志轮转文件不删除问题的解决方法
2016/08/02 Python
Python引用类型和值类型的区别与使用解析
2017/10/17 Python
wxPython修改文本框颜色过程解析
2020/02/14 Python
俄罗斯有趣和原创礼物网上商店:MagicMag
2019/08/01 全球购物
美国购物网站:Clickhere2shop
2021/01/28 全球购物
电子商务专员岗位职责
2013/12/11 职场文书
采购部部门职责
2013/12/15 职场文书
法律专业实习鉴定
2013/12/22 职场文书
小学节能减排倡议书
2014/05/15 职场文书
综合素质评价思想道德自我评价
2015/03/09 职场文书
2015年营销工作总结范文
2015/04/23 职场文书
学校就业保障协议书
2019/06/24 职场文书
详解JS数组方法
2021/11/20 Javascript