最近在做文件上传功能,需求是在每次选择文件时获取文件并进行类型和大小的校验,并且不能用原生的input按钮(太丑)。我们知道input的onchange事件只会在选择的文件发生改变时触发,如果两次选了同一个文件怎么办?原生的input元素该怎么美化呢?于是开始了研究之旅。
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
先思考UI的问题
先考虑第一个问题:原生input上传标签的美观问题。
这个无法通过样式来解决,只能隐藏或特殊处理:
- 透明度设置为0,把它贴到一个按钮上。这样点击那个按钮也就可以直接打开文件选择框了
- 隐藏它,点击其他按钮后以编程的形式触发input的点击事件(click())
第一种方法,首先type="file"的input元素的cursor:pointer;
属性有些特殊:鼠标放到选择文件的盒子上没有效果,只有放到提示文字上才显示:
这样由于元素叠在按钮的上层,导致无法实现鼠标小手效果(这很重要好吗!),如果不在乎可以采纳,反正我在乎。
第二种方法兼容性良好,采用中转的方式:
<button>上传</button>
<input type="file" style="display:none;"/>
<script>
document.querySelector("button").addEventListener('click',()=>{
document.querySelector("input[type='file']").click()
})
</script>
点击发现可以正常打开文件选择框:
再考虑如何获取每次选择的文件
主要考虑如何在选择的文件不变的情况下获取所选的文件。
这其实是个伪命题,注册了onchange
事件监听就是只能在发生变化时触发,不是吗?
灵机一动:可以把每次选择文件后,把文件用变量存起来,然后再将input标签清空(input.value = ""
),这样下次文件选择时自然会触发change事件了,既是是勾选了跟上次同样的文件。
然而测试发现,这样不ok,清空以后变量里的文件也不见了。这条路行不通!
仔细想了想,之前用过elementUI
的组件,他们的上传组件可是支持的哦,有图有真相:
上图是我连续三次选择了同一份文件,人家支持的很好.
于是关注点来到了他们是怎么做到的?于是扒扒人家的源码,在element-plus/packages/components/upload/src/upload.vue
里有这么几行代码:
<div :class="['el-upload', `el-upload--${listType}`]"
tabindex="0"
@click="handleClick"
@keydown.self.enter.space="handleKeydown">
//省略...
</div>
function handleClick() {
if (!props.disabled) {
//清空input元素的file内容
inputRef.value.value = null
inputRef.value.click()
}
}
所以,人家是在点击外层的按钮时,先将input的值(里面的file)清空,然后让用户重新选择,如果不点击input里的文件会一直保留。这很合理。
于是根据这个技巧最终代码整理如下:
<button>上传</button>
<input type="file" style="display:none;"/>
<script>
document.querySelector("button").addEventListener('click',()=>{
document.querySelector("input[type='file']").value = "";
document.querySelector("input[type='file']").click()
})
document.querySelector("input[type='file']").addEventListener('change',(e)=>{
console.log(e.target.files)
})
</script>
效果图(连续三次选择同一文件):
最后
感谢阅读,希望本文对你有所帮助!