zoukankan      html  css  js  c++  java
  • input file 文件选择的取消事件

    示例

    原生的input标签无法监听取消事件, 我们通过对容器的blur事件和click事件, 以及input的change事件, 三者结合进行判断:

    <!DOCTYPE html>
    <html lang="zh">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>选择文件的取消事件</title>
        <style>
            * {
                font-size: large;
            }
        </style>
    </head>
    
    <body>
        <div>
            <button id="btn">选择文件</button>
        </div>
        <script>
            addFileSelect(
                btn,
                /* 选择文件事件 */
                (input) => {
                    alert('您选择了文件: ' + input.files[0].name);
                },
                /* 取消选择事件 */
                () => {
                    alert('您取消了文件选择');
                }
            );
    
            /**
             * 为容器添加文件选择事件, 容器通常是一个按钮
             */
            function addFileSelect(container, onselect, oncancel) {
                // <input type="file">
                let input = document.createElement('input'); input.type = 'file';
                // states
                let waiting = false; // 是否尚在等待选择文件
                let clicked = false; // 按钮是否被点击
                container.addEventListener('click', () => {
                    clicked = true; // 按钮被点击
                    input.click(); // 弹窗
                    waiting = true; // 等待用户选择文件, 此时按钮会失去焦点
                });
                container.addEventListener('blur', () => {
                    if (clicked && waiting) {
                        clicked = false; // 用户点击容器后, 容器会失去一次焦点, 此时处于waiting状态
                        // waiting没有被input的change事件置为false, 却触发了blur的失焦事件
                    } else if (waiting) { // 容器再次失去焦点, 仍旧处于waiting状态, 断言用户取消了选择
                        console.log('blur事件测试到用户取消了选择');
                        oncancel?.();
                    }
                });
                input.addEventListener('change', () => {
                    waiting = false; // 检测到用户选择了文件
                    if (input.value === '') { // 此时, 用户肯定点击了取消按钮, 否则value不会变为空串, 而且之前肯定选择过文件, 否则不会触发change事件
                        console.log('change事件感知到用户取消了选择');
                        oncancel?.();
                    } else {
                        onselect?.(input);
                    }
                });
            }
        </script>
    </body>
    
    </html>
    

    算法改进: blur的对立事件: focus

    在回忆上午完成的代码时, 我发现我们需要手动点击容器之外的UI使其产生blur事件才能检测到取消事件, 但是弹窗时由于容器失去焦点导致已经产生过一次该事件了呀?
    原来是系统自动将焦点放到容器上了! 当我们的文件选择框无论因为以下哪种原因关闭的时候, 容器都会自动获得blur事件:

    • 用户选择了一个文件
    • 用户取消了选择文件
      所以我们是可以立即判断的! 加入容器的焦点事件, 发现焦点事件先于点击事件之前触发.
      但是change事件可能排在最后!
    focus => click => blur => 弹窗 => (A or B) => 弹窗关闭 => focus =>? change
    

    最关键的点是什么? 我也很混乱

    但是没有关系, 我还是找到了关键点, 由于change事件可能排在最后, 因此要在弹窗关闭 => focus中判断时不能依赖input的事件.
    但是此时input的值肯定已经发生了变化, 如果用户取消了选择, 那么input的值肯定是空串???

    由于事件过于复杂, 实际上我们只关心点击之后的事情, 所以在容器的click事情中添加后续的事件监听器. 事件全部只监听一次:

    容器点击事件 => 容器失去焦点 => 容器获得焦点 =>? input改变事件
    

    只有input的change事件是不稳定的.

    解决方案

             /**
             * 为容器添加文件选择事件, 容器通常是一个按钮
             */
            function addFileSelect(container, onselect, oncancel) {
                container.addEventListener('click', () => {
                    let input = document.createElement('input'); input.type='file';
                    input.click();
                    let selected = false;
                    let onchange = null; // 取消选择时不会触发change事件, 需要手动移除监听器
    
                    container.addEventListener('focus', () => {
                        console.log(input.value); // 大概先于onchange事件100ms执行, 所以一定是空串
                        // 当取消选择时则不会触发onchange事件
                        let close_time = new Date(); // 记录弹窗关闭的时间
                        // 轮询
                        (function loop() {
                            let crt_time = new Date(); // 查询时间
                            if (selected) {
                                onselect?.(input);
                            } else if (crt_time - close_time > 1000) { // 该时间不确保一定可以触发change事件
                                input.removeEventListener('change', onchange);
                                oncancel?.();
                            } else {
                                setTimeout(loop, 20);
                            };
                        })();
                    }, { once: true });
    
                    input.addEventListener('change', onchange = () => {
                        console.log('change');
                        selected = true;
                    }, { once: true });
                });
            }
    

    我们甚至可以丢弃change事件, 同时基于轮询次数判断取消, 而不是基于时间:

            /**
             * 为容器添加文件选择事件, 容器通常是一个按钮
             */
            function addFileSelect(container, onselect, oncancel) {
                container.addEventListener('click', () => {
                    let input = document.createElement('input'); input.type='file';
                    input.click();
    
                    container.addEventListener('focus', () => {
                        console.log(input.value); // 大概先于onchange事件100ms执行, 所以一定是空串
                        let loop_count = 0; // 轮询次数
                        // 轮询
                        (function loop() {
                            if (input.value !== '') { // 不需要change事件
                                onselect?.(input);
                            } else if (++loop_count >= 10) { // 基于轮询次数的判断
                                oncancel?.();
                            } else { // 暂时无法判断, 继续轮询
                                setTimeout(loop, 20);
                            };
                        })();
                    }, { once: true });
                });
            }
    
  • 相关阅读:
    【记录】百度统计监控博客园
    【织梦】网站地图创建和美化
    【IDE】JRebel热部署实现
    【字体图标】 Font Awesome字体图标如何使用?
    【Eureka】springCloud项目搭建
    java 服务定期卡顿、卡死,服务在运行没挂,日志疯狂打印,接口不能用
    idea springboot 无法启动 Unable to start EmbeddedWebApplicationContext
    POI导出xlsx
    mysql decimal设置默认值0 无效,设置后自动变为null(通过Navicat可视化工具操作)
    Log file ./ib_logfile2 is of different size 268435456 bytes than other log files 50331648 bytes!
  • 原文地址:https://www.cnblogs.com/develon/p/13718155.html
Copyright © 2011-2022 走看看