取消input type="file"的事件

67

我正在使用标准的文件上传输入框,并寻找一种方法来将函数附加到事件上,当用户点击/按下“取消”按钮(或从“选择文件”对话框中退出)时触发。

我找不到在所有浏览器和平台上都能一致运行的事件。

我已经阅读了这个问题的答案:捕获input type=file上的cancel事件, 但它们并不起作用,因为大多数浏览器在取消选择文件对话框后不会触发change事件。

我正在寻找一个纯JS解决方案,但也接受jQuery解决方案。

有没有人成功解决这个问题?


由于不是所有浏览器都执行.change(),所以应该寻找被调用的事件。有没有特定的浏览器您希望与之配合使用? - Twisty
在 https://jsfiddle.net/Twisty/j18td9cs/ 上进行了一些测试,在 FF 中,由于“取消”是浏览器对话框的元素,我只能检查是否选择了文件。我注意到如果我选择一个文件,然后再浏览第二次,然后点击取消,它会保留文件值...所以这里没有帮助。可以在页面上创建自己的取消按钮。尝试查看是否更新了某些内容或返回是否选择了取消,例如使用 confirm()prompt() - Twisty
这个回答解决了你的问题吗?如何检测文件输入框上的取消按钮被点击? - leonheess
17个回答

37

一些研究表明,无法检测在文件选择对话框窗口中选择“取消”时的操作。您可以使用 onchangeonblur 来检查是否已选择文件或是否已将某些内容添加到输入 value 中。

代码示例如下:https://jsfiddle.net/Twisty/j18td9cs/

HTML

<form>
  Select File:
  <input type="file" name="test1" id="testFile" />
  <button type="reset" id="pseudoCancel">
    Cancel
  </button>
</form>

JavaScript

var inputElement = document.getElementById("testFile");
var cancelButton = document.getElementById("pseudoCancel");
var numFiles = 0;

inputElement.onclick = function(event) {
  var target = event.target || event.srcElement;
  console.log(target, "clicked.");
  console.log(event);
  if (target.value.length == 0) {
    console.log("Suspect Cancel was hit, no files selected.");
    cancelButton.onclick();
  } else {
    console.log("File selected: ", target.value);
    numFiles = target.files.length;
  }
}

inputElement.onchange = function(event) {
  var target = event.target || event.srcElement;
  console.log(target, "changed.");
  console.log(event);
  if (target.value.length == 0) {
    console.log("Suspect Cancel was hit, no files selected.");
    if (numFiles == target.files.length) {
      cancelButton.onclick();
    }
  } else {
    console.log("File selected: ", target.value);
    numFiles = target.files.length;
  }
}

inputElement.onblur = function(event) {
  var target = event.target || event.srcElement;
  console.log(target, "changed.");
  console.log(event);
  if (target.value.length == 0) {
    console.log("Suspect Cancel was hit, no files selected.");
    if (numFiles == target.files.length) {
      cancelButton.onclick();
    }
  } else {
    console.log("File selected: ", target.value);
    numFiles = target.files.length;
  }
}


cancelButton.onclick = function(event) {
  console.log("Pseudo Cancel button clicked.");
}

我建议制作自己的取消或重置按钮,以重置表单或清除输入的值。


