图片上传
在前端开发中,上传文件是一个普遍的需求,现在项目都是使用现成的组件库或者试用公司内部自己维护的公共组件库进行上传功能的实现,例如 elementUI、ant design。这里想说的是这个前端上传的组件是怎么实现的,其实现原理是通过 XMLHttpRequest实现的,xhr 中有一个 upload 方法,通过 upload 方法进行文件上传,同时 xhr还支持一个 progress 事件,通过这个 progress 就可以知道文件上传的进度,这个功能对大文件上传非常有用。关于上传这部分代码的实现,可以看一下 ElementUI的实现,点击查看。
图片压缩
除了文件上传功能,还要看一下压缩的功能,对于做过图片上传的小伙伴可能都知道现在手机拍出来的照片一般都要几兆,对于大部分业务场景不需要如此高清的图片,上传之后为了节省存储通常后端会进行图片的压缩,当然有些项目是需要保留高清图片,这些情况排除在外。JS 前端的压缩功能,需要借助 Canvas,首先将图片绘制到 Canvas 上,然后在从 Cavas 上把图片导出来,导出来的时候可以调整质量来达到图片压缩的目的。最后通过上面说的方式上传到后台服务器。下边是一个简单的例子。导出时需要注意类型,PNG 的透明在 JPG 时会变为黑色背景。
代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input
type="file"
id="fileElem"
multiple
accept="image/*"
style="display: none"
/>
<a href="#" id="fileSelect">Select some files</a>
<div id="fileList">
<p>No files selected!</p>
</div>
<button name="upload" value="test" onclick="javascript:sendFiles();">
Upload Image
</button>
<script>
const fileSelect = document.getElementById("fileSelect"),
fileElem = document.getElementById("fileElem"),
fileList = document.getElementById("fileList");
//点击时触发 input file 按钮
fileSelect.addEventListener(
"click",
(e) => {
if (fileElem) {
fileElem.click();
}
e.preventDefault(); // prevent navigation to "#"
},
false
);
//文件选择完成触发 change 事件
fileElem.addEventListener("change", handleFiles, false);
function handleFiles() {
if (!this.files.length) {
fileList.innerHTML = "<p>No files selected!</p>";
} else {
fileList.innerHTML = "";
const list = document.createElement("ul");
fileList.appendChild(list);
//显示用户上传的图片文件
for (let i = 0; i < this.files.length; i++) {
const li = document.createElement("li");
list.appendChild(li);
const img = document.createElement("img");
img.classList.add("obj");
img.src = URL.createObjectURL(this.files[i]);
img.file = this.files[i];
img.height = 60;
img.onload = () => {
URL.revokeObjectURL(img.src);
};
li.appendChild(img);
const info = document.createElement("span");
info.innerHTML = `${this.files[i].name}: ${this.files[i].size} bytes`;
li.appendChild(info);
}
}
}
//图片压缩
const compressImage = async (file, { quality = 1, type = file.type }) => {
// Get as image data
const imageBitmap = await createImageBitmap(file);
// Draw to canvas
const canvas = document.createElement("canvas");
canvas.width = imageBitmap.width;
canvas.height = imageBitmap.height;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imageBitmap, 0, 0);
// Turn into Blob
return await new Promise((resolve) =>
canvas.toBlob(resolve, type, quality)
);
};
//上传文件
function sendFiles() {
const imgs = document.querySelectorAll(".obj");
for (let i = 0; i < imgs.length; i++) {
new FileUpload(imgs[i], imgs[i].file);
}
}
//上传主方法
function FileUpload(img, file) {
const reader = new FileReader();
this.ctrl = createThrobber(img);
const xhr = new XMLHttpRequest();
this.xhr = xhr;
const self = this;
this.xhr.upload.addEventListener(
"progress",
(e) => {
if (e.lengthComputable) {
const percentage = Math.round((e.loaded * 100) / e.total);
self.ctrl.update(percentage);
}
},
false
);
xhr.upload.addEventListener(
"load",
(e) => {
self.ctrl.update(100);
const canvas = self.ctrl.ctx.canvas;
canvas.parentNode.removeChild(canvas);
},
false
);
//******************修改为自己的地址
xhr.open("POST", "http://10.91.3.193:8090/test/upload");
compressImage(file, {
quality: 0.5,
type: "image/png",
}).then((compressedFile) => {
xhr.send(compressedFile);
});
}
//进度条
function createThrobber(img) {
const throbberWidth = 64;
const throbberHeight = 6;
const throbber = document.createElement("canvas");
throbber.classList.add("upload-progress");
throbber.setAttribute("width", throbberWidth);
throbber.setAttribute("height", throbberHeight);
img.parentNode.appendChild(throbber);
throbber.ctx = throbber.getContext("2d");
throbber.ctx.fillStyle = "orange";
throbber.update = (percent) => {
throbber.ctx.fillRect(
0,
0,
(throbberWidth * percent) / 100,
throbberHeight
);
if (percent === 100) {
throbber.ctx.fillStyle = "green";
}
};
throbber.update(0);
return throbber;
}
</script>
</body>
</html>