Semuanya mengalir, semuanya berubah, tetapi hanya 
input[type=file] karena merusak saraf semua pengembang web pemula, dan terus melakukannya hingga sekarang. Ingat diri Anda N tahun yang lalu, ketika Anda baru saja mulai memahami dasar-dasar membuat situs web. Muda dan tidak berpengalaman, Anda benar-benar terkejut ketika tombol pemilihan file benar-benar menolak untuk mengubah warna latar belakang ke persik favorit Anda. Pada saat itulah Anda pertama kali menemukan gunung es yang tidak bisa dihancurkan ini yang disebut “Mengunduh File,” yang hingga hari ini terus “menenggelamkan” pengembang web pemula.
Menggunakan contoh membuat bidang unggahan file, saya akan menunjukkan kepada Anda cara menyembunyikan 
input[type=file] dengan benar, mengatur fokus pada objek yang tidak dapat memiliki fokus, menangani acara Seret-dan-Letakkan dan mengirim file melalui AJAX. Dan saya juga akan memperkenalkan Anda dengan beberapa bug browser dan cara untuk mengatasinya. Artikel ini ditulis untuk pemula, tetapi di beberapa titik dapat bermanfaat dan menghibur bahkan untuk pengembang berpengalaman.
Markup dan gaya primer
Mari kita mulai dengan markup HTML:
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>  ,   </title> <link rel="stylesheet" href="style.css"> <script type="text/javascript" src="jquery-3.3.1.min.js"></script> <script type="text/javascript" src="script.js"></script> </head> <body> <form id="upload-container" method="POST" action="send.php"> <img id="upload-image" src="upload.svg"> <div> <input id="file-input" type="file" name="file" multiple> <label for="file-input"> </label> <span>   </span> </div> </form> </body> </html> 
Mungkin elemen utama yang harus Anda perhatikan adalah
 <label for="file-input"> </label> 
Spesifikasi HTML tidak memungkinkan kami untuk memaksakan properti visual secara langsung pada 
input[type=file] , tetapi kami memiliki tag 
label , mengklik yang menyebabkan klik pada elemen formulir yang dilampirkan. Yang menggembirakan kami, tag ini tidak memiliki batasan dalam stilisasi: kami dapat melakukan apa pun yang kami inginkan dengannya.
Sebuah rencana aksi muncul: kami menyesuaikan label sesuka kami, dan menyembunyikan 
input[type=file] dari pandangan. Pertama, atur gaya halaman umum:
 body { padding: 0; margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; } #upload-container { display: flex; justify-content: center; align-items: center; flex-direction: column; width: 400px; height: 400px; outline: 2px dashed #5d5d5d; outline-offset: -12px; background-color: #e0f2f7; font-family: 'Segoe UI'; color: #1f3c44; } #upload-container img { width: 40%; margin-bottom: 20px; user-select: none; } 
Sekarang gaya label kami:
 #upload-container label { font-weight: bold; } #upload-container label:hover { cursor: pointer; text-decoration: underline; } 
Apa yang kami perjuangkan ( 
input[type=file] dihapus dari markup):

Tentu saja, Anda dapat memusatkan label, menambahkan latar belakang dan perbatasan, mendapatkan tombol penuh, tetapi prioritas kami adalah Drag-and-Drop.
Menyembunyikan input
Sekarang kita perlu menyembunyikan 
input[type=file] . Hal pertama yang mengejutkan kepala adalah 
display: none dan 
visibility: hidden properti 
visibility: hidden . Tapi ini tidak sesederhana itu. Pada beberapa browser lama, mengklik label tidak akan berpengaruh lagi. Tapi itu belum semuanya. Seperti yang Anda ketahui, elemen tak terlihat tidak dapat menerima fokus, dan apa pun yang mereka katakan, fokus itu penting, karena bagi sebagian orang ini adalah satu-satunya cara untuk berinteraksi dengan situs. Jadi metode ini tidak cocok untuk kita. Mari kita berkeliling:
 #upload-container div { position: relative; z-index: 10; } #upload-container input[type=file] { width: 0.1px; height: 0.1px; opacity: 0; position: absolute; z-index: -10; } 
