Câu trả lời:
Mặc dù không phải là một giải pháp trực tiếp, và cũng tệ ở chỗ nó chỉ hoạt động (theo như tôi đã thử nghiệm) hoạt động với onfocus (yêu cầu chặn sự kiện khá hạn chế), bạn có thể đạt được nó bằng cách sau:
document.body.onfocus = function(){ /*rock it*/ }
Điều thú vị về điều này là bạn có thể đính kèm / tách nó ra kịp thời với sự kiện tệp và nó cũng có vẻ hoạt động tốt với các đầu vào ẩn (một đặc quyền nhất định nếu bạn đang sử dụng giải pháp trực quan cho kiểu nhập mặc định tồi tàn = ' tập tin'). Sau đó, bạn chỉ cần tìm xem giá trị đầu vào có thay đổi hay không.
Một ví dụ:
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')
}
Xem nó trong thực tế: http://jsfiddle.net/Shiboe/yuK3r/6/
Đáng buồn thay, nó dường như chỉ hoạt động trên các trình duyệt webkit. Có thể ai đó khác có thể tìm ra giải pháp firefox / IE
addEventListener("focus",fn,{once: true})
. Tuy nhiên tôi không thể làm cho nó cháy được cả khi sử dụng document.body.addEventListener
.. Tôi không biết tại sao. Thay vào đó, tôi nhận được cùng một kết quả với window.addEventListener
.
mousemove
sự kiện window.document
ngoài việc lắng nghe focus
trên window
dường như hoạt động ở mọi nơi (ít nhất là trong các trình duyệt hiện đại, tôi không quan tâm đến những tàn tích của thập kỷ trước). Về cơ bản, bất kỳ sự kiện tương tác nào cũng có thể được sử dụng cho tác vụ này bị chặn bởi hộp thoại mở tệp đã mở.
Bạn không thể.
Kết quả của hộp thoại tệp không được hiển thị cho trình duyệt.
change
sự kiện nào được gửi đi.
change
sự kiện nào được thực hiện cho tệp đó.
Vì vậy, tôi sẽ ngả mũ trước câu hỏi này vì tôi đã nghĩ ra một giải pháp mới. Tôi có một Ứng dụng web tiến bộ cho phép người dùng chụp ảnh và quay video và tải chúng lên. Chúng tôi sử dụng WebRTC khi có thể, nhưng quay lại trình chọn tệp HTML5 cho các thiết bị ít hỗ trợ hơn * ho Safari ho *. Nếu bạn đang làm việc cụ thể trên một ứng dụng web di động Android / iOS sử dụng camera gốc để chụp ảnh / quay video trực tiếp, thì đây là giải pháp tốt nhất mà tôi đã thử.
Điểm mấu chốt của vấn đề này là khi trang tải, hiện tại file
là null
, nhưng sau đó khi người dùng mở hộp thoại và nhấn "Hủy", file
vẫn là null
, do đó nó không "thay đổi", do đó không có sự kiện "thay đổi" nào được kích hoạt. Đối với máy tính để bàn, điều này không quá tệ vì hầu hết giao diện người dùng của máy tính để bàn không phụ thuộc vào việc biết khi nào hủy được gọi, nhưng giao diện người dùng di động đưa máy ảnh lên để chụp ảnh / video thì rất phụ thuộc nhiều vào việc biết khi nào nhấn hủy.
Ban đầu tôi đã sử dụng document.body.onfocus
sự kiện để phát hiện thời điểm người dùng quay lại từ bộ chọn tệp và điều này hoạt động với hầu hết các thiết bị, nhưng iOS 11.3 đã phá vỡ nó vì sự kiện đó không được kích hoạt.
Giải pháp của tôi cho điều này là * shudder * để đo thời gian CPU để xác định xem trang hiện đang ở nền trước hay nền sau. Trên thiết bị di động, thời gian xử lý được cấp cho ứng dụng hiện ở nền trước. Khi camera hiển thị, nó sẽ đánh cắp thời gian của CPU và làm mất tác dụng của trình duyệt. Tất cả những gì chúng ta cần làm là đo lượng thời gian xử lý trang của chúng ta được cung cấp, khi máy ảnh khởi chạy, thời gian khả dụng của chúng ta sẽ giảm đáng kể. Khi máy ảnh bị loại bỏ (hoặc bị hủy hoặc bằng cách khác), thời gian có sẵn của chúng tôi sẽ tăng đột biến.
Chúng ta có thể đo thời gian của CPU bằng cách sử dụng setTimeout()
để gọi một lệnh gọi lại trong X mili giây, sau đó đo thời gian thực sự gọi nó. Trình duyệt sẽ không bao giờ gọi nó chính xác sau X mili giây, nhưng nếu nó đóng hợp lý thì chúng ta phải ở phía trước. Nếu trình duyệt ở rất xa (chậm hơn 10 lần so với yêu cầu) thì chúng tôi phải ở trong nền. Cách triển khai cơ bản của điều này là như vậy:
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);
});
}
Tôi đã thử nghiệm điều này trên phiên bản iOS và Android gần đây, đưa camera gốc lên bằng cách thiết lập các thuộc tính trên <input />
phần tử.
<input type="file" accept="image/*" capture="camera" />
<input type="file" accept="video/*" capture="camcorder" />
Điều này thực sự diễn ra tốt hơn rất nhiều so với tôi mong đợi. Nó chạy 10 lần thử nghiệm bằng cách yêu cầu bộ đếm thời gian được gọi trong 25 mili giây. Sau đó, nó đo lường thời gian thực sự để gọi ra và nếu trung bình của 10 lần thử nghiệm là dưới 50 mili giây, chúng tôi giả định rằng chúng tôi phải ở phía trước và máy ảnh đã biến mất. Nếu nó lớn hơn 50 mili giây, thì chúng ta vẫn phải ở chế độ nền và nên tiếp tục đợi.
Tôi đã sử dụng setTimeout()
chứ không phải setInterval()
vì cái sau có thể xếp hàng nhiều lệnh gọi thực thi ngay sau nhau. Điều này có thể làm tăng đáng kể nhiễu trong dữ liệu của chúng tôi, vì vậy tôi mắc kẹt vớisetTimeout()
mặc dù nó phức tạp hơn một chút để làm như vậy.
Những con số cụ thể này hoạt động tốt đối với tôi, mặc dù tôi đã ít nhất một lần thấy trường hợp loại bỏ camera được phát hiện sớm. Tôi tin rằng điều này là do máy ảnh có thể mở chậm và thiết bị có thể chạy 10 lần thử trước khi thực sự ở chế độ nền. Thêm nhiều thử nghiệm hơn hoặc đợi khoảng 25-50 mili giây trước khi bắt đầu chức năng này có thể là một cách giải quyết cho điều đó.
Thật không may, điều này không thực sự hoạt động cho các trình duyệt trên máy tính để bàn. Về lý thuyết, thủ thuật tương tự cũng có thể xảy ra vì chúng ưu tiên trang hiện tại hơn các trang nền. Tuy nhiên, nhiều máy tính để bàn có đủ tài nguyên để giữ trang chạy ở tốc độ tối đa ngay cả khi ở chế độ nền, vì vậy chiến lược này không thực sự hiệu quả trong thực tế.
Một giải pháp thay thế mà không nhiều người đề cập đến mà tôi đã khám phá là chế nhạo a FileList
. Chúng tôi bắt đầu với null
trong <input />
và sau đó nếu người dùng mở máy ảnh và hủy bỏ họ trở lại null
, mà không phải là một sự thay đổi và không có sự kiện sẽ kích hoạt. Một giải pháp sẽ là gán một tệp giả cho <input />
ở đầu trang, do đó cài đặt thành null
sẽ là một thay đổi sẽ kích hoạt sự kiện thích hợp.
Thật không may, không có cách nào chính thức để tạo một FileList
, và <input />
phần tử yêu cầu một FileList
đặc biệt và sẽ không chấp nhận bất kỳ giá trị nào khác bên cạnh đó null
. Đương nhiên, FileList
các đối tượng không thể được xây dựng trực tiếp, làm cho một số vấn đề bảo mật cũ mà dường như không còn liên quan nữa. Cách duy nhất để có được một <input />
phần tử bên ngoài một phần tử là sử dụng một bản hack sao chép dữ liệu để giả mạo sự kiện khay nhớ tạm có thể chứaFileList
đối tượng (về cơ bản bạn đang giả mạo tệp kéo và thả -sự kiện trang web của bạn). Điều này có thể thực hiện được trong Firefox, nhưng không khả thi với iOS Safari, vì vậy nó không khả thi cho trường hợp sử dụng cụ thể của tôi.
Không cần phải nói điều này thật nực cười. Thực tế là các trang web không được cung cấp thông báo rằng một phần tử giao diện người dùng quan trọng đã thay đổi chỉ đơn giản là buồn cười. Đây thực sự là một lỗi trong thông số kỹ thuật, vì nó không bao giờ được dành cho giao diện người dùng chụp đa phương tiện toàn màn hình và về mặt kỹ thuật , việc không kích hoạt sự kiện "thay đổi" .
Tuy nhiên , các nhà cung cấp trình duyệt có thể vui lòng nhận ra thực tế của điều này? Điều này có thể được giải quyết bằng sự kiện "hoàn thành" mới được kích hoạt ngay cả khi không có thay đổi nào xảy ra hoặc bạn có thể chỉ cần kích hoạt "thay đổi". Vâng, điều đó sẽ chống lại thông số kỹ thuật, nhưng đối với tôi, việc trích xuất một sự kiện thay đổi về phía JavaScript là điều tầm thường, nhưng về cơ bản là không thể tạo ra sự kiện "xong" của riêng tôi. Ngay cả giải pháp của tôi thực sự chỉ là phỏng đoán, nếu không có gì đảm bảo về trạng thái của trình duyệt.
Về cơ bản, API này về cơ bản không thể sử dụng được cho các thiết bị di động và tôi nghĩ rằng một thay đổi trình duyệt tương đối đơn giản có thể làm cho việc này dễ dàng hơn vô cùng đối với các nhà phát triển web * bước ra khỏi hộp xà phòng *.
Khi bạn chọn một tệp và nhấp vào mở / hủy, input
phần tử sẽ mất tiêu điểm hay còn gọi là blur
. Giả sử giá trị ban đầu value
của input
giá trị trống, bất kỳ giá trị không trống nào trong blur
trình xử lý của bạn sẽ cho biết OK và giá trị trống sẽ có nghĩa là Hủy.
CẬP NHẬT: Không blur
được kích hoạt khi input
ẩn. Vì vậy, không thể sử dụng thủ thuật này với tải lên dựa trên IFRAME, trừ khi bạn muốn tạm thời hiển thị input
.
blur
này không được kích hoạt khi bạn chọn một tệp. Chưa thử trong các trình duyệt khác.
/* 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 */
}
});
else
tuyên bố của bạn có bao giờ được đánh giá không?
Hầu hết các giải pháp này không hiệu quả với tôi.
Vấn đề là bạn không bao giờ biết sự kiện nào sẽ được kích hoạt nắm đấm, có click
hay change
không? Bạn không thể giả định bất kỳ thứ tự nào, vì nó có thể phụ thuộc vào việc triển khai của trình duyệt.
Ít nhất trong Opera và Chrome (cuối năm 2015) click
được kích hoạt ngay trước khi 'lấp đầy' đầu vào bằng các tệp, vì vậy bạn sẽ không bao giờ biết khoảng thời gian files.length != 0
cho đến khi bạn trì hoãn click
để được kích hoạt sau đó change
.
Đây là mã:
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);
};
});
Chỉ cần lắng nghe sự kiện nhấp chuột là tốt.
Tiếp theo từ ví dụ của Shiboe, đây là một ví dụ về 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!');
}
});
Bạn có thể thấy nó hoạt động tại đây: http://jsfiddle.net/T3Vwz
Bạn có thể bắt được thông báo hủy nếu bạn chọn cùng một tệp như trước đó và bạn nhấp vào hủy: trong trường hợp này.
Bạn có thể làm như thế này:
<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>
Cách dễ nhất là kiểm tra xem có tệp nào trong bộ nhớ tạm thời hay không. Nếu bạn muốn nhận sự kiện thay đổi mỗi khi người dùng nhấp vào đầu vào tệp, bạn có thể kích hoạt sự kiện đó.
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
}
});
Giải pháp của Shiboe sẽ là một giải pháp tốt nếu nó hoạt động trên webkit di động, nhưng nó không. Những gì tôi có thể nghĩ ra là thêm trình nghe sự kiện di chuột vào một số đối tượng dom tại thời điểm cửa sổ nhập tệp được mở, như sau:
$('.upload-progress').mousemove(function() {
checkForFiles(this);
});
checkForFiles = function(me) {
var filefield = $('#myfileinput');
var files = filefield.get(0).files;
if (files == undefined || files[0] == undefined) $(me).remove(); // user cancelled the upload
};
Sự kiện di chuyển chuột bị chặn khỏi trang trong khi hộp thoại tệp đang mở và khi hộp thoại đóng sẽ kiểm tra xem có tệp nào trong đầu vào tệp hay không. Trong trường hợp của tôi, tôi muốn một chỉ báo hoạt động chặn mọi thứ cho đến khi tệp được tải lên, vì vậy tôi chỉ muốn xóa chỉ báo của mình khi hủy.
Tuy nhiên, điều này không giải quyết được cho thiết bị di động, vì không có chuột để di chuyển. Giải pháp của tôi ở đó chưa hoàn hảo, nhưng tôi nghĩ nó đủ tốt.
$('.upload-progress').bind('touchstart', function() {
checkForFiles(this);
});
Bây giờ chúng ta đang lắng nghe một cú chạm trên màn hình để kiểm tra các tệp tương tự. Tôi khá tin tưởng rằng ngón tay của người dùng sẽ được đưa lên màn hình khá nhanh sau khi hủy và loại bỏ chỉ báo hoạt động này.
Người ta cũng có thể chỉ thêm chỉ báo hoạt động trên sự kiện thay đổi đầu vào tệp, nhưng trên thiết bị di động thường có độ trễ vài giây giữa việc chọn hình ảnh và kích hoạt sự kiện thay đổi, vì vậy UX tốt hơn nhiều cho chỉ báo hoạt động được hiển thị tại bắt đầu của quá trình.
Tôi đã tìm thấy thuộc tính này, nó đơn giản nhất.
if ($('#selectedFile')[0].files.length > 1)
{
// Clicked on 'open' with file
} else {
// Clicked on 'cancel'
}
Đây, selectedFile
là một input type=file
.
Tôi biết đây là một câu hỏi rất cũ nhưng để đề phòng nó giúp ích cho ai đó, tôi thấy khi sử dụng sự kiện onmousemove để phát hiện việc hủy, cần phải kiểm tra hai hoặc nhiều sự kiện như vậy trong một khoảng thời gian ngắn. Điều này là do các sự kiện onmousemove đơn lẻ được tạo bởi trình duyệt (Chrome 65) mỗi khi con trỏ được di chuyển ra khỏi cửa sổ hộp thoại tệp đã chọn và mỗi lần nó được di chuyển ra khỏi cửa sổ chính và quay lại. Một bộ đếm đơn giản của các sự kiện di chuyển chuột cùng với thời gian chờ trong thời gian ngắn để đặt lại bộ đếm về 0 đã có tác dụng.
Trong trường hợp của tôi, tôi đã phải ẩn nút gửi trong khi người dùng đang chọn hình ảnh.
Đây là những gì tôi nghĩ ra:
$(document).on('click', '#image-field', function(e) {
$('.submit-button').prop('disabled', true)
})
$(document).on('focus', '#image-field'), function(e) {
$('.submit-button').prop('disabled', false)
})
#image-field
là bộ chọn tệp của tôi. Khi ai đó nhấp vào nó, tôi vô hiệu hóa nút gửi biểu mẫu. Vấn đề là, khi hộp thoại tệp đóng - không quan trọng họ chọn tệp hay hủy - #image-field
đã lấy lại tiêu điểm, vì vậy tôi lắng nghe sự kiện đó.
CẬP NHẬT
Tôi thấy rằng, điều này không hoạt động trong safari và poltergeist / phantomjs. Hãy tính đến thông tin này nếu bạn muốn triển khai nó.
Kết hợp các giải pháp của Shiboe và alx, tôi đã có mã đáng tin cậy nhất:
var selector = $('<input/>')
.attr({ /* just for example, use your own attributes */
"id": "FilesSelector",
"name": "File",
"type": "file",
"contentEditable": "false" /* if you "click" on input via label, this prevents IE7-8 from just setting caret into file input's text filed*/
})
.on("click.filesSelector", function () {
/* do some magic here, e.g. invoke callback for selection begin */
var cancelled = false; /* need this because .one calls handler once for each event type */
setTimeout(function () {
$(document).one("mousemove.filesSelector focusin.filesSelector", function () {
/* namespace is optional */
if (selector.val().length === 0 && !cancelled) {
cancelled = true; /* prevent double cancel */
/* that's the point of cancel, */
}
});
}, 1); /* 1 is enough as we just need to delay until first available tick */
})
.on("change.filesSelector", function () {
/* do some magic here, e.g. invoke callback for successful selection */
})
.appendTo(yourHolder).end(); /* just for example */
Nói chung, sự kiện di chuyển chuột thực hiện thủ thuật, nhưng trong trường hợp người dùng thực hiện một nhấp chuột và hơn:
... chúng tôi sẽ không nhận được sự kiện di chuyển chuột do đó không có lệnh gọi lại hủy. Hơn nữa, nếu người dùng hủy hộp thoại thứ hai và di chuyển chuột, chúng tôi sẽ nhận được 2 lệnh gọi lại hủy. May mắn thay, sự kiện jQuery focusIn đặc biệt sẽ xuất hiện trên tài liệu trong cả hai trường hợp, giúp chúng ta tránh những trường hợp như vậy. Hạn chế duy nhất là nếu một trong hai khóa sự kiện focusIn.
mousemove
sự kiện document
trong khi chọn tệp trong hộp thoại hệ điều hành trong Chrome 56.0.2924.87 trên Ubuntu 16.10. Không thành vấn đề, khi không di chuyển chuột hoặc chỉ sử dụng bàn phím để chọn tệp. Tôi đã phải thay thế mousemove
bằng keydown
để phát hiện hủy càng sớm càng tốt, nhưng không "quá sớm", khi hộp thoại vẫn đang mở.
Tôi thấy rằng phản hồi của tôi sẽ khá lỗi thời, nhưng không bao giờ ít hơn. Tôi phải đối mặt với cùng một vấn đề. Vì vậy, đây là giải pháp của tôi. Đoạn mã hữu ích nhất được cắt là mã của KGA. Nhưng nó không hoàn toàn hoạt động và hơi phức tạp. Nhưng tôi đã đơn giản hóa nó.
Ngoài ra, tác nhân gây ra rắc rối chính là thực tế, sự kiện 'thay đổi' đó không đến ngay lập tức sau khi tiêu điểm, vì vậy chúng ta phải đợi một thời gian.
"#appendfile" - người dùng nhấp vào để nối một tệp mới. Hrefs lấy các sự kiện tiêu điểm.
$("#appendfile").one("focusin", function () {
// no matter - user uploaded file or canceled,
// appendfile gets focus
// change doesn't come instantly after focus, so we have to wait for some time
// wrapper represents an element where a new file input is placed into
setTimeout(function(){
if (wrapper.find("input.fileinput").val() != "") {
// user has uploaded some file
// add your logic for new file here
}
else {
// user canceled file upload
// you have to remove a fileinput element from DOM
}
}, 900);
});
Bạn có thể phát hiện điều này chỉ trong một số trường hợp hạn chế. Cụ thể, trong chrome nếu một tệp được chọn trước đó và sau đó hộp thoại tệp được nhấp và hủy được nhấp vào, Chrome sẽ xóa tệp và kích hoạt sự kiện onChange.
https://code.google.com/p/chromium/issues/detail?id=2508
Trong trường hợp này, bạn có thể phát hiện điều này bằng cách xử lý sự kiện onChange và kiểm tra thuộc tính tệp .
Những điều sau đây dường như phù hợp với tôi (trên máy tính để bàn, cửa sổ):
var openFile = function (mimeType, fileExtension) {
var defer = $q.defer();
var uploadInput = document.createElement("input");
uploadInput.type = 'file';
uploadInput.accept = '.' + fileExtension + ',' + mimeType;
var hasActivated = false;
var hasChangedBeenCalled = false;
var hasFocusBeenCalled = false;
var focusCallback = function () {
if (hasActivated) {
hasFocusBeenCalled = true;
document.removeEventListener('focus', focusCallback, true);
setTimeout(function () {
if (!hasChangedBeenCalled) {
uploadInput.removeEventListener('change', changedCallback, true);
defer.resolve(null);
}
}, 300);
}
};
var changedCallback = function () {
uploadInput.removeEventListener('change', changedCallback, true);
if (!hasFocusBeenCalled) {
document.removeEventListener('focus', focusCallback, true);
}
hasChangedBeenCalled = true;
if (uploadInput.files.length === 1) {
//File picked
var reader = new FileReader();
reader.onload = function (e) {
defer.resolve(e.target.result);
};
reader.readAsText(uploadInput.files[0]);
}
else {
defer.resolve(null);
}
};
document.addEventListener('focus', focusCallback, true); //Detect cancel
uploadInput.addEventListener('change', changedCallback, true); //Detect when a file is picked
uploadInput.click();
hasActivated = true;
return defer.promise;
}
Điều này sử dụng anglejs $ q nhưng bạn có thể thay thế nó bằng bất kỳ khung hứa hẹn nào khác nếu cần.
Đã thử nghiệm trên IE11, Edge, Chrome, Firefox, nhưng dường như nó không hoạt động trên Chrome trên Máy tính bảng Android vì nó không kích hoạt sự kiện Focus.
Trường file
-type, thật khó chịu, không phản hồi với nhiều sự kiện (mờ sẽ rất đáng yêu). Tôi thấy rất nhiều người đề xuất change
các giải pháp định hướng và họ bị phản đối.
change
hoạt động, nhưng nó có một lỗ hổng lớn (so với những gì chúng tôi muốn xảy ra).
Khi bạn mới tải một trang chứa trường tệp, hãy mở hộp và nhấn hủy. Không có gì thay đổi .
Những gì tôi chọn làm là tải ở trạng thái kiểm soát.
section#after-image
trong trường hợp của tôi bị ẩn khỏi chế độ xem. Khi tôi file field
thay đổi, nút tải lên sẽ hiển thị. Sau khi tải lên thành công, section#after-image
được hiển thị.change
sự kiện sẽ được kích hoạt bởi hành động hủy này và ở đó tôi có thể (và làm) ẩn lại nút tải lên của mình cho đến khi chọn được tệp thích hợp.Tôi đã may mắn rằng trạng thái được kiểm soát này đã là thiết kế của biểu mẫu của tôi. Bạn không cần phải sử dụng cùng một kiểu, chỉ cần ẩn nút tải lên ban đầu và sau khi thay đổi, hãy đặt trường ẩn hoặc biến javascript thành thứ mà bạn có thể giám sát khi gửi.
Tôi đã thử thay đổi giá trị của tệp [0] trước khi trường được tương tác với. Điều này không làm bất cứ điều gì liên quan đến trao đổi.
Vì vậy, có, change
hoạt động, ít nhất là tốt như chúng tôi sẽ nhận được. Trường tệp được bảo mật, vì những lý do rõ ràng, nhưng trước sự thất vọng của các nhà phát triển có thiện chí.
Nó không phù hợp với mục đích của tôi, nhưng bạn có thể onclick
tải một lời nhắc cảnh báo (không phải alert()
vì điều đó ngăn chặn quá trình xử lý trang) và ẩn nó nếu thay đổi được kích hoạt và tệp [0] là trống. Nếu thay đổi không được kích hoạt, div vẫn ở trạng thái của nó.
Có một cách hackish để làm điều này (thêm callbacks hoặc giải quyết một số triển khai hứa hẹn / trì hoãn thay vì alert()
gọi):
var result = null;
$('<input type="file" />')
.on('change', function () {
result = this.files[0];
alert('selected!');
})
.click();
setTimeout(function () {
$(document).one('mousemove', function () {
if (!result) {
alert('cancelled');
}
});
}, 1000);
Cách hoạt động: trong khi hộp thoại chọn tệp đang mở, tài liệu không nhận được các sự kiện con trỏ chuột. Có độ trễ 1000ms để cho phép hộp thoại thực sự xuất hiện và chặn cửa sổ trình duyệt. Đã kiểm tra trong Chrome và Firefox (chỉ dành cho Windows).
Nhưng đây không phải là cách đáng tin cậy để phát hiện hộp thoại bị hủy. Tuy nhiên, có thể cải thiện một số hành vi giao diện người dùng cho bạn.
Đây là giải pháp của tôi, sử dụng tiêu điểm đầu vào tệp (không sử dụng bất kỳ bộ hẹn giờ nào)
var fileInputSelectionInitiated = false;
function fileInputAnimationStart() {
fileInputSelectionInitiated = true;
if (!$("#image-selector-area-icon").hasClass("fa-spin"))
$("#image-selector-area-icon").addClass("fa-spin");
if (!$("#image-selector-button-icon").hasClass("fa-spin"))
$("#image-selector-button-icon").addClass("fa-spin");
}
function fileInputAnimationStop() {
fileInputSelectionInitiated = false;
if ($("#image-selector-area-icon").hasClass("fa-spin"))
$("#image-selector-area-icon").removeClass("fa-spin");
if ($("#image-selector-button-icon").hasClass("fa-spin"))
$("#image-selector-button-icon").removeClass("fa-spin");
}
$("#image-selector-area-wrapper").click(function (e) {
$("#fileinput").focus();
$("#fileinput").click();
});
$("#preview-image-wrapper").click(function (e) {
$("#fileinput").focus();
$("#fileinput").click();
});
$("#fileinput").click(function (e) {
fileInputAnimationStart();
});
$("#fileinput").focus(function (e) {
fileInputAnimationStop();
});
$("#fileinput").change(function(e) {
// ...
}
Error: { "message": "Uncaught SyntaxError: missing ) after argument list", "filename": "https://stacksnippets.net/js", "lineno": 51, "colno": 1 }
Điều này tốt nhất là hacky, nhưng đây là một ví dụ hoạt động về giải pháp của tôi để phát hiện xem người dùng đã tải lên tệp hay chưa và chỉ cho phép họ tiếp tục nếu họ đã tải tệp lên.
Về cơ bản ẩn Continue
, Save
, Proceed
hoặc bất cứ nút bấm của bạn là. Sau đó, trong JavaScript, bạn lấy tên tệp. Nếu tên tệp không có giá trị, thì không hiển thịContinue
nút. Nếu nó có một giá trị, thì hãy hiển thị nút. Điều này cũng hoạt động nếu lúc đầu họ tải lên một tệp và sau đó họ cố tải lên một tệp khác và nhấp vào hủy.
Đây là mã.
HTML:
<div class="container">
<div class="row">
<input class="file-input" type="file" accept="image/*" name="fileUpload" id="fileUpload" capture="camera">
<label for="fileUpload" id="file-upload-btn">Capture or Upload Photo</label>
</div>
<div class="row padding-top-two-em">
<input class="btn btn-success hidden" id="accept-btn" type="submit" value="Accept & Continue"/>
<button class="btn btn-danger">Back</button>
</div></div>
JavaScript:
$('#fileUpload').change(function () {
var fileName = $('#fileUpload').val();
if (fileName != "") {
$('#file-upload-btn').html(fileName);
$('#accept-btn').removeClass('hidden').addClass('show');
} else {
$('#file-upload-btn').html("Upload File");
$('#accept-btn').addClass('hidden');
}
});
CSS:
.file-input {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
.file-input + label {
font-size: 1.25em;
font-weight: normal;
color: white;
background-color: blue;
display: inline-block;
padding: 5px;
}
.file-input:focus + label,
.file-input + label:hover {
background-color: red;
}
.file-input + label {
cursor: pointer;
}
.file-input + label * {
pointer-events: none;
}
Đối với CSS, phần lớn điều này là làm cho trang web và nút có thể truy cập được cho tất cả mọi người. Tạo kiểu cho nút của bạn theo bất kỳ thứ gì bạn thích.
Chà, điều này không trả lời chính xác câu hỏi của bạn. Giả định của tôi là, bạn có một tình huống, khi bạn thêm đầu vào tệp và gọi lựa chọn tệp, và nếu người dùng nhấn hủy, bạn chỉ cần xóa đầu vào.
Nếu đúng như vậy, thì: Tại sao phải thêm đầu vào tệp trống?
Tạo một cái ngay lập tức, nhưng chỉ thêm nó vào DOM khi nó được điền vào. Như vậy:
var fileInput = $("<input type='file' name='files' style='display: none' />");
fileInput.bind("change", function() {
if (fileInput.val() !== null) {
// if has value add it to DOM
$("#files").append(fileInput);
}
}).click();
Vì vậy, ở đây tôi tạo <input type = "file" /> ngay lập tức, liên kết với sự kiện thay đổi của nó và sau đó ngay lập tức gọi nhấp chuột. Thay đổi sẽ chỉ kích hoạt khi người dùng chọn một tệp và nhấn Ok, nếu không, đầu vào sẽ không được thêm vào DOM, do đó sẽ không được gửi.
Ví dụ làm việc tại đây: https://jsfiddle.net/69g0Lxno/3/
Nếu bạn đã yêu cầu JQuery, giải pháp này có thể thực hiện công việc (đây là mã chính xác mà tôi thực sự cần trong trường hợp của mình, mặc dù việc sử dụng Promise chỉ để buộc mã đợi cho đến khi lựa chọn tệp được giải quyết):
await new Promise(resolve => {
const input = $("<input type='file'/>");
input.on('change', function() {
resolve($(this).val());
});
$('body').one('focus', '*', e => {
resolve(null);
e.stopPropagation();
});
input.click();
});
Có một số giải pháp được đề xuất trong chủ đề này và khó phát hiện khi người dùng nhấp vào nút "Hủy" trên hộp chọn tệp là một vấn đề ảnh hưởng đến nhiều người.
Thực tế là không có cách nào đáng tin cậy 100% để phát hiện xem người dùng có nhấp vào nút "Hủy" trên hộp chọn tệp hay không. Nhưng có những cách để phát hiện một cách đáng tin cậy nếu người dùng đã thêm tệp vào tệp đầu vào. Vì vậy, đây là chiến lược cơ bản của câu trả lời này!
Tôi quyết định thêm câu trả lời này vì rõ ràng các câu trả lời khác không hoạt động trên hầu hết các trình duyệt hoặc được đảm bảo trên thiết bị di động.
Tóm lại, mã dựa trên 3 điểm:
Để hiểu rõ hơn, hãy xem đoạn mã bên dưới và các ghi chú.
[...]
<button type="button" onclick="addIptFl();">ADD INPUT FILE!</button>
<span id="ipt_fl_parent"></span>
[...]
function dynIptFl(jqElInst, funcsObj) {
if (typeof funcsObj === "undefined" || funcsObj === "") {
funcsObj = {};
}
if (funcsObj.hasOwnProperty("before")) {
if (!funcsObj["before"].hasOwnProperty("args")) {
funcsObj["before"]["args"] = [];
}
funcsObj["before"]["func"].apply(this, funcsObj["before"]["args"]);
}
var jqElInstFl = jqElInst.find("input[type=file]");
// NOTE: Open the file selection box via js. By Questor
jqElInstFl.trigger("click");
// NOTE: This event is triggered if the user selects a file. By Questor
jqElInstFl.on("change", {funcsObj: funcsObj}, function(e) {
// NOTE: With the strategy below we avoid problems with other unwanted events
// that may be associated with the DOM element. By Questor
e.preventDefault();
var funcsObj = e.data.funcsObj;
if (funcsObj.hasOwnProperty("after")) {
if (!funcsObj["after"].hasOwnProperty("args")) {
funcsObj["after"]["args"] = [];
}
funcsObj["after"]["func"].apply(this, funcsObj["after"]["args"]);
}
});
}
function remIptFl() {
// NOTE: Remove the input file. By Questor
$("#ipt_fl_parent").empty();
}
function addIptFl() {
function addBefore(someArgs0, someArgs1) {
// NOTE: All the logic here happens just before the file selection box opens.
// By Questor
// SOME CODE HERE!
}
function addAfter(someArgs0, someArgs1) {
// NOTE: All the logic here happens only if the user adds a file. By Questor
// SOME CODE HERE!
$("#ipt_fl_parent").prepend(jqElInst);
}
// NOTE: The input file is hidden as all manipulation must be done via js.
// By Questor
var jqElInst = $('\
<span>\
<button type="button" onclick="remIptFl();">REMOVE INPUT FILE!</button>\
<input type="file" name="input_fl_nm" style="display: block;">\
</span>\
');
var funcsObj = {
before: {
func: addBefore,
args: [someArgs0, someArgs1]
},
after: {
func: addAfter,
// NOTE: The instance with the input file ("jqElInst") could be passed
// here instead of using the context of the "addIptFl()" function. That
// way "addBefore()" and "addAfter()" will not need to be inside "addIptFl()",
// for example. By Questor
args: [someArgs0, someArgs1]
}
};
dynIptFl(jqElInst, funcsObj);
}
Cảm ơn! = D
Chúng tôi đã đạt được ở góc độ như dưới đây.
<input type="file" formControlName="FileUpload" click)="handleFileInput($event.target.files)" />
/>
this.uploadPanel = false;
handleFileInput(files: FileList) {
this.fileToUpload = files.item(0);
console.log("ggg" + files);
this.uploadPanel = true;
}
@HostListener("window:focus", ["$event"])
onFocus(event: FocusEvent): void {
if (this.uploadPanel == true) {
console.log("cancel clicked")
this.addSlot
.get("FileUpload")
.setValidators([
Validators.required,
FileValidator.validate,
requiredFileType("png")
]);
this.addSlot.get("FileUpload").updateValueAndValidity();
}
}
Chỉ cần thêm trình nghe 'thay đổi' vào đầu vào của bạn có loại là tệp. I E
<input type="file" id="file_to_upload" name="file_to_upload" />
Tôi đã sử dụng jQuery và rõ ràng là bất kỳ ai cũng có thể sử dụng valina JS (theo yêu cầu).
$("#file_to_upload").change(function() {
if (this.files.length) {
alert('file choosen');
} else {
alert('file NOT choosen');
}
});
.change()
không được gọi một cách đáng tin cậy nếu người dùng nhấp vào "hủy" trên bộ chọn tệp.
enter code here
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<h1>Hello</h1>
<div id="cancel01">
<button>Cancel</button>
</div>
<div id="cancel02">
<button>Cancel</button>
</div>
<div id="cancel03">
<button>Cancel</button>
</div>
<form>
<input type="file" name="name" placeholder="Name" />
</form>
<script>
const nameInput = document.querySelector('input[type="file"]');
/******
*The below code if for How to detect when cancel is clicked on file input
******/
nameInput.addEventListener('keydown', e => {
/******
*If the cancel button is clicked,then you should change the input file value to empty
******/
if (e.key == 'Backspace' || e.code == 'Backspace' || e.keyCode == 8) {
console.log(e);
/******
*The below code will delete the file path
******/
nameInput.value = '';
}
});
</script>
</body>
</html>
Lưu ý: mã này không phát hiện ra việc hủy, nó cung cấp một cách để tránh sự cần thiết phải phát hiện trong trường hợp phổ biến mà mọi người cố gắng phát hiện ra nó.
Tôi đến đây khi đang tìm giải pháp cho việc tải tệp lên bằng đầu vào ẩn, tôi tin rằng đây là lý do phổ biến nhất để tìm cách phát hiện việc hủy đầu vào tệp (hộp thoại mở tệp -> nếu tệp đã được chọn thì hãy chạy một số mã, nếu không thì không làm gì cả), đây là giải pháp của tôi:
var fileSelectorResolve;
var fileSelector = document.createElement('input');
fileSelector.setAttribute('type', 'file');
fileSelector.addEventListener('input', function(){
fileSelectorResolve(this.files[0]);
fileSelectorResolve = null;
fileSelector.value = '';
});
function selectFile(){
if(fileSelectorResolve){
fileSelectorResolve();
fileSelectorResolve = null;
}
return new Promise(function(resolve){
fileSelectorResolve = resolve;
fileSelector.dispatchEvent(new MouseEvent('click'));
});
}
Lưu ý rằng nếu không có tệp nào được chọn thì dòng đầu tiên sẽ chỉ trả về một lần nữa khi selectFile()
được gọi lại (hoặc nếu bạn đã gọi fileSelectorResolve()
từ nơi khác).
async function logFileName(){
const file = await selectFile();
if(!file) return;
console.log(file.name);
}
Một vi dụ khac:
async function uploadFile(){
const file = await selectFile();
if(!file) return;
// ... make an ajax call here to upload the file ...
}
Bạn có thể tạo trình nghe thay đổi jquery trên trường đầu vào và phát hiện người dùng đó hủy hoặc đóng cửa sổ tải lên theo giá trị của trường.
đây là một ví dụ:
//when upload button change
$('#upload_btn').change(function(){
//get uploaded file
var file = this.files[0];
//if user choosed a file
if(file){
//upload file or perform your desired functiuonality
}else{
//user click cancel or close the upload window
}
});
e.target.files