基于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遭遇CoffeeScript的时候 使用分享
Sep 17 Javascript
Javascript自定义排序 node运行 实例
Jun 05 Javascript
用innerhtml提高页面打开速度的方法
Aug 02 Javascript
禁用Enter键表单自动提交实现代码
May 22 Javascript
JS实现文字链接感应鼠标淡入淡出改变颜色的方法
Feb 26 Javascript
在JavaScript中处理时间之setMinutes()方法的使用
Jun 11 Javascript
jQuery结合jQuery.cookie.js插件实现换肤功能示例
Oct 14 jQuery
JS实现按钮添加背景音乐示例代码
Oct 17 Javascript
Vue完整项目构建(进阶篇)
Feb 10 Javascript
VUE中v-on:click事件中获取当前dom元素的代码
Aug 22 Javascript
解决Angular4项目部署到服务器上刷新404的问题
Aug 31 Javascript
angular2 组件之间通过service互相传递的实例
Sep 30 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类的自动载入机制
2016/09/16 PHP
php如何计算两坐标点之间的距离
2018/12/29 PHP
JQuery为textarea添加maxlength属性的代码
2010/04/07 Javascript
jQuery cdn使用介绍
2013/05/08 Javascript
基于jquery实现的定时显示与隐藏div广告的实现代码
2013/08/22 Javascript
通过遮罩层实现浮层DIV登录的js代码
2014/02/07 Javascript
Node.js与PHP、Python的字符处理性能对比
2014/07/06 Javascript
js使用for循环与innerHTML获取选中tr下td值
2014/09/26 Javascript
jQuery中Form相关知识汇总
2015/01/06 Javascript
JavaScript的代码编写格式规范指南
2015/12/07 Javascript
AngularJS入门教程之AngularJS模型
2016/04/18 Javascript
JS仿京东移动端手指拨动切换轮播图效果
2020/04/10 Javascript
JavaScript简单计算人的年龄示例
2017/04/15 Javascript
BootStrap Table实现server分页序号连续显示功能(当前页从上一页的结束序号开始)
2017/09/12 Javascript
JavaScript中的事件与异常捕获详析
2019/02/24 Javascript
[01:05]DOTA2完美大师赛趣味视频之选手教你打职业
2017/11/23 DOTA
python发送arp欺骗攻击代码分析
2014/01/16 Python
python绘图库Matplotlib的安装
2014/07/03 Python
浅析python实现scrapy定时执行爬虫
2018/03/04 Python
python获取代码运行时间的实例代码
2018/06/11 Python
Python使用Selenium爬取淘宝异步加载的数据方法
2018/12/17 Python
如何在Django中设置定时任务的方法示例
2019/01/18 Python
PHP统计代码行数的小代码
2019/09/19 Python
python向图片里添加文字
2019/11/26 Python
Python基础之函数基本用法与进阶详解
2020/01/02 Python
Python实现在线批量美颜功能过程解析
2020/06/10 Python
python统计mysql数据量变化并调用接口告警的示例代码
2020/09/21 Python
曼联官方网上商店:Manchester United Direct
2017/07/28 全球购物
科茨沃尔德家居商店:Scotts of Stow
2018/06/29 全球购物
乌克兰网上珠宝商店:GoldSoveren
2020/03/31 全球购物
简单租房协议书(范本)
2014/10/13 职场文书
公司市场部岗位职责
2015/04/15 职场文书
2015年社区精神文明工作总结
2015/05/26 职场文书
Spring Boot 底层原理基础深度解析
2022/04/03 Java/Android
golang三种设计模式之简单工厂、方法工厂和抽象工厂
2022/04/10 Golang
python可视化分析绘制带趋势线的散点图和边缘直方图
2022/06/25 Python