Kami benar-benar memposisikan 
input[type=file] kami 
input[type=file] relatif terhadap blok induknya, menguranginya menjadi 
0.1px , membuatnya transparan dan mengatur 
z-index lebih kecil dari pada induknya, sehingga, tentu saja, tentu saja.
Selamat, kami mencapai apa yang kami inginkan: bidang kami terlihat persis seperti pada gambar sebelumnya.
Sesuaikan fokus
Karena 
input[type=file] kami 
input[type=file] fisik ada di halaman, ia memiliki kemampuan untuk menerima fokus. Artinya, jika kita menekan tombol 
Tab pada halaman, maka pada titik tertentu fokus akan beralih ke 
input[type=file] . Tetapi masalahnya adalah kita tidak akan melihat ini: bidang yang kita sembunyikan akan menonjol. Ya, jika saat ini kita menekan 
Enter , kotak dialog akan terbuka dan semuanya akan berfungsi sebagaimana mestinya, tetapi bagaimana kita memahami bahwa ini saatnya untuk menekan?
Tugas kami adalah memilih tanda dengan cara tertentu pada saat fokusnya adalah pada bidang unggah file. Tetapi bagaimana kita dapat melakukan ini jika tag tidak dapat menerima fokus? Penikmat CSS3 akan segera berpikir tentang pseudo-class 
:focus , yang mendefinisikan gaya untuk elemen dalam fokus, dan 
+ atau 
~ penyeleksi yang memilih tetangga yang tepat: elemen yang terletak di tingkat bersarang yang sama, datang setelah elemen yang dipilih. Menimbang bahwa dalam 
input[type=file] markup kami 
input[type=file] terletak tepat di depan tag 
label , entri berikut terjadi:
 #upload-container input[type=file]:focus + label {  } 
Tapi sekali lagi, tidak sesederhana itu. Untuk memulai, mari kita bahas bagaimana kita harus memilih label. Seperti yang Anda ketahui, semua browser modern dan bukan jadi memiliki properti default unik untuk elemen-elemen dalam fokus. Pada dasarnya, ini adalah properti 
outline , yang menciptakan goresan di sekitar elemen yang berbeda dari 
border karena tidak mengubah ukuran elemen dan dapat dipindahkan dari situ. Sebagai aturan, orang hanya menggunakan satu browser, sehingga mereka terbiasa dengan standarnya. Untuk mempermudah orang menavigasi situs kami, kami harus mencoba menyesuaikan fokus sehingga terlihat sealami mungkin untuk sebagian besar browser modern yang populer. Secara teori, menggunakan JavaScript, Anda bisa mendapatkan informasi tentang browser tempat pengguna membuka situs tersebut, dan menyesuaikan gaya yang sesuai, tetapi topik ini terlalu rumit dan rumit sebagai bagian dari artikel yang ditujukan terutama untuk pemula. Kami akan mencoba bertahan dengan sedikit darah.
Di browser yang didasarkan pada mesin WebKet (Google Chrome, Opera, Safari), properti default untuk elemen dalam fokus adalah dalam bentuk:
 :focus { outline: -webkit-focus-ring-color auto 5px; } 
Di sini 
-webkit-focus-ring-color adalah 
-webkit-focus-ring-color fokus-stroke khusus untuk mesin ini saja. Yaitu, baris ini akan bekerja secara eksklusif di browser WebKit, dan inilah yang kami butuhkan. Tentukan properti ini untuk label kami:
 #upload-container input[type=file]:focus + label { outline: -webkit-focus-ring-color auto 5px; } 
Kami membuka Google Chrome atau Opera, kami melihat. Semuanya berfungsi sebagaimana mestinya:

