经过前端代码,利用File API对图文发布操做进行优化,提升用户体验。
发布框是web应用的一种常见图文发布功能,在微博、评论、论坛、博客或内容管理系统等产品中常用。作好发布框的交互设计,能提升用户的编辑效率,提升用户体验,给产品增长锦上添花的效果。javascript
在实际的项目中,遇到了如下需求,用户(运营人员)能够经过发布框发布话题相关内容,产品经理指望在此发布框上实现如下功能:
一、用户能够拖动文件,当文件进入浏览器时提示用户拖动文件到发布框;
二、当拖动的文件(例如.exe)不符合要求时,给予拒绝提示,不能上传;
三、当拖动文件(批量)为图片或文档时,解析图片和文字,预览(或上传),其他类型的文件拒绝;html
四、发布框支持图片复制、和QQ、PrintScreen键等工具的截图后粘贴(或ctrl+v)。前端
拖放是 HTML5 中常见的功能。即:把抓取的对象拖放到其余位置(想一想一下两个元素换位)。与他相关就是两个动做——拖和放。因此,它涉及到两个元素。一个是被拖的元素,称为拖放源;另外一个是要放的目标,称为拖放目标。所涉及到的事件就是两类:drag(源)和drop(目标)。
与它相关的两个事件(按触发的前后顺序,参照物是鼠标指针而非文件边缘):java
这里咱们主要用到了投放目标的drop事件,它的兼容性以下图所示:node
<textarea rows="" cols="" id="myTextarea"></textarea> <script type="text/javascript"> //<!-- let myTextarea = document.getElementById('myTextarea'); document.addEventListener("dragenter", function(e) { // 被拖动物品进入页面给予虚线边提示 e.preventDefault(); myTextarea.style.border = "#666 1px dashed"; }, false); document.addEventListener("dragleave", function(e) { // 被拖动物品离开页面 e.preventDefault(); myTextarea.style.border = "none"; }, false); myTextarea.addEventListener("dragover", function(e) { // 被拖动物品移动 e.preventDefault(); }, false); myTextarea.addEventListener('drop', function(e){ // 被拖动物品放置 e.preventDefault(); let upfile = e.dataTransfer.files; console.log(upfile); },false) //--> </script>
dragenter(dragleave)的事件触发相似于mouseover(mouseout),当在子节点内外的拖动时,会触发子节点的drage事件并向上冒泡,引发屡次触发当前节点的drag事件。举例来讲,咱们只在document上绑定dragenter事件,可是任何进出页面子标签的拖动,都会再次触发document的dragenter事件。能够经过是否包含和relatedTarget来解决。web
两种方向的拖动都会触发目标节点的dragenter事件,这不是咱们想要的结果!segmentfault
//判断两个a中是否包含b function contains(a,b){ return a.contains ? a != b && a.contains(b) :!!(a.compareDocumentPosition(b) & 16); } NodeB.addEventListener("dragenter", (e) => { event.preventDefault(); let related = e.relatedTarget || e.fromElement; if ((related != NodeB) && !NodeB.contains(related)) { //do something } }, false); NodeB.addEventListener("dragleave", (e) => { e.preventDefault(); let related = e.relatedTarget || e.toElement; if ((related != NodeB) && !NodeB.contains(related)) { //do something } }, false);
event对象有一个属性叫relatedTarget,这个属性就是用来判断enter和leave事件目标节点的相关节点的属性。简单的来讲就是当触发enter事件时,relatedTarget属性表明的就是鼠标刚刚离开的那个节点,当触发leave事件时它表明的是鼠标移向的那个对象。因为IE不支持这个属性,不过它有代替的属性,分别是 fromElement和toElement, node.contains()返回的是一个布尔值,来表示传入的节点是否为该节点的后代节点。利用这两个特性,就能够解决这个问题。后端
任何拖动事件,event参数中都会一个DataTransfer属性,它有一些经常使用属性和方法:
一、DataTransfer.effectAllowed和dropEffect,用来设置拖和放的鼠标指针类型,用处不大,具体效果可点击此处查看
二、DataTransfer.files,拖拽的本地文件列表。若是拖动操做不涉及拖动文件,则此属性为空列表。
三、DataTransfer.items,只读,提供DataTransferItemList对象,该对象是全部拖动数据的列表,包含DataTransfer.files。数组
// 图片校验 checkFile(file){ const isJPG = /jpg|jpeg|png/.test(file.type.toLowerCase()); const isFile = /jpg|jpeg|png/.test(file.name.toLowerCase()); if (!isJPG || !isFile) { console.log('只能够上传jpg、png的图片。') } const isLt2M = file.size / 1024 / 1024 < 2; if (!isLt2M) { console.log('图片尺寸不容许超过2MB!') } return isJPG && isFile && isLt2M; } let content:string, pic:[] textareaDropfn(e){ e.preventDefault(); let fileList = e.dataTransfer.files; let contentText:string; for (let i = 0; i < fileList.length; i++) { const el = fileList[i]; if(el.type == 'text/plain' || el.type == 'text/html'){ // 文本文件 let reader = new FileReader(); let that = this; reader.onload = (function(file) { return function(e) { that.weiboContent += this.result; }; })(el); //读取文本内容 reader.readAsText(el, "gbk"); } else if(this.checkFile(el)) { //读取图片 this.pic.push(el); } } return false; }
DataTransfer.files对象包含了咱们拖动的File对象,是个数组对象,包含如下属性:浏览器
咱们须要name、type、size属性来校验格式和大小是否知足要求。HTML5给咱们提供了FileReader API 用于读取文件,即把文件内容读入内存。它的参数是 File 对象或 Blob 对象。
在前端开发中,最多见的file就是表单上传的文件,它是一个file对象,而FileList对象则是这些file对象的集合列表,表明所选择的全部文件。file对象继承于Blob对象,该对象表示二进制原始数据,提供slice方法(能够用来文件分片),能够访问到字节内部的原始数据块。总之,file对象包含与FlieList对象,而file对象继承于Blob对象!他们的关系以下图:
对于不一样类型的文件,FileReader 提供不一样的方法读取文件。
readAsText(Blob|File, opt_encoding):返回文本字符串。默认状况下,文本编码格式是 UTF-8,能够经过可选的格式参数,指定其余编码格式的文本。用此方法咱们能够读取文件内容。
readAsDataURL(Blob|File):返回一个基于 Base64 编码的 data-uri 对象。用此方法咱们能够作图片预览。
咱们知道,img的src属性或background的url属性,能够经过被赋值为图片网络地址或base64的方式显示图片。在文件上传中,咱们通常会先将本地文件上传到服务器,上传成功后,由后台返回图片的网络地址再在前端显示。经过FileReader的readAsDataURL方法,咱们能够不通过后台,直接将本地图片显示在页面上。这样作能够减小先后端频繁的交互过程,减小服务器端无用的图片资源,代码以下:
let input = document.getElementById("file"); input.onchange = function(){ let file = this.files[0]; if(!!file){ let reader = new FileReader(); // 图片文件转换为base64 reader.readAsDataURL(file); reader.onload = function(){ // 显示图片 document.getElementById("file_img").src = this.result; } } }
还有,URL对象也提供了一个把File类型的文件,转化为url(数据URL)的方法。
传入一个 File 对象或者 Blob 对象,能生成一个连接:
let objecturl = window.URL.createObjectURL(file|blob);
这个 URL 能够放置于任何一般能够放置 URL 的地方,也可用此方法作图片预览。
在发布框里支持粘贴图片,可省去用户截图保存、再删除的麻烦。copy、cut、paste这三个事件是一个类型的事件。咱们指望得到剪贴板(clipboard)里面的图片,能够用给页面中的元素绑定paste事件的方法,当用户鼠标在该元素上或者该元素处于focus状态,右键粘贴或者ctrl+v的操做都会触发。
粘贴图片咱们须要解决下面几个问题
一、监听用户的粘贴操做
二、获取到剪切板上的数据
三、将获取到的数据渲染到网页中
myTextarea.addEventListener("paste", function (e){ let items = e.clipboardData && e.clipboardData.items || []; });
clipboardData对象是一个DataTransfer类型的对象,DataTransfer 是拖动产生的一个对象,但实际上粘贴事件也是它。items的DataTransferItem有两个属性kind和type,咱们能够经过循环取出粘贴板上的数据,而后经过kind来判断是文件(file)仍是字符串(string),若是kind是file,能够用getAsFile方法获取到文件。type属性则包含的是具体体的数据类型即MIME-Type。
textareaPaste(e){ let cbd = e.clipboardData; for(let i = 0; i < cbd.items.length; i++) { let item = cbd.items[i]; if(item.kind == "file"){ var blob = item.getAsFile(); if (blob.size === 0) { return; } this.postImg(blob); } } }
获取到file文件以后,就能够进行一些列操做了。
结合拖放事件API,DataTransfer对象和文件读取对象FileList等方面的知识,能够实现拖拽上传图文并预览效果。其实它们能实现的功能远不止这些,好比拖动排序、大文件分片段点上传,或者结合后台处理Word文档等操做。因为技术的发展,这些桌面上的功能,均可以在前端实现,咱们须要有一个探索的心。
做者:TNFE 大鹏哥