发布框前端体验优化

2次阅读

共计 5611 个字符,预计需要花费 15 分钟才能阅读完成。

通过前端代码,利用 File API 对图文发布操作进行优化,提高用户体验。
发布框是 web 应用的一种常见图文发布功能,在微博、评论、论坛、博客或内容管理系统等产品中经常使用。做好发布框的交互设计,能提高用户的编辑效率,提高用户体验,给产品增加锦上添花的效果。
需求背景:
在实际的项目中,遇到了以下需求,用户(运营人员)可以通过发布框发布话题相关内容,产品经理期望在此发布框上实现以下功能:1、用户可以拖动文件,当文件进入浏览器时提示用户拖动文件到发布框;2、当拖动的文件(例如.exe)不符合要求时,给予拒绝提示,不能上传;3、当拖动文件(批量)为图片或文档时,解析图片和文字,预览(或上传),其余类型的文件拒绝;
4、发布框支持图片复制、和 QQ、PrintScreen 键等工具的截图后粘贴(或 ctrl+v)。
技术点:

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

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

dragstart:按下鼠标时触发
drag:按下鼠标持续时触发 (执行多次)
dragend:鼠标放开时触发

投放目标:

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

兼容性
这里我们主要用到了投放目标的 drop 事件,它的兼容性如下图所示:
主要代码:
<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 来解决。
两种方向的拖动都会触发目标节点的 dragenter 事件,这不是我们想要的结果!
// 判断两个 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 属性,它有一些常用属性和方法:1、DataTransfer.effectAllowed 和 dropEffect,用来设置拖和放的鼠标指针类型,用处不大,具体效果可点击此处查看 2、DataTransfer.files,拖拽的本地文件列表。如果拖动操作不涉及拖动文件,则此属性为空列表。3、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 的操作都会触发。粘贴图片我们需要解决下面几个问题 1、监听用户的粘贴操作 2、获取到剪切板上的数据 3、将获取到的数据渲染到网页中
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 文档等操作。由于技术的发展,这些桌面上的功能,都可以在前端实现,我们需要有一个探索的心。

正文完
 0