Mari kita lihat bagaimana semuanya dengan fokus di Mozilla Firefox dan Microsoft Edge. Untuk browser ini, properti default adalah:
 :focus { outline: 1px solid #0078d7; } 
dan
 :focus { outline: 1px solid #212121; } 
sesuai.
Sayangnya, awalan 
-moz- tidak akan berfungsi dengan properti 
outline . Karena itu, kita harus memilih mana dari dua properti yang kita pilih. Karena jumlah pengguna Firefox jauh lebih tinggi, itu lebih rasional untuk memberikan preferensi ke browser khusus ini. Ini tidak berarti bahwa kami menghilangkan kesempatan bagi pengguna Edge dan browser lain untuk melihat di mana fokusnya sekarang, itu hanya akan terlihat “berbeda” dari mereka. Nah, Anda harus berkorban.
Kami menambahkan gaya dari Mozilla Firefox sebelum gaya untuk WebKit: pertama semua browser akan menerapkan properti pertama, dan yang dapat (Google Chrome, Opera, Safari, dll.) Akan menerapkan yang kedua.
 #upload-container input[type=file]:focus + label { outline: 1px solid #0078d7; outline: -webkit-focus-ring-color auto 5px; } 
Dan di sini mulai aneh: di Edge semuanya berfungsi dengan baik, tetapi Firefox untuk beberapa alasan yang tidak diketahui menolak untuk menerapkan properti ke label dengan fokus pada 
input[type=file] . Dan acara 
focus itu sendiri terjadi - diperiksa melalui JavaScript. Selain itu, jika Anda memaksakan fokus pada bidang pemilihan file melalui alat pengembang, maka properti akan berlaku dan langkah kami akan muncul! Rupanya, ini adalah bug dari browser itu sendiri, tetapi jika seseorang memiliki ide mengapa ini terjadi - tulis di komentar.
Yah, tidak ada, pahlawan normal selalu berkeliling. Seperti yang saya katakan sebelumnya, acara 
focus terjadi, yang berarti bahwa kita dapat menyesuaikan properti langsung dari JavaScript. Tetapi untuk ini kita harus mengubah logika pemilih kita:
 #upload-container label.focus { outline: 1px solid #0078d7; outline: -webkit-focus-ring-color auto 5px; } 
Kami akan menjelaskan kelas 
.focus untuk label kami dan kami akan menambahkannya setiap kali ketika 
input[type=file] menerima fokus dan menghapusnya ketika kehilangan.
 $('#file-input').focus(function() { $('label').addClass('focus'); }) .focusout(function() { $('label').removeClass('focus'); }); 
Sekarang semuanya berjalan sebagaimana mestinya. Selamat, kami telah menemukan fokus.
Seret dan lepas
Bekerja dengan Drag-and-Drop dilakukan dengan melacak peristiwa browser khusus: 
drag, dragstart, dragend, dragover, dragenter, dragleave, drop . Anda dapat dengan mudah menemukan deskripsi terperinci masing-masing di Internet. Kami hanya akan melacak beberapa dari mereka.
Pertama, tentukan elemen seret dan lepas:
 var dropZone = $('#upload-container'); 
Kemudian kami menjelaskan dalam CSS sebuah kelas khusus yang akan kami tetapkan 
dropZone ketika kursor menarik file tepat di atasnya. Ini diperlukan untuk memberi tahu pengguna secara visual bahwa file tersebut sudah dapat dirilis.
 #upload-container.dragover { background-color: #fafafa; outline-offset: -17px; } 
Sekarang mari kita pindah ke file JS. Untuk memulai, kita perlu membatalkan semua tindakan default untuk acara Drag-and-Drop. Sebagai contoh, salah satu dari peristiwa tersebut adalah pembukaan file yang dilemparkan oleh browser. Kami tidak membutuhkan ini sama sekali, jadi kami akan menulis baris berikut:
 dropZone.on('drag dragstart dragend dragover dragenter dragleave drop', function(){ return false; }); 
Di jQuery, memanggil 
return false sama dengan memanggil dua fungsi sekaligus: 
e.preventDefault() dan 
e.stopPropagation() .
Kami mulai menggambarkan event handler kami sendiri. Kami akan bertindak dengan cara yang sama seperti yang kami lakukan dengan fokus, tetapi kali ini kami akan melacak peristiwa 
dragenter dan 
dragover untuk menambahkan kelas dan acara 
dragleave untuk menghapusnya:
 dropZone.on('dragover dragenter', function() { dropZone.addClass('dragover'); }); dropZone.on('dragleave', function(e) { dropZone.removeClass('dragover'); }); 
Dan lagi, kejutan yang tidak menyenangkan menunggu kami: ketika Anda bergerak bersama 
dropZone dengan mouse dengan file, bidang mulai berkedip. Ini terjadi di browser Microsoft Edge dan WebKit. Ngomong-ngomong, sebagian besar browser WebKit ini sedang berjalan di mesin Blink (menghargai ironi, ya?). Tapi di Mozilla, tidak ada yang berkedip. Rupanya, saya memutuskan untuk memperbaikinya setelah fokus bug.
Dan kerlipan ini terjadi karena fakta bahwa ketika Anda mengarahkan kursor ke 
dropZone , baik itu gambar atau 
div dengan bidang pemilihan file dan label, untuk beberapa alasan acara 
dragleave . Jelas bagi kami bahwa kami tidak meninggalkan lapangan, tetapi browser, untuk beberapa alasan, tidak, dan karena ini mereka 
.focus - 
.focus menghapus kelas 
dropZone dari 
dropZone .
Dan lagi, kita harus keluar entah bagaimana. Jika peramban itu sendiri tidak mengerti bahwa kami tidak meninggalkan lapangan, kami harus membantunya. Dan kami akan melakukan ini melalui kondisi tambahan: kami menghitung koordinat tetikus relatif ke 
dropZone , dan kemudian memeriksa apakah kursor berada di luar blok. Jika dibiarkan, maka kami menghapus gaya:
 dropZone.on('dragleave', function(e) { let dx = e.pageX - dropZone.offset().left; let dy = e.pageY - dropZone.offset().top; if ((dx < 0) || (dx > dropZone.width()) || (dy < 0) || (dy > dropZone.height())) { dropZone.removeClass('dragover'); }; }); 
Dan semuanya, masalahnya selesai! Seperti inilah bidang kami dengan file di dalamnya:

Mari kita beralih ke menangani event 
drop itu sendiri. Tetapi pertama-tama, ingatlah bahwa, selain Drag-and-Drop, kami memiliki 
input[type=file] , dan masing-masing metode ini independen dalam esensinya, tetapi harus melakukan tindakan yang sama: mengunggah file. Oleh karena itu, saya mengusulkan untuk membuat fungsi terpisah universal untuk kedua metode, di mana kami akan mentransfer file, dan itu sudah akan memutuskan apa yang harus dilakukan dengan mereka. Kami akan menyebutnya 
sendFiles() , tetapi kami akan menjelaskannya sedikit nanti. Untuk memulai, tangani acara 
drop :
 dropZone.on('drop', function(e) { dropZone.removeClass('dragover'); let files = e.originalEvent.dataTransfer.files; sendFiles(files); }); 
Pertama, 
.dragover hapus kelas 
dropZone dari 
dropZone . Kemudian kita mendapatkan array yang berisi file-file. Jika Anda menggunakan jQuery, maka 
e.originalEvent.dataTransfer.files adalah 
e.originalEvent.dataTransfer.files , jika Anda menulis dalam JS murni, maka 
e.dataTransfer.files . Baiklah, lalu kita meneruskan array ke fungsi kita yang belum terealisasi.
Sekarang kita akan menyelesaikan metode pemuatan melalui 
input[type=file] :
 $('#file-input').change(function() { let files = this.files; sendFiles(files); }); 
Kami melacak 
change acara pada tombol pemilihan file, kami mendapatkan array melalui 
this.files dan mengirimkannya ke fungsi.
Mengirim file melalui AJAX
Langkah terakhir - deskripsi fungsi pemrosesan file - adalah unik untuk semua orang. Ini akan langsung tergantung pada tujuan yang Anda kejar. Sebagai contoh, saya akan menunjukkan cara mengirim file ke server melalui AJAX.
Misalkan kita membuat bidang untuk mengunggah foto. Kami tidak ingin hal lain sampai ke server kami, jadi kami akan memutuskan jenis file: biarlah PNG dan JPEG. Anda juga perlu mengatur ukuran maksimum satu foto yang dapat dikirim pengguna. Terbatas hingga lima megabita. Kami mulai menggambarkan fungsi kami:
 function sendFiles(files) { let maxFileSize = 5242880; let Data = new FormData(); $(files).each(function(index, file) { if ((file.size <= maxFileSize) && ((file.type == 'image/png') || (file.type == 'image/jpeg'))) { Data.append('images[]', file); } }); }; 
Dalam variabel 
maxFileSize ukuran file maksimum yang akan kami kirim ke server. 
FormData() , kita akan membuat objek baru dari kelas 
FormData , yang memungkinkan kita untuk menghasilkan set pasangan nilai kunci. Objek seperti itu dapat dengan mudah dikirim melalui AJAX. Selanjutnya, kita menggunakan jQuery 
.each construct untuk array 
files , yang akan menerapkan fungsi yang kita atur untuk setiap elemennya. Jumlah elemen dan elemen itu sendiri akan diteruskan sebagai argumen ke fungsi, yang akan kami proses masing-masing sebagai 
index dan 
file . Dalam fungsi itu sendiri, kami akan memeriksa file terhadap kriteria kami: ukurannya kurang dari lima megabyte, dan tipenya PNG atau JPEG. Jika file lulus tes, lalu tambahkan ke objek 
FormData kami dengan memanggil fungsi 
append() . Kuncinya adalah string 
'photos[]' , tanda kurung di ujung yang menunjukkan bahwa itu adalah array di mana ada beberapa objek. Objek itu sendiri akan berupa 
file .
Sekarang semuanya siap untuk mengirim file melalui AJAX. Tambahkan baris berikut ke fungsi kami:
 $.ajax({ url: dropZone.attr('action'), type: dropZone.attr('method'), data: Data, contentType: false, processData: false, success: function(data) { alert('   '); } }); 
Sebagai 
url dan 
type parameter 
type kami mengindikasikan nilai atribut 
action dan 
method dari 
input[type=file] masing-masing. Untuk melewati AJAX kita akan menjadi objek 
Data . Parameter 
contentType: false dan 
processData: false diperlukan agar browser tidak secara tidak sengaja menerjemahkan file kami ke dalam beberapa format lain. Dalam parameter 
success , kami menentukan fungsi yang akan dieksekusi jika file berhasil ditransfer ke server. Isinya tergantung pada imajinasi Anda, tetapi saya akan membatasi diri pada output sederhana dari sebuah pesan tentang unduhan yang berhasil.
Selamat, sekarang Anda dapat membuat bidang unggahan file Anda sendiri! Tentu saja, saya tidak memposisikan metode saya sebagai satu-satunya yang benar dan yang benar. Tujuan saya adalah menunjukkan jalan umum untuk menyelesaikan masalah ini, terutama cocok untuk pemula. Jika Anda berpikir bahwa di suatu tempat Anda bisa melakukan sesuatu yang lebih baik - tulis di komentar, kami akan membahas!
Itu saja. Terima kasih atas perhatian anda!
Unduh:
- Versi final
- Masalah fokus
- Masalah berkedip-kedip
Sentuh:
- Versi final
- Masalah fokus
- Masalah berkedip-kedip