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 });
                });
            }
    
  • 相关阅读:
    LeetCode OJ String to Integer (atoi) 字符串转数字
    HDU 1005 Number Sequence(AC代码)
    HDU 1004 Let the Balloon Rise(AC代码)
    HDU 1003 Max Sum(AC代码)
    012 Integer to Roman 整数转换成罗马数字
    011 Container With Most Water 盛最多水的容器
    010 Regular Expression Matching 正则表达式匹配
    007 Reverse Integer 旋转整数
    006 ZigZag Conversion
    005 Longest Palindromic Substring 最长回文子串
  • 原文地址:https://www.cnblogs.com/develon/p/13718155.html
Copyright © 2011-2022 走看看