一切都在流动,一切都在变化,但是只有
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: none
和
visibility: 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; }
我们打开Google 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()
。
我们开始描述我们自己的事件处理程序。 我们将以与焦点相同的方式进行操作,但是这次我们将跟踪
dragenter
和
dragover
事件以添加一个类,并
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
事件。 对于我们来说显而易见的是,我们没有离开此字段,但是由于某些原因,浏览器没有退出,因此,它们
.focus
从
dropZone
删除了
.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); });
首先,
.dragover
从
dropZone
删除
.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
构造,这将应用我们为其每个元素设置的功能。 元素的编号和元素本身将作为参数传递给函数,我们将分别将其作为
index
和
file
处理。 在函数本身中,我们将根据条件检查文件:文件大小小于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(' '); } });
作为参数
url
和
type
我们分别指示
input[type=file]
的
action
和
method
属性的值。 为了通过AJAX,我们将成为一个
Data
对象。 需要参数
contentType: false
和
processData: false
,以便浏览器不会无意间将我们的文件转换为其他格式。 在
success
参数中,我们指定将文件成功传输到服务器后将执行的功能。 它的内容取决于您的想象力,但是我将只介绍有关成功下载的消息的适度输出。
恭喜,您现在可以创建自己的文件上传字段! 当然,我不会将我的方法定位为唯一正确和正确的方法。 我的目标是展示解决此问题的一般过程,主要适合初学者。 如果您认为可以在某处做得更好-请在评论中写下,我们将进行讨论!
仅此而已。 感谢您的关注!
资料下载:
- 最终版本
- 焦点问题
- 闪烁问题
触摸:
- 最终版本
- 焦点问题
- 闪烁问题