写一个巧妙的上传按钮

2,292 阅读2分钟

最近在做文件上传功能,需求是在每次选择文件时获取文件并进行类型和大小的校验,并且不能用原生的input按钮(太丑)。我们知道input的onchange事件只会在选择的文件发生改变时触发,如果两次选了同一个文件怎么办?原生的input元素该怎么美化呢?于是开始了研究之旅。

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

先思考UI的问题

先考虑第一个问题:原生input上传标签的美观问题。

这个无法通过样式来解决,只能隐藏或特殊处理:

  • 透明度设置为0,把它贴到一个按钮上。这样点击那个按钮也就可以直接打开文件选择框了
  • 隐藏它,点击其他按钮后以编程的形式触发input的点击事件(click())

第一种方法,首先type="file"的input元素的cursor:pointer; 属性有些特殊:鼠标放到选择文件的盒子上没有效果,只有放到提示文字上才显示: image.png 这样由于元素叠在按钮的上层,导致无法实现鼠标小手效果(这很重要好吗!),如果不在乎可以采纳,反正我在乎。

第二种方法兼容性良好,采用中转的方式:

<button>上传</button>
<input type="file" style="display:none;"/>
<script>
document.querySelector("button").addEventListener('click',()=>{
    document.querySelector("input[type='file']").click()
})
</script>

点击发现可以正常打开文件选择框:

image.png

再考虑如何获取每次选择的文件

主要考虑如何在选择的文件不变的情况下获取所选的文件。

这其实是个伪命题,注册了onchange事件监听就是只能在发生变化时触发,不是吗?

灵机一动:可以把每次选择文件后,把文件用变量存起来,然后再将input标签清空(input.value = ""),这样下次文件选择时自然会触发change事件了,既是是勾选了跟上次同样的文件。

然而测试发现,这样不ok,清空以后变量里的文件也不见了。这条路行不通!

仔细想了想,之前用过elementUI的组件,他们的上传组件可是支持的哦,有图有真相:

image.png 上图是我连续三次选择了同一份文件,人家支持的很好.

于是关注点来到了他们是怎么做到的?于是扒扒人家的源码,在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>

效果图(连续三次选择同一文件):

image.png

最后

感谢阅读,希望本文对你有所帮助!