Html 如何检测何时单击文件输入取消?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4628544/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-29 06:01:58  来源:igfitidea点击:

How to detect when cancel is clicked on file input?

htmlfileinput

提问by Ppp

How can I detect when the user cancels a file input using an html file input?

如何检测用户何时使用 html 文件输入取消文件输入?

onChange lets me detect when they choose a file, but I would also like to know when they cancel (close the file choose dialog without selecting anything).

onChange 让我检测他们何时选择文件,但我也想知道他们何时取消(关闭文件选择对话框而不选择任何内容)。

回答by Shiboe

While not a direct solution, and also bad in that it only (as far as I've tested) works with onfocus (requiring a pretty limiting event blocking) you can achieve it with the following:

虽然不是直接的解决方案,而且也很糟糕,因为它仅(据我测试)与 onfocus 一起使用(需要非常有限的事件阻塞),您可以通过以下方式实现它:

document.body.onfocus = function(){ /*rock it*/ }

What's nice about this, is that you can attach/detach it in time with the file event, and it also seems to work fine with hidden inputs (a definite perk if you're using a visual workaround for the crappy default input type='file'). After that, you just need to figure out if the input value changed.

这样做的好处是,您可以在文件事件中及时附加/分离它,并且它似乎也适用于隐藏的输入(如果您对蹩脚的默认输入类型 =文件')。之后,您只需要弄清楚输入值是否发生了变化。

An example:

一个例子:

var godzilla = document.getElementById('godzilla')

godzilla.onclick = charge

function charge()
{
    document.body.onfocus = roar
    console.log('chargin')
}

function roar()
{
    if(godzilla.value.length) alert('ROAR! FILES!')
    else alert('*empty wheeze*')
    document.body.onfocus = null
    console.log('depleted')
}

See it in action: http://jsfiddle.net/Shiboe/yuK3r/6/

看到它在行动:http: //jsfiddle.net/Shiboe/yuK3r/6/

Sadly, it only seems to work on webkit browsers. Maybe someone else can figure out the firefox/IE solution

可悲的是,它似乎只适用于 webkit 浏览器。也许其他人可以找出firefox/IE解决方案

回答by Oded

You can't.

你不能。

The result of the file dialog is not exposed to the browser.

文件对话框的结果不会暴露给浏览器。

回答by Douglas Parker

So I'll throw my hat into this question since I came up with a novel solution. I have a Progressive Web App which allows users to capture photos and videos and upload them. We use WebRTC when possible, but fall back to HTML5 file pickers for devices with less support *cough Safari cough*. If you're working specifically on an Android/iOS mobile web application which uses the native camera to capture photos/videos directly, then this is the best solution I have come across.

所以我想出了一个新颖的解决方案,所以我会提出这个问题。我有一个渐进式 Web 应用程序,它允许用户捕捉照片和视频并上传它们。我们在可能的情况下使用 WebRTC,但对于支持较少 *cough Safari 咳嗽 * 的设备,我们会回退到 HTML5 文件选择器。如果您专门在使用原生相机直接捕获照片/视频的 Android/iOS 移动 Web 应用程序上工作,那么这是我遇到的最佳解决方案。

The crux of this problem is that when the page loads, the fileis null, but then when the user opens the dialog and presses "Cancel", the fileis still null, hence it did not "change", so no "change" event is triggered. For desktops, this isn't too bad because most desktop UI's aren't dependent on knowing when a cancel is invoked, but mobile UI's which bring up the camera to capture a photo/video are verydependent on knowing when a cancel is pressed.

这个问题的症结在于,当页面加载时,fileis null,但是当用户打开对话框并按下“取消”时,file仍然是null,因此它没有“更改”,因此不会触发“更改”事件。对于台式机来说,这还不错,因为大多数台式机 UI 并不依赖于知道何时调用取消,但是启动相机以捕获照片/视频的移动 UI非常依赖于知道何时按下取消。

I originally used the document.body.onfocusevent to detect when the user returned from the file picker, and this worked for most devices, but iOS 11.3 broke it as that event is not triggered.

我最初使用该document.body.onfocus事件来检测用户何时从文件选择器返回,这适用于大多数设备,但 iOS 11.3 由于未触发该事件而破坏了它。

Concept

概念

My solution to this is *shudder* to measure CPU timing to determine if the page is currently in the foreground or the background. On mobile devices, processing time is given to the app currently in the foreground. When a camera is visible it will steal CPU time and deprioritize the browser. All we need to do is measure how much processing time our page is given, when camera launches our available time will drop drastically. When the camera is dismissed (either cancelled or otherwise), our available time spike back up.

我对此的解决方案是 *shudder* 测量 CPU 时间以确定页面当前是在前台还是在后台。在移动设备上,处理时间分配给当前处于前台的应用程序。当摄像头可见时,它将窃取 CPU 时间并降低浏览器的优先级。我们需要做的就是测量给我们的页面多少处理时间,当相机启动时,我们的可用时间将急剧下降。当相机被关闭(取消或以其他方式)时,我们的可用时间会重新增加。

Implementation

执行

We can measure CPU timing by using setTimeout()to invoke a callback in X milliseconds, and then measure how long it took to actually invoke it. The browser will never invoke it exactlyafter X milliseconds, but if it is reasonable close then we must be in the foreground. If the browser is very far away (over 10x slower than requested) then we must be in the background. A basic implementation of this is like so:

我们可以通过使用setTimeout()在 X 毫秒内调用回调来测量 CPU 时序,然后测量实际调用它需要多长时间。该浏览器将不会调用它究竟X毫秒后,但如果它是合理的收盘价,那么我们必须在前台。如果浏览器距离很远(比请求慢 10 倍以上),那么我们必须在后台。一个基本的实现是这样的:

function waitForCameraDismiss() {
  const REQUESTED_DELAY_MS = 25;
  const ALLOWED_MARGIN_OF_ERROR_MS = 25;
  const MAX_REASONABLE_DELAY_MS =
      REQUESTED_DELAY_MS + ALLOWED_MARGIN_OF_ERROR_MS;
  const MAX_TRIALS_TO_RECORD = 10;

  const triggerDelays = [];
  let lastTriggerTime = Date.now();

  return new Promise((resolve) => {
    const evtTimer = () => {
      // Add the time since the last run
      const now = Date.now();
      triggerDelays.push(now - lastTriggerTime);
      lastTriggerTime = now;

      // Wait until we have enough trials before interpreting them.
      if (triggerDelays.length < MAX_TRIALS_TO_RECORD) {
        window.setTimeout(evtTimer, REQUESTED_DELAY_MS);
        return;
      }

      // Only maintain the last few event delays as trials so as not
      // to penalize a long time in the camera and to avoid exploding
      // memory.
      if (triggerDelays.length > MAX_TRIALS_TO_RECORD) {
        triggerDelays.shift();
      }

      // Compute the average of all trials. If it is outside the
      // acceptable margin of error, then the user must have the
      // camera open. If it is within the margin of error, then the
      // user must have dismissed the camera and returned to the page.
      const averageDelay =
          triggerDelays.reduce((l, r) => l + r) / triggerDelays.length
      if (averageDelay < MAX_REASONABLE_DELAY_MS) {
        // Beyond any reasonable doubt, the user has returned from the
        // camera
        resolve();
      } else {
        // Probably not returned from camera, run another trial.
        window.setTimeout(evtTimer, REQUESTED_DELAY_MS);
      }
    };
    window.setTimeout(evtTimer, REQUESTED_DELAY_MS);
  });
}

I tested this on recent version of iOS and Android, bringing up the native camera by setting the attributes on the <input />element.

我在最新版本的 iOS 和 Android 上对此进行了测试,通过在<input />元素上设置属性来启动本机相机。

<input type="file" accept="image/*" capture="camera" />
<input type="file" accept="video/*" capture="camcorder" />

This works out actually a lot better than I expected. It runs 10 trials by requesting a timer to be invoked in 25 milliseconds. It then measures how long it actually took to invoke, and if the average of 10 trials is less than 50 milliseconds, we assume that we must be in the foreground and the camera is gone. If it is greater than 50 milliseconds, then we must still be in the background and should continue to wait.

这实际上比我预期的要好得多。它通过请求在 25 毫秒内调用一个计时器来运行 10 次试验。然后它测量调用实际花费的时间,如果 10 次试验的平均值小于 50 毫秒,我们假设我们必须在前台并且相机不见了。如果大于50毫秒,那么我们肯定还在后台,应该继续等待。

Some additional details

一些额外的细节

I used setTimeout()rather than setInterval()because the latter can queue multiple invocations which execute immediately after each other. This could drastically increase the noise in our data, so I stuck with setTimeout()even though it is a little more complicated to do so.

我使用setTimeout()而不是setInterval()因为后者可以将多个调用排队,这些调用会在彼此之后立即执行。这可能会大大增加我们数据中的噪音,所以我坚持使用,setTimeout()即使这样做有点复杂。

These particular numbers worked well for me, though I have see at least once instance where the camera dismiss was detected prematurely. I believe this is because the camera may be slow to open, and the device may run 10 trials before it actually becomes backgrounded. Adding more trials or waiting some 25-50 milliseconds before starting this function may be a workaround for that.

这些特定的数字对我来说效果很好,尽管我至少见过一次过早检测到相机关闭的实例。我相信这是因为相机打开速度可能很慢,并且设备可能会在实际变为后台之前运行 10 次试验。在启动此功能之前添加更多试验或等待大约 25-50 毫秒可能是一种解决方法。

Desktop

桌面

Unfortuantely, this doesn't really work for desktop browsers. In theory the same trick is possible as they do prioritize the current page over backgrounded pages. However many desktops have enough resources to keep the page running at full speed even when backgrounded, so this strategy doesn't really work in practice.

不幸的是,这不适用于桌面浏览器。理论上,同样的技巧是可能的,因为它们确实优先考虑当前页面而不是背景页面。然而,许多桌面有足够的资源来保持页面全速运行,即使在后台运行时,这种策略在实践中并没有真正奏效。

Alternative solutions

替代解决方案

One alternative solution not many people mention that I did explore was mocking a FileList. We start with nullin the <input />and then if the user opens the camera and cancels they come back to null, which is not a change and no event will trigger. One solution would be to assign a dummy file to the <input />at page start, therefore setting to nullwould be a change which would trigger the appropriate event.

一个没有多少人提到我确实探索过的替代解决方案是模拟FileList. 我们从nullin开始,<input />然后如果用户打开相机并取消,他们会回到null,这不是更改,也不会触发任何事件。一种解决方案是将一个虚拟文件分配给<input />页面开始处,因此设置为null将是触发适当事件的更改。

Unfortunately, there's no way official way to create a FileList, and the <input />element requires a FileListin particular and will not accept any other value besides null. Naturally, FileListobjects cannot be directly constructed, do to some old security issue which isn't even relevant anymore apparently. The only way to get ahold of one outside of an <input />element is to utilize a hack which copy-pastes data to fake a clipboard event which can contain a FileListobject (you're basically faking a drag-and-drop-a-file-on-your-website event). This is possible in Firefox, but not for iOS Safari, so it was not viable for my particular use case.

不幸的是,没有办法创建 a 的官方方法FileList,并且该<input />元素FileList特别需要 a并且不会接受除 之外的任何其他值null。自然地,FileList对象不能直接构造,解决一些甚至不再明显相关的旧安全问题。在<input />元素之外获得一个的唯一方法是利用一个 hack 复制粘贴数据来伪造一个可以包含一个FileList对象的剪贴板事件(你基本上是在伪造一个拖放文件-您的网站活动)。这在 Firefox 中是可能的,但不适用于 iOS Safari,因此它不适用于我的特定用例。

Browsers, please...

浏览器请...

Needless to say this is patently ridiculous. The fact that web pages are given zero notification that a critical UI element has changed is simply laughable. This is really a bug in the spec, as it was never intended for a full-screen media capture UI, and not triggering the "change" event is technicallyto spec.

不用说,这显然是荒谬的。网页在关键 UI 元素已更改时收到零通知这一事实简直可笑。这确实是规范中的一个错误,因为它从未打算用于全屏媒体捕获 UI,并且不触发“更改”事件在技术上符合规范。

However, can browser vendors please recognize the reality of this? This could be solved with either a new "done" event which is triggered even when no change occurs, or you could just trigger "change" anyways. Yeah, that would be against spec, but it is trivial for me to dedup a change event on the JavaScript side, yet fundamentally impossible to invent my own "done" event. Even my solution is really just heuristics, if offer no guarantees on the state of the browser.

然而,浏览器厂商能否认清这一现实?这可以通过一个新的“完成”事件来解决,即使没有发生任何变化也会触发,或者你可以触发“变化”。是的,这会违反规范,但是在 JavaScript 端删除更改事件对我来说是微不足道的,但从根本上不可能发明我自己的“完成”事件。如果不保证浏览器的状态,即使我的解决方案也只是启发式方法。

As it stands, this API is fundamentally unusable for mobile devices, and I think a relatively simple browser change could make this infinitely easier for web developers *steps off soap box*.

就目前而言,这个 API 从根本上无法用于移动设备,我认为一个相对简单的浏览器更改可以使 Web 开发人员更容易*离开肥皂盒*。

回答by zvolkov

When you select a file and click open/cancel, the inputelement should lose focus aka blur. Assuming the initial valueof the inputis empty, any non empty value in your blurhandler would indicate an OK, and an empty value would mean a Cancel.

当您选择一个文件并单击打开/取消时,该input元素应该失去焦点 aka blur。假设初始valueinput是空的,在你的任何非空值的blur处理程序将指示OK,和一个空值将意味着取消。

UPDATE: The bluris not triggered when the inputis hidden. So can't use this trick with IFRAME-based uploads, unless you want to temporarily display the input.

更新:隐藏blur时不会触发input。所以不能在基于 IFRAME 的上传中使用这个技巧,除非你想暂时显示input.

回答by Rizky

/* Tested on Google Chrome */
$("input[type=file]").bind("change", function() {
    var selected_file_name = $(this).val();
    if ( selected_file_name.length > 0 ) {
        /* Some file selected */
    }
    else {
        /* No file selected or cancel/close
           dialog button clicked */
        /* If user has select a file before,
           when they submit, it will treated as
           no file selected */
    }
});

回答by superdario128

Most of these solutions don't work for me.

大多数这些解决方案对我不起作用。

The problem is that you never know which event will be triggered fist, is it clickor is it change? You can't assume any order, because it probably depends on the browser's implementation.

问题是,你永远不知道这将触发事件的拳头,是它click还是它change?您不能假设任何顺序,因为它可能取决于浏览器的实现。

At least in Opera and Chrome (late 2015) clickis triggered just before 'filling' input with files, so you will never know the length of files.length != 0until you delay clickto be triggered after change.

至少在Opera和Chrome(2015年末)click只是之前触发“填充”使用文件输入,所以你永远不会知道的长度files.length != 0,直到你延迟click之后被触发change

Here is code:

这是代码:

var inputfile = $("#yourid");

inputfile.on("change click", function(ev){
    if (ev.originalEvent != null){
        console.log("OK clicked");
    }
    document.body.onfocus = function(){
        document.body.onfocus = null;
        setTimeout(function(){
            if (inputfile.val().length === 0) console.log("Cancel clicked");
        }, 1000);
    };
});

回答by AEQ

Just listen to the click event as well.

只需收听点击事件。

Following from Shiboe's example, here's a jQuery example:

继 Shiboe 的示例之后,这是一个 jQuery 示例:

var godzilla = $('#godzilla');
var godzillaBtn = $('#godzilla-btn');

godzillaBtn.on('click', function(){
    godzilla.trigger('click');
});

godzilla.on('change click', function(){

    if (godzilla.val() != '') {
        $('#state').html('You have chosen a Mech!');    
    } else {
        $('#state').html('Choose your Mech!');
    }

});

You can see it in action here: http://jsfiddle.net/T3Vwz

你可以在这里看到它的实际效果:http: //jsfiddle.net/T3Vwz

回答by loveJs

You can catch the cancel if you choose the same file as previously and you click cancel: in this case.

如果您选择与以前相同的文件并单击取消,则可以捕获取消:在这种情况下。

You can do it like this:

你可以这样做:

<input type="file" id="myinputfile"/>
<script>
document.getElementById('myinputfile').addEventListener('change', myMethod, false);
function myMethod(evt) {
  var files = evt.target.files; 
  f= files[0];
  if (f==undefined) {
     // the user has clicked on cancel
  }
  else if (f.name.match(".*\.jpg")|| f.name.match(".*\.png")) {
     //.... the user has choosen an image file
     var reader = new FileReader();
     reader.onload = function(evt) { 
        try {
        myimage.src=evt.target.result;
            ...
         } catch (err) {
            ...
         }
     };
  }     
  reader.readAsDataURL(f);          
</script>

回答by Krzysztof Janiszewski

The easiest way is to check if there are any files in temporary memory. If you want to get the change event every time user clicks the file input you can trigger it.

最简单的方法是检查临时内存中是否有任何文件。如果您想在每次用户单击文件输入时获取更改事件,您可以触发它。

var yourFileInput = $("#yourFileInput");

yourFileInput.on('mouseup', function() {
    $(this).trigger("change");
}).on('change', function() {
    if (this.files.length) {
        //User chose a picture
    } else {
        //User clicked cancel
    }
});

回答by Israel

I found this atribute, its most simple yet.

我找到了这个属性,它是最简单的。

if ($('#selectedFile')[0].files.length > 1)
{
   // Clicked on 'open' with file
} else {
   // Clicked on 'cancel'
}

Here, selectedFileis an input type=file.

在这里,selectedFile是一个input type=file.