customElements.define()注册上传组件必须继承HTMLElement并调用super(),否则无法实例化;需在connectedCallback中attachShadow并插入结构样式;标签名须含短横线;属性响应需observedAttributes与attributeChangedCallback配合;拖拽文件夹须用webkitGetAsEntry递归读取,且仅Chromium内核完整支持。

用 customElements.define() 注册上传组件类
必须继承 HTMLElement,并在构造函数里调用 super();否则组件无法被 DOM 正确实例化。不写 connectedCallback 就没法在挂载时自动渲染 UI 或绑定事件——比如拖拽监听、点击触发隐藏 <input type="file">。
常见错误是把模板直接拼接进 innerHTML,但没处理好 Shadow DOM 隔离:样式泄露或事件监听失效。正确做法是在 connectedCallback 中调用 this.attachShadow({ mode: 'open' }),再往 shadowRoot 里插入结构和样式。
-
customElements.define('file-uploader', FileUploader)中的标签名必须含短横线(如file-uploader),否则浏览器会报DOMException: Failed to execute 'define' on 'CustomElementRegistry' - 如果组件需要响应
accept、multiple、webkitdirectory等属性,得在static get observedAttributes()里声明,并在attributeChangedCallback中同步更新内部<input>的对应属性
拖拽文件夹时需显式启用 webkitdirectory 并处理递归读取
仅给 <input type="file"> 加 webkitdirectory 属性,只能在点击触发时选中文件夹;拖拽场景下,浏览器不会自动识别目录结构——必须监听 drop 事件,对每个 DataTransferItem 调用 webkitGetAsEntry(),再用 createReader().readEntries() 递归遍历子项。
容易忽略的是:Firefox 不支持 webkitdirectory,也不提供等效 API;Safari 对 webkitGetAsEntry() 的支持也有限制(比如不能读取根目录外的路径)。所以实际封装时,得 fallback 到只支持单层文件拖拽,或明确标注“仅 Chromium 内核支持完整文件夹上传”。
立即学习“前端免费学习笔记(深入)”;
- 拖拽进入区域时,必须阻止
dragover默认行为(e.preventDefault()),否则drop事件根本不会触发 -
FileList和DataTransferItemList是不同对象:前者来自点击选择,后者来自拖拽;不能直接用Array.from(files)处理后者,得用item.getAsFile()或item.webkitGetAsEntry()
accept 属性在自定义组件里要双向同步
原生 <input type="file"> 的 accept 只影响文件选择器弹窗的过滤,但用户仍可手动修改文件类型后缀绕过——所以组件内必须配合 JS 校验。更关键的是:自定义组件的 accept 属性值(如 accept=".pdf,.jpg")需实时同步到内部隐藏 <input> 上,否则点击触发时根本不起作用。
注意 MIME 类型写法差异:accept="image/*" 匹配所有图片,但 accept=".png" 只匹配扩展名为 .png 的文件;两者混合使用(如 accept="image/*,.pdf")在部分浏览器中可能失效,建议统一用扩展名列表。
- Vue/React 项目里传入
accept是字符串,但组件内部要转成数组做校验,避免split(',')后留空格导致匹配失败 - 校验文件类型不能只看
file.name后缀,必须结合file.type(如application/pdf),因为用户可能改后缀但没改内容
隐藏 <input type="file"> 后触发点击的兼容性陷阱
用 inputRef.click() 触发文件选择是最常用方式,但它在 iOS Safari 和部分 Android WebView 中无效——这些环境要求用户真实交互(tap/click)才能激活文件输入。解决方案是把隐藏 input 的 opacity: 0 + position: absolute 覆盖在自定义按钮上,确保点击区域与 input 重叠,而不是靠 JS 模拟点击。
另一个坑是:Chrome 98+ 开始限制非用户手势触发的 click(),比如在 setTimeout 或异步回调里调用会静默失败。必须保证调用栈里有用户事件(如 onclick、ontouchstart)的直接链路。
- 不要用
display: none或visibility: hidden隐藏 input,这会让它完全不可交互;要用opacity: 0+pointer-events: none配合定位覆盖 - 移动端双击放大页面时,可能意外触发 input 的 focus,导致键盘弹出;建议加
input { user-select: none; }防止误操作



















