摘要: 本教程将介绍 JavaScript FileReader API 及其在实现文件上传中的应用。
JavaScript FileReader API 简介
当您将文件 拖放 到网页浏览器中或选择文件通过文件输入元素上传时,JavaScript 将每个文件表示为一个 File
对象。
File
对象允许您在 JavaScript 中访问所选文件。JavaScript 使用 FileList
对象来保存 File
对象。
要读取文件的内容,您需要使用 FileReader
对象。请注意,FileReader
只能访问您通过拖放或文件输入选择的那些文件。
要使用 FileReader
对象,您需要执行以下步骤:
首先,创建一个新的 FileObject
。
const reader = new FileReader();
Code language: JavaScript (javascript)
其次,调用其中一个读取方法来读取文件的内容。例如:
reader.readAsDataURL(file);
Code language: JavaScript (javascript)
readAsDataURL()
方法读取您从 FileList
对象获得的文件内容。
readAsDataURL()
方法返回一个包含 result
属性的对象,该属性包含 data: URL
格式的数据。data: URL
代表文件数据,以 base64 编码字符串形式表示。
例如,您可以使用 readAsDataURL()
读取图像并在网页上显示其 base64 编码字符串。
除了 readAsDataURL()
方法之外,FileReader
还有其他方法用于读取文件数据,例如 readAsText()
、readAsBinaryString()
和 readAsArrayBuffer()
。
由于所有这些方法都以异步方式读取文件数据,因此您不能像这样直接返回结果:
const data = reader.readAsDataURL(file);
Code language: JavaScript (javascript)
当 readAsDataURL()
方法成功完成读取文件时,FileReader
将触发 load
事件。
第三,添加一个事件处理程序来处理 FileReader
对象的 load
事件:
reader.addEventListener('load', (e) => {
const data = e.target.result;
}
Code language: JavaScript (javascript)
使用 JavaScript FileReader 实现图像上传应用程序
我们将使用 FileReader
来实现一个 图像上传应用程序
当您将图像拖放到放置区域时,该应用程序将使用 FileReader
读取图像,并在页面上显示它们,以及文件名和文件大小。

此外,该应用程序将使用 Fetch API 将文件上传到服务器。
对于服务器端,我们将实现一个简单的 PHP 脚本,将图像上传到服务器上的 'uploads'
文件夹。
设置项目结构
首先,创建以下文件和目录结构:
├── css
| └── style.css
├── images
| └── upload.svg
├── js
| └── app.js
├── index.html
├── upload.php
└── uploads
Code language: JavaScript (javascript)
index.html
以下是 index.html
文件的示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/style.css" />
<title>FileReader API Demo - Image Upload Application</title>
</head>
<body>
<main>
<div class="dropzone">
<img src="images/upload.svg" alt="upload" width="60" />
<input type="file" class="files" id="images"
accept="image/png, image/jpeg"
multiple />
<label for="images">Choose multiple images</label>
<h3>or drag & drop your PNG or JPEG files here</h3>
</div>
<div class="image-list"></div>
</main>
<script src="js/app.js"></script>
</body>
</html>
Code language: JavaScript (javascript)
在 index.html
中,我们将 css/style.css
添加到 html
文档的 head
部分,并将 js/app.js
添加到结束 body
标记之前。
具有 dropzone
类别的 div
元素允许您将图像拖放到其中。此外,文件输入元素将使您能够选择要上传的文件。
文件输入元素接受多个文件,并且只允许 jpeg 和 png 图片。
<input type="file" class="files" id="images"
accept="image/png, image/jpeg"
multiple />
Code language: JavaScript (javascript)
style.css
提供将文件输入元素转换为按钮的样式。此外,它还具有 active
类,当您将文件拖放到放置区域时,它会突出显示放置区域。
具有 image-list
类的 div
元素将显示上传的图像。
app.js
首先,使用 querySelector()
方法选择放置区域、文件输入(文件)和图像列表元素。
const imageList = document.querySelector('.image-list');
const fileInput = document.querySelector('.files');
const dropzone = document.querySelector('.dropzone');
Code language: JavaScript (javascript)
其次,定义一个函数,该函数在放置区域添加或移除 active
类。
const setActive = (dropzone, active = true) => {
const hasActiveClass = dropzone.classList.contains('active');
if (active && !hasActiveClass) {
return dropzone.classList.add('active');
}
if (!active && hasActiveClass) {
return dropzone.classList.remove('active');
}
};
Code language: JavaScript (javascript)
如果您调用 setActive(dropzone)
,它将向 dropzone
添加 active
类。如果您调用 setActive(dropzone, false)
,它将从 dropzone
中移除 active
类。
第三,当 dragenter
和 dragover
事件发生时,突出显示 dropzone
,当 dragleave
和 drop
事件发生时,移除突出显示。
dropzone.addEventListener('dragenter', (e) => {
e.preventDefault();
setActive(dropzone);
});
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
setActive(dropzone);
});
dropzone.addEventListener('dragleave', (e) => {
e.preventDefault();
setActive(dropzone, false);
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
setActive(dropzone, false);
// ..
});
Code language: JavaScript (javascript)
第四,在 dropzone
的 drop
事件处理程序中,获取 e.target
中的 FileList
对象,即 e.target.files
。
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
setActive(dropzone, false);
// get the FileList
const { files } = e.dataTransfer;
handleImages(files);
});
Code language: JavaScript (javascript)
在 drop 事件处理程序中,我们使用对象解构来获取 FileList
对象并调用 handleImages()
函数来处理上传的图像。
第五,定义 handleImages()
函数。
const handleImages = (files) => {
// get valid images
let validImages = [...files].filter((file) =>
['image/jpeg', 'image/png'].includes(file.type)
);
// show the image
validImages.forEach(showImage);
// upload all images
uploadImages(validImages);
};
Code language: JavaScript (javascript)
handleImages()
函数获取有效的图像,使用 showImage()
函数在页面上显示每个有效的图像,并使用 uploadImages()
函数将所有图像上传到服务器。
第六,定义 showImage()
函数,该函数在 validImages
数组中显示每个图像。
const showImage = (image) => {
const reader = new FileReader();
reader.readAsDataURL(image);
reader.addEventListener('load', (e) => {
const div = document.createElement('div');
div.classList.add('image');
div.innerHTML = `
<img src="${e.target.result}" alt="${image.name}">
<p>${image.name}</p>
<p>${formatBytes(image.size)}</p>
`;
imageList.appendChild(div);
});
};
Code language: JavaScript (javascript)
showImage()
使用 FileReader
以数据 URL 的形式读取上传的图像。当 FileReader
完成读取文件后,它将创建一个新的 div
元素来保存图像信息。
请注意,formatBytes()
函数将以字节为单位的大小转换为人类可读的格式。
function formatBytes(size, decimals = 2) {
if (size === 0) return '0 bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(size) / Math.log(k));
return parseFloat((size / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
Code language: JavaScript (javascript)
第七,定义 uploadImages()
函数,该函数将所有图像上传到服务器。
const uploadImages = async (images) => {
const formData = new FormData();
[...images].forEach((image) =>
formData.append('images[]', image, image.name)
);
const response = await fetch('upload.php', {
method: 'POST',
body: formData,
});
return await response.json();
};
Code language: JavaScript (javascript)
uploadImages()
函数使用 FormData
API 来构建要提交的数据。
const formData = new FormData();
Code language: JavaScript (javascript)
对于每个图像,我们将其添加到 FormData
对象中。
[...images].forEach((image) =>
formData.append('images[]', image, image.name)
);
Code language: JavaScript (javascript)
请注意,images
变量是一个 FileList
对象,而不是一个数组。要使用 forEach()
方法,可以使用展开运算符 (...
) 将 FileList
对象转换为数组,如下所示:
[...images]
Code language: JavaScript (javascript)
表单数据中的所有键值对都具有相同的键 images[]
;在 PHP 中,您可以将其作为数组访问 ($_FILES['images']
)。
uploadImages()
函数使用 Fetch API 将图像(作为 FormData
对象)上传到服务器。
const response = await fetch('upload.php', {
method: 'POST',
body: formData,
});
return await response.json();
Code language: JavaScript (javascript)
第八,如果用户使用此输入元素选择文件,则向文件输入元素添加 change 事件处理程序。
fileInput.addEventListener('change', (e) => {
const { files } = e.target;
handleImages(files);
});
Code language: JavaScript (javascript)
在 change 事件处理程序中,您可以将 FileList
对象访问为 e.target.files
。显示和上传图像的逻辑与拖放相同。
请注意,如果您将图像拖放到放置区域之外,网页浏览器将默认显示这些图像。
要阻止这种情况,您可以像这样调用文档的 dragover
和 drop
事件对象的 preventDefault()
方法:
// prevent the drag & drop on the page
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());
Code language: JavaScript (javascript)
以下是完整的 app.js
文件:
const imageList = document.querySelector('.image-list');
const fileInput = document.querySelector('.files');
const dropzone = document.querySelector('.dropzone');
const setActive = (dropzone, active = true) => {
// active class
const hasActiveClass = dropzone.classList.contains('active');
if (active && !hasActiveClass) {
return dropzone.classList.add('active');
}
if (!active && hasActiveClass) {
return dropzone.classList.remove('active');
}
};
dropzone.addEventListener('dragenter', (e) => {
e.preventDefault();
setActive(dropzone);
});
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
setActive(dropzone);
});
dropzone.addEventListener('dragleave', (e) => {
e.preventDefault();
setActive(dropzone, false);
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
setActive(dropzone, false);
// get the valid files
const { files } = e.dataTransfer;
// hand images
handleImages(files);
});
const handleImages = (files) => {
// get valid images
let validImages = [...files].filter((file) =>
['image/jpeg', 'image/png'].includes(file.type)
);
// show the image
validImages.forEach(showImage);
// upload files
uploadImages(validImages);
};
const showImage = (image) => {
const reader = new FileReader();
reader.readAsDataURL(image);
reader.addEventListener('load', (e) => {
const div = document.createElement('div');
div.classList.add('image');
div.innerHTML = `
<img src="${e.target.result}" alt="${image.name}">
<p>${image.name}</p>
<p>${formatBytes(image.size)}</p>
`;
imageList.appendChild(div);
});
};
const uploadImages = async (images) => {
const formData = new FormData();
[...images].forEach((image) =>
formData.append('images[]', image, image.name)
);
const response = await fetch('upload.php', {
method: 'POST',
body: formData,
});
return await response.json();
};
function formatBytes(size, decimals = 2) {
if (size === 0) return '0 bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(size) / Math.log(k));
return parseFloat((size / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
fileInput.addEventListener('change', (e) => {
const { files } = e.target;
handleImages(files);
});
// prevent the drag & drop on the page
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());
Code language: JavaScript (javascript)
最后,创建一个简单的 upload.php
脚本,将上传的图像移动到 uploads
文件夹。
<?php
const APP_ROOT = 'http://localhost:8080/';
const UPLOAD_DIR = __DIR__ . '/uploads';
const MESSAGES = [
UPLOAD_ERR_OK => 'File uploaded successfully',
UPLOAD_ERR_INI_SIZE => 'File is too big to upload',
UPLOAD_ERR_FORM_SIZE => 'File is too big to upload',
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder on the server',
UPLOAD_ERR_CANT_WRITE => 'File is failed to save to disk.',
UPLOAD_ERR_EXTENSION => 'File is not allowed to upload to this server',
];
const ALLOWED_FILES = [
'image/png' => 'png',
'image/jpeg' => 'jpg'
];
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
const HTTP_STATUSES = [
200 => 'OK',
400 => 'Bad Request',
404 => 'Not Found',
405 => 'Method Not Allowed'
];
$is_post_request = strtolower($_SERVER['REQUEST_METHOD']) === 'post';
$has_files = isset($_FILES['images']);
if (!$is_post_request || !$has_files) {
response(405, [
'success' => false,
'message' => ' Method not allowed or files do not exist'
]);
}
$files = $_FILES['images'];
$file_count = count($files['name']);
// validation
$errors = [];
for ($i = 0; $i < $file_count; $i++) {
// get the uploaded file info
$status = $files['error'][$i];
$filename = $files['name'][$i];
$tmp = $files['tmp_name'][$i];
// an error occurs
if ($status !== UPLOAD_ERR_OK) {
$errors[$filename] = MESSAGES[$status];
continue;
}
// validate the file size
$filesize = filesize($tmp);
if ($filesize > MAX_SIZE) {
// construct an error message
$message = sprintf(
"The file %s is %s which is greater than the allowed size %s",
$filename,
format_filesize($filesize),
format_filesize(MAX_SIZE)
);
$errors[$filesize] = $message;
continue;
}
// validate the file type
if (!in_array(get_mime_type($tmp), array_keys(ALLOWED_FILES))) {
$errors[$filename] = "The file $filename is allowed to upload";
}
}
if ($errors) {
response(400, [
'success' => false,
'message' => $errors
]);
}
// move the files
for ($i = 0; $i < $file_count; $i++) {
$filename = $files['name'][$i];
$tmp = $files['tmp_name'][$i];
$mime_type = get_mime_type($tmp);
// set the filename as the basename + extension
$uploaded_file = pathinfo($filename, PATHINFO_FILENAME) . '.' . ALLOWED_FILES[$mime_type];
// new filepath
$filepath = UPLOAD_DIR . '/' . $uploaded_file;
// move the file to the upload dir
$success = move_uploaded_file($tmp, $filepath);
if (!$success) {
$errors[$filename] = "The file $filename was failed to move.";
}
}
if ($errors) {
response(400, [
'success' => false,
'message' => $errors
]);
}
response(200, [
'success' => true,
'message' => 'The files uploaded successfully'
]);
/**
* Return a mime type of file or false if an error occurred
*
* @param string $filename
* @return string | bool
*/
function get_mime_type(string $filename)
{
$info = finfo_open(FILEINFO_MIME_TYPE);
if (!$info) {
return false;
}
$mime_type = finfo_file($info, $filename);
finfo_close($info);
return $mime_type;
}
/**
* Return a human-readable file size
*
* @param int $bytes
* @param int $decimals
* @return string
*/
function format_filesize(int $bytes, int $decimals = 2): string
{
$units = 'BKMGTP';
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $units[(int)$factor];
}
/**
* Response JSON to the client
* @param int $status_code
* @param array|null $data
*/
function response(int $status_code, array $data = null)
{
header("HTTP/1.1 " . $status_code . " " . HTTP_STATUSES[$status_code]);
header("Content-Type: application/json");
echo json_encode($data);
exit;
}
Code language: JavaScript (javascript)
有关详细信息,请阅读更多关于 如何在 PHP 中上传多个文件 的内容。
总结
- 使用 JavaScript FileReader API 读取用户通过拖放或文件输入元素选择的那些文件。