我们值得拥有的文件上传字段

一切都在流动,一切都在变化,但是只有input[type=file]破坏了所有新手Web开发人员的神经,并且一直持续到现在。 N年前,当您刚开始了解创建网站的基本知识时,请记住自己。 年轻且经验不足,当文件选择按钮完全拒绝将背景颜色更改为您喜欢的桃子时,您真的感到惊讶。 正是在那一刻,您第一次遇到了这个坚不可摧的“下载文件”冰山,直到今天,它仍然“淹没”了新手Web开发人员。

以创建文件上传字段为例,我将向您展示如何正确隐藏input[type=file] ,将焦点设置在无法获得焦点的对象上,处理拖放事件以及通过AJAX发送文件。 另外,我还将向您介绍一些浏览器错误以及解决这些错误的方法。 本文是为初学者编写的,但在某些时候,即使是经验丰富的开发人员,也可能会很有用和有趣。

标记和主要样式


让我们从HTML标记开始:

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>  ,   </title> <link rel="stylesheet" href="style.css"> <script type="text/javascript" src="jquery-3.3.1.min.js"></script> <script type="text/javascript" src="script.js"></script> </head> <body> <form id="upload-container" method="POST" action="send.php"> <img id="upload-image" src="upload.svg"> <div> <input id="file-input" type="file" name="file" multiple> <label for="file-input"> </label> <span>   </span> </div> </form> </body> </html> 

也许您应该注意的主要元素是

 <label for="file-input"> </label> 

HTML规范不允许我们直接在input[type=file]上施加视觉属性,但是我们有一个label标记,单击它会导致单击它所附加的form元素。 令我们高兴的是,此标签在样式化方面没有任何限制:我们可以使用它来做我们想做的一切。

一个行动计划应运而生:我们随心所欲地对标签进行input[type=file]化,并将input[type=file]隐藏在视线之外。 首先,设置常规页面样式:

 body { padding: 0; margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; } #upload-container { display: flex; justify-content: center; align-items: center; flex-direction: column; width: 400px; height: 400px; outline: 2px dashed #5d5d5d; outline-offset: -12px; background-color: #e0f2f7; font-family: 'Segoe UI'; color: #1f3c44; } #upload-container img { width: 40%; margin-bottom: 20px; user-select: none; } 

现在设置标签样式:

 #upload-container label { font-weight: bold; } #upload-container label:hover { cursor: pointer; text-decoration: underline; } 

我们正在努力的目标(从标记中删除了input[type=file] ):

当然,您可以将标签居中,添加背景和边框,获得完整的按钮,但我们的首要任务是拖放。

隐藏输入


现在我们需要隐藏input[type=file] 。 首先要引起关注的是display: nonevisibility: hidden属性。 但这不是那么简单。 在某些较旧的浏览器上,单击标签将不再起作用。 但这还不是全部。 如您所知,看不见的元素无法获得焦点,无论它们说什么,焦点都很重要,因为对于某些人来说,这是与网站进行交互的唯一方法。 因此这种方法不适合我们。 让我们来解决这个问题:

 #upload-container div { position: relative; z-index: 10; } #upload-container input[type=file] { width: 0.1px; height: 0.1px; opacity: 0; position: absolute; z-index: -10; } 

我们绝对将input[type=file]相对于其父块0.1px ,将其减小到0.1px ,使其透明,并设置其z-index小于父块的z-index ,可以这么说。

恭喜,我们实现了我们想要的:我们的领域与上图中完全一样。

调整焦点


由于我们的input[type=file]实际存在于页面上,因此它具有接收焦点的能力。 也就是说,如果我们按页面上的Tab键,那么在某些时候焦点将切换到input[type=file] 。 但是问题是我们看不到这一点:我们隐藏的字段会脱颖而出。 是的,如果此时我们按Enter ,对话框将打开,一切将按预期进行,但是我们如何理解该按一下了?

我们的任务是在焦点位于文件上传字段时以某种方式选择标记。 但是,如果标签无法获得焦点,我们该怎么做呢? CSS3鉴赏家将立即考虑伪类:focus ,它定义了focus元素的样式,以及+~选择器选择了正确的邻居:位于同一嵌套级别的元素,位于所选元素之后。 考虑到在我们的标记input[type=file]它直接位于label标签的前面,因此发生了以下输入:

 #upload-container input[type=file]:focus + label { /*  */ } 

但是话又说回来,并不是那么简单。 首先,让我们讨论如何选择标签。 如您所知,所有现代浏览器(并非如此)都具有针对焦点元素的唯一默认属性。 基本上,这是outline属性,它在元素周围创建一个笔触,该元素不同于border ,因为它不会调整元素的大小并且可以从元素上移开。 通常,人们只使用一个浏览器,因此他们习惯了它的标准。 为了使人们更轻松地浏览我们的网站,我们应该尝试调整焦点,以使其在大多数流行的现代浏览器中看起来都尽可能自然。 从理论上讲,使用JavaScript,您可以获得有关用户在哪个浏览器中打开了网站的信息,并相应地自定义了样式,但是作为主要面向初学者的文章的一部分,该主题太繁琐且繁琐。 我们将尝试一点点地过。