这在Firefox上不起作用。:( 如果之前没有选择文件,一旦我点击“浏览”按钮,它就会记录到控制台中怀疑取消被点击,未选择任何文件。伪取消按钮已点击。。在浏览对话框中单击取消时,什么也不会发生。如果之前选择了一个文件,一旦我点击“浏览”,它就会记录下我选择了先前的文件,即使我仍然在对话框中。 - Noitidart
我在FF中写了这个。需要再次检查。你有测试代码的fiddle吗? - Twisty
感谢您的快速回复,我只是使用了上面的fiddle,没有写任何单独的内容。 - Noitidart
@Noitidart 看起来 FF 会先触发 focus,然后是 blur,最后如果用户选择了文件,则会触发 change。因此,这里的 clickblur 事件会中断逻辑。更新后的 jQuery:https://jsfiddle.net/Twisty/btf1m9nc/ - Twisty
浏览器可能也会出现看不见的输入元素问题。在我的情况下,我可以使用display none或visibility hidden隐藏我的输入元素,并通过带标签的按钮触发对话框。至少在Mac上的Chrome浏览器中,onfocus事件仍将被触发。但是,在智能手机上进行测试(iPhone或HTC U11上的Chrome浏览器),发现它们不会为这种特殊情况触发onfocus事件。这可能不仅仅与隐藏的输入有关,而是因为我通过带标签的按钮触发对话框,而不是直接触发输入元素本身。 - Chaoste

16

我有一个完美的解决方案。

聚焦事件将在更改事件之前执行。

因此,我需要使用setTimeout方法使得focus方法比change方法晚执行。

const createUpload = () => {
    let lock = false
    return new Promise((resolve, reject) => {
        // create input file
        const el = document.createElement('input')
        el.id = +new Date()
        el.style.display = 'none'
        el.setAttribute('type', 'file')
        document.body.appendChild(el)

        el.addEventListener('change', () => {
            lock = true
            const file = el.files[0]
            
            resolve(file)
            // remove dom
            document.body.removeChild(document.getElementById(el.id))
        }, { once: true })

        // file blur
        window.addEventListener('focus', () => {
            setTimeout(() => {
                if (!lock && document.getElementById(el.id)) {
                    reject(new Error('onblur'))
                    // remove dom
                    document.body.removeChild(document.getElementById(el.id))
                }
            }, 300)
        }, { once: true })

        // open file select box
        el.click()
    })
}



try {
    const file = createUpload()
    console.log(file)
} catch(e) {
    console.log(e.message) // onblur
}

window 上的 focus 事件非常完美,您可以将整个内容包装在一个 Promise 中,并在用户取消文件选择时拒绝 Promise。 - Akash Kava
我猜如果用户从另一个标签重新进入,也会触发删除? - I have 10 fingers
@AkashKava 你能提供那个代码吗? - undefined

9

现在在这种情况下触发了一个cancel事件,自大约一年前起(对于Chrome和Firefox而言是两年),所有三个主要的浏览器引擎都支持

document.querySelector("input").addEventListener("cancel", (evt) => {
  console.log("You closed the file picker dialog without selecting a file.");
});
<input type="file">

很遗憾,我们无法使用oncancel全局处理程序来检测此事件,因为这可能指向在<dialog>元素上触发的同名事件,从而产生误报。据我所知,目前没有简单的检测方法。

1
它运作良好且优雅,顺便说一句,这是在2023年才添加的,希望这个答案能够置顶。 - undefined

3
在您的“inputElement.onclick”中,您应该设置一个标志(在我的情况下,我设置了window.inputFileTrueClosed = true),以便在按下“取消”按钮后检测窗口获得焦点的情况。以下代码可以检测窗口是否再次获得焦点:这意味着“取消”按钮可能已经被按下:
var isChrome = !!window.chrome;

    window.addEventListener('focus', function (e) {
       
    
    if(window.inputFileTrueClosed != false){
      
        if(isChrome == true){
           setTimeout(
                    function() 
                    {
                         if(window.inputFileTrueClosed != false){
   //if it is Chrome we have to wait because file.change(function(){... comes after "the window gets the focus"
                         window.inputFileTrueClosed = false; 
                         }
                    }, 1000);
}
else
{
        // if it is NOT Chrome (ex.Safari) do something when the "cancel" button is pressed.

        window.inputFileTrueClosed = false;
        
    }
}
})

很明显,窗口会获得许多其他事件的焦点,但使用该标志可以选择需要检测的事件。

2
当我们选择文件时,将触发以下事件 -
情形1:当单击“选择文件”后又单击“取消”
焦点事件 Focus value=""
单击事件 Click value=""
失去焦点事件 Blur value=""
焦点事件 Focus value=""
失去焦点事件 Blur value=""(当用户单击某处)
情形2:当文件被选中时 -
焦点事件 Focus value=""
单击事件 Click value=""
失去焦点事件 Blur value=""
焦点事件 Focus value=""
更改事件 Change value="filevalue"
失去焦点事件 Blur value="filevalue"
焦点事件 Focus value="filevalue"
失去焦点事件 Blur value="filevalue"(当用户单击某处)
我们可以看到,当模糊事件(最后一个事件)在没有文件值的焦点事件之后被调用时,这意味着单击了取消按钮。
我的情况是在文件加载时将提交按钮文本更改为“请稍候”
当前事件变量用于保存当前事件值,即单击或聚焦。如果当前事件=焦点且文件值为空,则表示单击了取消按钮。

Javascript

var currentEvent = "focus";

function onFileBrowse() {    
    var vtbFile = $('#uploadFile')[0].files[0];

    if (vtbFile != undefined) {
        var extension = vtbFile.name.split('.').pop().toLowerCase();
        var valError = "";

        if (extension === 'xlsx' || extension === 'xlsb' || extension === 'csv') {
            if (vtbFile.size === 0)
                valError = "File '" + vtbFile.name + "' is empty";
        }
        else
            valError = "Extension '" + extension + "' is not supported.";

        if (valError !== "") {            
            alert("There was an issue validating the file. " + valError, 20000);
        }        
    }
    //hide Indicator
    var buttonUpload = document.getElementById('btnUploadTB');
    buttonUpload.innerText = "Submit";
};


function fileClick() {
    //show Indicator
    var buttonUpload = document.getElementById('btnUploadTB');
    buttonUpload.innerText = "Please wait..";
    
    document.getElementById('uploadFile').value = null;   
    currentEvent = "click";
}
function fileFocus() {
    currentEvent = "focus";
}

function fileBlur() {
    
    if (!document.getElementById('uploadFile').value && currentEvent == "focus") {
        console.log('blur' + 'change to submit');
        //hide Indicator
        var buttonUpload = document.getElementById('btnUploadTB');
        buttonUpload.innerText = "Submit";
    }
}
HTML

<input class="k-button k-upload-button" type="file" id="uploadFile" name="uploadFile"
    accept=".csv,.xlsx,.xlsb" onChange='onFileBrowse()' onclick="fileClick()" onfocus="fileFocus()" onblur="fileBlur()" />

<button id="btnUploadTB" type="button" class="btn btn-default" onclick="uploadTBFile()" style="width:28%;margin-left: 3px;">Submit</button>


由于事件之间存在不同的延迟,因此这种解决方法就像是“通知”对话框发生了一些延迟的更改(在我的情况下为500秒 - 这很多并使UI滞后),但在这种情况下,需要较少的代码。 - Antoniossss

1

这段代码不会监听取消按钮是否被点击,但对我来说它是有效的。每次输入值发生变化时,它都会检查是否有文件附加在其上。从那里开始,我基本上可以做任何我需要做的事情。

$("#imageUpload").change(function() {
  if (this.files && this.files[0]) {
    console.log("Has file selected");
  } else {
    console.log("No file selected");
  }
});
<input type="file" id="imageUpload" />

<script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=" crossorigin="anonymous"></script>


0
In react , on Change event of input field , the event has no files on cancel event, we can proceed with this assumption. Cancel event will be captured.

  handleChange = (event) => {
    console.log(event);
    console.log(event.target.files[0]);
    this.setState({
      tableDataResult: false,
    });
    if(event.target.files[0]){
      this.setState({
        csvfile: event.target.files[0],
      });
    }else{
//Cancel event called
      console.log("false");
      this.setState({
        csvfile: oldValue,
      });
    }
  };

<input
                                        style={{
                                          width: "450px",
                                          marginLeft: "15px",
                                          marginTop: "5px",
                                        }}
                                        className="csv-input"
                                        type="file"
                                        ref={(input) => {
                                          this.filesInput = input;
                                        }}
                                        name="file"
                                        placeholder={null}
                                        onChange={this.handleChange}
                                      />

0
我在使用jQuery实现图像上传表单的自动提交后,遇到了类似的问题。如果用户取消了对话框,它会发送一个空白值。需要在同一脚本中检测此空值即可解决问题:
$('#upload-portrait-input').on('change', function(){
   if ( $(this).val() != '' )
    {
    $('#portraitForm').submit();
    }
   else { // do something when the user clicks cancel
    }
 });

0

这解决了我的问题,虽然除了Google Chrome之外没有在其他浏览器上进行测试:

$("#fileUpload").on("change", function (event) {
   if (!$(this)[0].files[0]) {
      event.preventDefault();
      return;
   };

   // Else do something here.
});

$(this)[0] 是将jQuery对象转换为普通JavaScript对象。当您点击取消按钮时,files的数组长度为零。您可以使用$(this)[0].files.length < 1,或检查$(this)[0].files[0]是否为undefined


我喜欢这个答案的编辑队列已经满了:D 我感同身受,我的眼睛也很疼。$(this)[0] - Jiří

0

我需要在用户浏览文件资源管理器(活动/非活动)时以不同的方式设置我的文件上传样式。只有clickblur事件。请注意,当用户单击取消按钮时,需要在文件上传之外(在我的情况下是button)单击。

这是我针对Angular的解决方案,但我想任何人都可以掌握这个想法,并使用您喜欢的框架/库/Vanilla JS进行调整。

<!-- HTML - Angular -->

<input hidden type="file" #fileInput (change)="onFileUpload($event.target.files)">
<button (click)="initBrowsingFlags(); fileInput.click()" (blur)="onBlur()" [class.browsing]="browsing">Upload</button>

// Typescript - Angular

/** Whether the user is browsing the file explorer. */
browsing = false;

/** 
 * If a 2nd `blur` event is emitted while {@link browsing} is still true, it means that the user
 * clicked the Cancel button on the file explorer.
 * */
alreadyOneBlurEventWhileBrowsing = false;

onFileUpload(files: FileList) {
  // ...
  this.resetBrowsingFlags();
}

onBlur() {
  if (!this.browsing) return;

  if (this.onCancelClickWhileBrowsing) {
    this.resetBrowsingFlags();
  } else {
    this.onCancelClickWhileBrowsing = true;
  }
}

initBrowsingFlags() {
  this.browsing = true;
  this.alreadyOneBlurEventWhileBrowsing= false;
}

resetBrowsingFlags() {
  this.browsing = false;
  this.alreadyOneBlurEventWhileBrowsing= false;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接