前端文件上传方案:别再只用input type=file了

前端文件上传方案:别再只用input type=file了

前端文件上传方案:别再只用input type=file了

毒舌时刻

这代码写得跟网红滤镜似的——仅供参考。

各位前端同行,咱们今天聊聊前端文件上传。别告诉我你还在用原生的input上传大文件,那感觉就像在用小水管灌满游泳池——慢得让人绝望。

为什么你需要文件上传方案

最近看到一个项目,上传100MB的文件直接卡死浏览器,没有任何进度提示,我差点当场去世。我就想问:你是在做上传还是在做浏览器杀手?

反面教材

<!-- 反面教材:原生文件上传 --> <input type="file" onchange="uploadFile(this.files[0])" /> <script> function uploadFile(file) { const formData = new FormData(); formData.append('file', file); // 直接上传,没有进度,没有断点续传 fetch('/api/upload', { method: 'POST', body: formData }); } </script> 

毒舌点评:这代码,我看了都替你的用户着急。原生上传大文件,你是想让用户等到天荒地老吗?

前端文件上传的正确姿势

1. 分片上传

// 正确姿势:分片上传 class ChunkUploader { constructor(file, options = {}) { this.file = file; this.chunkSize = options.chunkSize || 1024 * 1024; // 1MB this.chunks = Math.ceil(file.size / this.chunkSize); this.uploadedChunks = 0; } async upload() { const promises = []; for (let i = 0; i < this.chunks; i++) { const start = i * this.chunkSize; const end = Math.min(start + this.chunkSize, this.file.size); const chunk = this.file.slice(start, end); promises.push(this.uploadChunk(chunk, i)); } await Promise.all(promises); await this.mergeChunks(); } async uploadChunk(chunk, index) { const formData = new FormData(); formData.append('chunk', chunk); formData.append('index', index); formData.append('total', this.chunks); formData.append('filename', this.file.name); await fetch('/api/upload/chunk', { method: 'POST', body: formData }); this.uploadedChunks++; this.onProgress(this.uploadedChunks / this.chunks); } onProgress(progress) { console.log(`上传进度: ${(progress * 100).toFixed(2)}%`); } async mergeChunks() { await fetch('/api/upload/merge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: this.file.name, chunks: this.chunks }) }); } } // 使用 const uploader = new ChunkUploader(file, { chunkSize: 1024 * 1024 }); uploader.upload(); 

2. 断点续传

// 正确姿势:断点续传 class ResumableUploader { constructor(file) { this.file = file; this.chunkSize = 1024 * 1024; this.uploadedChunks = new Set(); } async init() { // 获取已上传的分片 const response = await fetch(`/api/upload/status?filename=${this.file.name}`); const { uploadedChunks } = await response.json(); this.uploadedChunks = new Set(uploadedChunks); } async upload() { const chunks = Math.ceil(this.file.size / this.chunkSize); for (let i = 0; i < chunks; i++) { if (this.uploadedChunks.has(i)) { console.log(`分片${i}已上传,跳过`); continue; } const start = i * this.chunkSize; const end = Math.min(start + this.chunkSize, this.file.size); const chunk = this.file.slice(start, end); await this.uploadChunk(chunk, i); this.uploadedChunks.add(i); // 保存进度到本地 localStorage.setItem('uploadProgress', JSON.stringify([...this.uploadedChunks])); } await this.mergeChunks(); localStorage.removeItem('uploadProgress'); } async uploadChunk(chunk, index) { // 上传逻辑... } } 

3. 拖拽上传

// 正确姿势:拖拽上传 import { useCallback } from 'react'; function DragUpload({ onUpload }) { const handleDrop = useCallback((e) => { e.preventDefault(); const files = Array.from(e.dataTransfer.files); files.forEach(file => onUpload(file)); }, [onUpload]); const handleDragOver = useCallback((e) => { e.preventDefault(); }, []); return ( <div className="drag-upload" onDrop={handleDrop} onDragOver={handleDragOver} > <p>拖拽文件到此处上传</p> <input type="file" multiple onChange={(e) => { Array.from(e.target.files).forEach(file => onUpload(file)); }} /> </div> ); } 

毒舌点评:早这么写,你的上传早就做好了。别告诉我你还在用原生input,那你还是趁早去用FTP吧。

实战技巧:文件上传指南

1. 上传优化策略

  1. 分片上传:大文件切分上传
  2. 断点续传:支持暂停恢复
  3. 并发控制:限制同时上传数量
  4. 进度显示:实时显示上传进度

2. 最佳实践

// ✅ 显示上传进度 const xhr = new XMLHttpRequest(); xhr.upload.onprogress = (e) => { const progress = (e.loaded / e.total) * 100; console.log(`${progress}%`); }; // ✅ 图片预览 const preview = URL.createObjectURL(file); // ✅ 文件类型检查 const allowedTypes = ['image/jpeg', 'image/png']; if (!allowedTypes.includes(file.type)) { alert('不支持的文件类型'); return; } 

最后想说的

文件上传不是小事,是用户体验的关键。别再只用input type=file了——优化一下,你的上传会更专业。

文件上传就像快递,原生input像平邮,优化后的上传像顺丰。别让用户等平邮,给他们顺丰的体验。

Read more

技术拆解:《从音频到动效:我是如何用 Web Audio API 拆解音乐的?》

技术拆解:《从音频到动效:我是如何用 Web Audio API 拆解音乐的?》

🎵 从音频到动效:我是如何用 Web Audio API 拆解音乐的? 「家人们谁懂啊!写完音乐可视化项目后,我终于搞懂了电脑是怎么『听懂』音乐的!今天就把最硬核的『音频解析』部分扒得干干净净,连代码带原理一起唠明白~」 做这个 HTML5 实时音频可视化项目时,参考了 ZEEKLOG 上《基于 HTML5 Canvas 与 Web Audio API 的实时音频可视化工具开发》这篇文章的核心技术框架,主要借鉴了 Web Audio API 中 AnalyserNode 的基础使用、FFT 频域转换的流程以及 Canvas 实时渲染的基础思路,帮我快速搭好了项目的底层骨架。 但在实际开发中,我在原基础上做了不少可视化效果的扩展和细节设计的打磨,新增了自己想要的可视化表现形式,也针对实际使用中的小问题做了定制化优化,让整个工具的视觉效果和使用体验更贴合自己的设计需求,下面具体说说我做的这些扩展和优化。 一、先看效果:

GLM-Image WebUI免配置教程:Gradio共享链接生成与内网穿透方案

GLM-Image WebUI免配置教程:Gradio共享链接生成与内网穿透方案 1. 项目简介与价值 智谱AI的GLM-Image是一个强大的文本到图像生成模型,能够根据文字描述创作出高质量的AI图像。但很多用户在本地部署后遇到了一个实际问题:生成的精美图片只能自己欣赏,无法方便地分享给朋友或同事。 这就是本文要解决的核心问题——如何在不进行复杂网络配置的情况下,让您的GLM-Image WebUI能够被其他人远程访问。我们将重点介绍两种简单实用的方法:Gradio自带的共享链接功能,以及更稳定的内网穿透方案。 无论您是设计师想要分享创作成果,还是开发者需要向团队成员演示AI生成效果,这篇文章都能帮您快速实现目标。 2. 环境准备与快速启动 在开始配置共享访问之前,请确保您的GLM-Image WebUI已经正常启动并运行。 2.1 检查服务状态 首先通过终端确认WebUI服务是否正常运行: # 检查服务进程 ps aux | grep gradio # 检查端口占用 netstat -tlnp | grep 7860 如果服务未启动,使用项目提供的启动脚本

零基础也能做网页?PyWebIO表单快速构建指南(新手必看)

第一章:PyWebIO表单快速构建入门 PyWebIO 是一个轻量级 Python 库,允许开发者通过函数式编程方式快速构建 Web 表单界面,无需编写前端代码。它特别适用于数据采集、简单交互工具或原型系统开发,能够将 Python 脚本直接转化为可交互的网页应用。 基础表单元素使用 PyWebIO 提供了多种内置输入组件,如文本框、下拉选择、复选框等,可通过 input() 和 select() 等函数调用。 # 示例:收集用户基本信息 from pywebio.input import * from pywebio.output import * info = input_group("用户信息", [ input('姓名', name='name'

一个前端一天可以做多少页面?

一个前端一天能做多少页面?这个问题没有固定答案,但在真实项目中,绝大多数情况下是 0.5–3 个页面/天(中等复杂度),极端情况下能到 5–10+ 个(纯切图/高度重复/用 AI 工具)。 下面按 2025–2026 年国内互联网/外包/大厂/中小厂的真实反馈和观察,给你一个务实的分层对比表(基于知乎、掘金、V2EX、Reddit、脉脉等高赞讨论共识): 页面类型 & 复杂度典型日完成量(经验中级前端,8小时有效编码)影响因素 & 真实案例备注对应场景 / 项目类型极简静态页(纯 HTML+CSS,无交互)3–8 个复制粘贴模板 + 改颜色/文字,