基于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 相关文章推荐
基于Jquery的动态创建DOM元素的代码
Dec 28 Javascript
JS操作Cookies包括(读取添加与删除)
Dec 26 Javascript
JS实现倒计时和文字滚动的效果实例
Oct 29 Javascript
jQuery实现文本展开收缩特效
Jun 03 Javascript
JS跨域解决方案之使用CORS实现跨域
Apr 14 Javascript
实例解析jQuery工具函数
Dec 01 Javascript
vue.js项目中实用的小技巧汇总
Nov 29 Javascript
Vue.js上传图片到阿里云OSS存储的方法示例
Dec 13 Javascript
Nuxt.js实现一个SSR的前端博客的示例代码
Sep 06 Javascript
vue打包静态资源后显示空白及static文件路径报错的解决
Sep 02 Javascript
vue实现几秒后跳转新页面代码
Sep 09 Javascript
JavaScript架构localStorage特殊场景下二次封装操作
Jun 21 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
Java中final关键字详解
2015/08/10 PHP
Yii框架实现记录日志到自定义文件的方法
2017/05/23 PHP
JQuery从头学起第一讲
2010/07/04 Javascript
JS 实现完美include载入实现代码
2010/08/05 Javascript
JQuery中判断一个元素下面是否有内容或者有某个标签的判断代码
2012/02/02 Javascript
javascript实现禁止复制网页内容
2014/12/16 Javascript
详解JavaScript对Date对象的操作问题(生成一个倒数7天的数组)
2015/10/01 Javascript
jquery事件的ready()方法使用详解
2015/11/11 Javascript
jquery实现表格中点击相应行变色功能效果【实例代码】
2016/05/09 Javascript
js中利用cookie实现记住密码功能
2020/08/20 Javascript
探究JavaScript中的五种事件处理程序方式
2016/12/07 Javascript
微信小程序基于slider组件动态修改标签透明度的方法示例
2017/12/04 Javascript
JavaScript防止全局变量污染的方法总结
2018/08/02 Javascript
JavaScript常用数组操作方法,包含ES6方法
2020/05/10 Javascript
Python实现的银行系统模拟程序完整案例
2019/04/12 Python
python设计tcp数据包协议类的例子
2019/07/23 Python
安装python及pycharm的教程图解
2019/10/10 Python
使用Python刷淘宝喵币(低阶入门版)
2019/10/30 Python
wxpython绘制音频效果
2019/11/18 Python
Python中包的用法及安装
2020/02/11 Python
基于python生成英文版词云图代码实例
2020/05/16 Python
python实现取余操作的简单实例
2020/08/16 Python
Restful_framework视图组件代码实例解析
2020/11/17 Python
python爬虫scrapy基本使用超详细教程
2021/02/20 Python
LocalStorage记住用户和密码功能
2017/07/24 HTML / CSS
全球虚拟主机商:HostGator
2017/02/06 全球购物
Ancheer官方户外和运动商店:销售电动自行车
2019/08/07 全球购物
事业单位个人应聘自荐信
2013/09/21 职场文书
经典导游欢迎词大全
2014/01/16 职场文书
优秀幼教自荐信
2014/02/03 职场文书
同事打架检讨书
2014/02/04 职场文书
请假条标准格式规范
2014/04/10 职场文书
大学生党性分析材料
2014/12/19 职场文书
强烈推荐:小学生:暑假作息时间表(值得收藏)
2019/07/09 职场文书
Windows下使用Nginx+Tomcat做负载均衡的完整步骤
2021/03/31 Servers
JavaScript与JQuery框架基础入门教程
2021/07/15 Javascript