在基于WebKet引擎的浏览器(Google Chrome,Opera,Safari)中,焦点元素的默认属性具有以下形式:

 :focus { outline: -webkit-focus-ring-color auto 5px; } 

-webkit-focus-ring-color是仅针对此引擎的焦点描边-webkit-focus-ring-color 。 也就是说,此行仅在WebKit浏览器中有效,而这正是我们所需要的。 为我们的标签指定此属性:

 #upload-container input[type=file]:focus + label { outline: -webkit-focus-ring-color auto 5px; } 

我们打开Goog​​le Chrome或Opera。 一切都会正常进行:

让我们来看一下Mozilla Firefox和Microsoft Edge中的情况。 对于这些浏览器,默认属性是:

 :focus { outline: 1px solid #0078d7; } 



 :focus { outline: 1px solid #212121; } 

相应地。

不幸的是, -moz-前缀不能与outline属性一起使用。 因此,我们将必须选择这两个属性中的哪一个。 由于Firefox用户数量要多得多,因此优先使用此特定浏览器更为合理。 这并不意味着我们剥夺了Edge用户和其他浏览器的机会来查看焦点所在,只是看起来与他们“有所不同”。 好吧,你必须做出牺牲。

我们在Mozilla Firefox的WebKit样式之前添加样式:首先,所有浏览器都将应用第一个属性,然后可以(谷歌浏览器,Opera,Safari等)使用第二个属性。

 #upload-container input[type=file]:focus + label { outline: 1px solid #0078d7; outline: -webkit-focus-ring-color auto 5px; } 

奇怪的事情开始了:在Edge中一切正常,但是Firefox由于某些未知原因拒绝将属性应用于标签,而只关注input[type=file]focus事件本身发生-通过JavaScript检查。 此外,如果您通过开发人员工具将焦点放在文件选择字段上,那么该属性将适用,并且将出现我们的笔画! 显然,这是浏览器本身的错误,但是如果有人知道为什么会发生-请在评论中写下。

好吧,没什么,普通的英雄总是四处走动。 如前所述, focus事件会发生,这意味着我们可以直接从JavaScript调整属性。 但是为此,我们将不得不更改选择器的逻辑:

 #upload-container label.focus { outline: 1px solid #0078d7; outline: -webkit-focus-ring-color auto 5px; } 

我们将为标签描述.focus类,并且每次input[type=file]获得焦点时都将添加它,并在失去焦点时将其删除。

 $('#file-input').focus(function() { $('label').addClass('focus'); }) .focusout(function() { $('label').removeClass('focus'); }); 

现在一切正常。 恭喜,我们找到了重点。

拖放式


使用拖放操作是通过跟踪特殊的浏览器事件进行的: drag, dragstart, dragend, dragover, dragenter, dragleave, drop 。 您可以在Internet上轻松找到它们的详细描述。 我们将仅跟踪其中一些。

首先,定义一个拖放元素:
 var dropZone = $('#upload-container'); 

然后,我们在CSS中描述一个特殊的类,当拉动文件的光标直接位于其上方时,我们将为其分配dropZone 。 必须以可视方式通知用户该文件已经可以释放。

 #upload-container.dragover { background-color: #fafafa; outline-offset: -17px; } 

现在,我们转到JS文件。 首先,我们需要撤消拖放事件的所有默认操作。 例如,此类事件之一是浏览器引发的文件打开。 我们根本不需要它,因此我们将编写以下行:

 dropZone.on('drag dragstart dragend dragover dragenter dragleave drop', function(){ return false; }); 

在jQuery中,调用return false等效于立即调用两个函数: e.preventDefault()e.stopPropagation()

我们开始描述我们自己的事件处理程序。 我们将以与焦点相同的方式进行操作,但是这次我们将跟踪dragenterdragover事件以添加一个类,并dragleave事件将其删除:

 dropZone.on('dragover dragenter', function() { dropZone.addClass('dragover'); }); dropZone.on('dragleave', function(e) { dropZone.removeClass('dragover'); }); 

再一次,令人不愉快的惊喜等待着我们:当您使用带有文件的鼠标沿着dropZone移动时,该字段开始闪烁。 这在Microsoft Edge和WebKit浏览器中发生。 顺便说一下,这些WebKit浏览器中的大多数当前都在Blink引擎上运行(很讽刺,是吧?)。 但是在Mozilla中,没有任何闪烁。 显然,我决定在解决焦点错误后对其进行修复。

发生这种闪烁的原因是,当您将鼠标悬停在dropZone之上时,无论是图片还是带有文件选择字段和标签的div ,出于某种原因dragleave事件。 对于我们来说显而易见的是,我们没有离开此字段,但是由于某些原因,浏览器没有退出,因此,它们.focusdropZone删除了.focus类。

再说一次,我们必须以某种方式下车。 如果浏览器本身不了解我们没有离开该领域,那么我们将不得不提供帮助。 然后,我们将通过其他条件来执行此操作:我们计算鼠标相对于dropZone的坐标,然后检查光标是否在块之外。 如果保留,则删除样式:

 dropZone.on('dragleave', function(e) { let dx = e.pageX - dropZone.offset().left; let dy = e.pageY - dropZone.offset().top; if ((dx < 0) || (dx > dropZone.width()) || (dy < 0) || (dy > dropZone.height())) { dropZone.removeClass('dragover'); }; }); 

就是这样,问题就解决了! 这是我们内部带有文件的字段的样子:


让我们继续处理drop事件本身。 但是首先请记住,除了拖放之外,我们还input[type=file] ,并且这些方法的本质上都是独立的,但是必须执行相同的操作:上传文件。 因此,我建议为这两种方法创建一个通用的单独函数,我们将文件传输到该函数中,并且它将已经决定如何处理它们。 我们将其称为sendFiles() ,但稍后将对其进行描述。 首先,请处理drop事件:

 dropZone.on('drop', function(e) { dropZone.removeClass('dragover'); let files = e.originalEvent.dataTransfer.files; sendFiles(files); }); 

首先, .dragoverdropZone删除.dragover类。 然后我们得到一个包含文件的数组。 如果使用jQuery,则路径为e.originalEvent.dataTransfer.files ;如果使用纯JS编写,则路径为e.dataTransfer.files 。 好吧,然后将数组传递给尚未实现的函数。

现在,我们将通过input[type=file]确定加载方法:

 $('#file-input').change(function() { let files = this.files; sendFiles(files); }); 

我们在文件选择按钮上跟踪change事件,我们通过this.files获得数组并将其发送给函数。

通过AJAX发送文件


最后一步-文件处理功能的描述-每个人都是唯一的。 这将直接取决于您追求的目标。 例如,我将展示如何通过AJAX将文件发送到服务器。

假设我们创建了一个用于上传照片的字段。 我们不希望其他东西到达我们的服务器,因此我们将决定文件类型:将其设为PNG和JPEG。 调节用户可以发送的一张照片的最大尺寸也是值得的。 限制为五兆字节。 我们开始描述我们的功能:

 function sendFiles(files) { let maxFileSize = 5242880; let Data = new FormData(); $(files).each(function(index, file) { if ((file.size <= maxFileSize) && ((file.type == 'image/png') || (file.type == 'image/jpeg'))) { Data.append('images[]', file); } }); }; 

在变量maxFileSize要发送到服务器maxFileSize最大文件大小。 FormData()函数,我们将创建FormData类的新对象,该类允许我们生成键值对的集合。 这样的对象可以通过AJAX轻松发送。 接下来,我们对files数组使用jQuery .each构造,这将应用我们为其每个元素设置的功能。 元素的编号和元素本身将作为参数传递给函数,我们将分别将其作为indexfile处理。 在函数本身中,我们将根据条件检查文件:文件大小小于5兆字节,类型为PNG或JPEG。 如果文件通过了测试,则可以通过调用append()函数将其添加到我们的FormData对象中。 关键是字符串'photos[]' ,其末尾的方括号表示该数组是可以包含多个对象的数组。 对象本身将是file

现在一切准备就绪,可以通过AJAX发送文件了。 将以下行添加到我们的函数中:

 $.ajax({ url: dropZone.attr('action'), type: dropZone.attr('method'), data: Data, contentType: false, processData: false, success: function(data) { alert('   '); } }); 

作为参数urltype我们分别指示input[type=file]actionmethod属性的值。 为了通过AJAX,我们将成为一个Data对象。 需要参数contentType: falseprocessData: false ,以便浏览器不会无意间将我们的文件转换为其他格式。 在success参数中,我们指定将文件成功传输到服务器后将执行的功能。 它的内容取决于您的想象力,但是我将只介绍有关成功下载的消息的适度输出。

恭喜,您现在可以创建自己的文件上传字段! 当然,我不会将我的方法定位为唯一正确和正确的方法。 我的目标是展示解决此问题的一般过程,主要适合初学者。 如果您认为可以在某处做得更好-请在评论中写下,我们将进行讨论!

仅此而已。 感谢您的关注!

资料下载:

  1. 最终版本
  2. 焦点问题
  3. 闪烁问题

触摸:

  1. 最终版本
  2. 焦点问题
  3. 闪烁问题

Source: https://habr.com/ru/post/zh-CN423035/


All Articles