发布框前端体验优化

经过前端代码,利用File API对图文发布操做进行优化,提升用户体验。

图片描述
发布框是web应用的一种常见图文发布功能,在微博、评论、论坛、博客或内容管理系统等产品中常用。作好发布框的交互设计,能提升用户的编辑效率,提升用户体验,给产品增长锦上添花的效果。javascript

需求背景:

在实际的项目中,遇到了如下需求,用户(运营人员)能够经过发布框发布话题相关内容,产品经理指望在此发布框上实现如下功能:
一、用户能够拖动文件,当文件进入浏览器时提示用户拖动文件到发布框;
二、当拖动的文件(例如.exe)不符合要求时,给予拒绝提示,不能上传;
三、当拖动文件(批量)为图片或文档时,解析图片和文字,预览(或上传),其他类型的文件拒绝;html

图片描述
四、发布框支持图片复制、和QQ、PrintScreen键等工具的截图后粘贴(或ctrl+v)。前端

图片描述

技术点:

  1. 拖放功能(drag & drop)
  2. File API功能
  3. 复制粘贴事件

drag & drop

拖放是 HTML5 中常见的功能。即:把抓取的对象拖放到其余位置(想一想一下两个元素换位)。与他相关就是两个动做——拖和放。因此,它涉及到两个元素。一个是被拖的元素,称为拖放源;另外一个是要放的目标,称为拖放目标。所涉及到的事件就是两类:drag(源)和drop(目标)。
与它相关的两个事件(按触发的前后顺序,参照物是鼠标指针而非文件边缘):java

拖拽源:

  1. dragstart:按下鼠标时触发
  2. drag:按下鼠标持续时触发 (执行屡次)
  3. dragend:鼠标放开时触发

投放目标:

  1. dragenter:拖动目标且鼠标进入投放区时触发
  2. dragover:拖动目标且鼠标移动在投放区时触发(每隔 350 毫秒会触发一次)
  3. dragleave:拖动对象且离开投放区时触发
  4. drop:拖动对象且在投放区放开鼠标时触发(须要在dragover上设置禁止默认事件,才会有触发,奇怪的设定)

兼容性

这里咱们主要用到了投放目标的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()返回的是一个布尔值,来表示传入的节点是否为该节点的后代节点。利用这两个特性,就能够解决这个问题。后端

DataTransfer对象

任何拖动事件,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:文件名,该属性只读。
  • size:文件大小,单位为字节,该属性只读。
  • type:文件的 MIME 类型,若是分辨不出类型,则为空字符串,该属性只读。
  • lastModified:文件的上次修改时间,格式为时间戳。
  • lastModifiedDate:文件的上次修改时间,格式为 Date 对象实例

咱们须要name、type、size属性来校验格式和大小是否知足要求。HTML5给咱们提供了FileReader API 用于读取文件,即把文件内容读入内存。它的参数是 File 对象或 Blob 对象。

什么是File

在前端开发中,最多见的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 大鹏哥

相关文章
相关标签/搜索