วิธีการสร้างไฟล์อัพโหลดแบบลากและวางด้วย JavaScript วานิลลา

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ ในบทความนี้ เราจะใช้ “vanilla” ES2015+ JavaScript (ไม่มีเฟรมเวิร์กหรือไลบรารี) เพื่อทำให้โปรเจ็กต์นี้เสร็จสมบูรณ์ และถือว่าคุณมีความรู้ด้าน JavaScript ในเบราว์เซอร์ ตัวอย่างนี้ควรเข้ากันได้กับทุกเบราว์เซอร์ที่เขียวชอุ่มตลอดรวมถึง IE 10 และ 11

เป็นที่ทราบกันดีอยู่แล้วว่าอินพุตสำหรับการเลือกไฟล์นั้นยากต่อการจัดรูปแบบตามที่นักพัฒนาต้องการ หลายคนจึงซ่อนไฟล์และสร้างปุ่มที่เปิดกล่องโต้ตอบการเลือกไฟล์แทน แม้ว่าในปัจจุบันนี้ เรามีวิธีจัดการการเลือกไฟล์ที่สนุกยิ่งขึ้นไปอีก นั่นคือ การลากและวาง

ในทางเทคนิค สิ่งนี้เป็นไปได้แล้วเนื่องจากการใช้งานอินพุตการเลือกไฟล์ส่วนใหญ่ (ถ้าไม่ใช่ ทั้งหมด ) อนุญาตให้คุณลากไฟล์ไปทับเพื่อเลือกไฟล์เหล่านั้น แต่สิ่งนี้ต้องการให้คุณแสดงองค์ประกอบ file จริงๆ ลองใช้ API ที่เบราว์เซอร์มอบให้เราเพื่อใช้งานตัวเลือกไฟล์แบบลากแล้ววางและตัวอัปโหลด

ในบทความนี้ เราจะใช้ “vanilla” ES2015+ JavaScript (ไม่มีเฟรมเวิร์กหรือไลบรารี) เพื่อทำให้โปรเจ็กต์นี้เสร็จสมบูรณ์ และถือว่าคุณมีความรู้ด้าน JavaScript ในเบราว์เซอร์ ตัวอย่างนี้ — นอกเหนือจากไวยากรณ์ ES2015+ ซึ่งสามารถเปลี่ยนเป็นไวยากรณ์ ES5 ได้อย่างง่ายดายหรือเปลี่ยนโดย Babel — ควรเข้ากันได้กับทุกเบราว์เซอร์ที่ใช้งานได้ตลอดรวมถึง IE 10 และ 11

ต่อไปนี้คือภาพรวมคร่าวๆ ว่าคุณจะทำอะไรได้บ้าง:

การดำเนินการอัปโหลดรูปภาพแบบลากแล้ววาง
การสาธิตหน้าเว็บที่คุณสามารถอัปโหลดรูปภาพผ่านการลากและวาง ดูตัวอย่างรูปภาพที่กำลังอัปโหลดทันที และดูความคืบหน้าของการอัปโหลดในแถบความคืบหน้า

กิจกรรมลากแล้ววาง

สิ่งแรกที่เราต้องพูดถึงคือเหตุการณ์ที่เกี่ยวข้องกับการลากแล้วปล่อยเนื่องจากเป็นแรงผลักดันที่อยู่เบื้องหลังคุณลักษณะนี้ โดยรวมแล้ว มีแปดเหตุการณ์ที่เบราว์เซอร์เรียกใช้ที่เกี่ยวข้องกับการลากและวาง: drag , dragend , dragenter , dragexit , dragleave , dragover , dragstart และ drop เราจะไม่พูดถึงมันทั้งหมดเพราะการ drag , dragend , dragexit และ dragstart นั้นทำงานบนองค์ประกอบที่กำลังถูกลาก และในกรณีของเรา เราจะลากไฟล์เข้ามาจากระบบไฟล์ของเราแทนที่จะเป็นองค์ประกอบ DOM ดังนั้นกิจกรรมเหล่านี้จะไม่ปรากฏขึ้น

หากคุณสงสัยเกี่ยวกับเหตุการณ์เหล่านี้ คุณสามารถอ่านเอกสารเกี่ยวกับเหตุการณ์เหล่านี้ได้ใน MDN

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

อย่างที่คุณคาดไว้ คุณสามารถลงทะเบียนตัวจัดการเหตุการณ์สำหรับเหตุการณ์เหล่านี้ในลักษณะเดียวกับที่คุณลงทะเบียนตัวจัดการเหตุการณ์สำหรับกิจกรรมเบราว์เซอร์ส่วนใหญ่: ผ่าน addEventListener

 let dropArea = document.getElementById('drop-area') dropArea.addEventListener('dragenter', handlerFunction, false) dropArea.addEventListener('dragleave', handlerFunction, false) dropArea.addEventListener('dragover', handlerFunction, false) dropArea.addEventListener('drop', handlerFunction, false)

นี่คือตารางเล็กๆ ที่อธิบายว่าเหตุการณ์เหล่านี้ทำอะไร โดยใช้ dropArea จากตัวอย่างโค้ดเพื่อทำให้ภาษาชัดเจนขึ้น:

เหตุการณ์ เมื่อไหร่ที่มันถูกไล่ออก?
dragenter รายการที่ลากจะถูกลากไปบน dropArea ทำให้เป็นเป้าหมายสำหรับเหตุการณ์การดรอปหากผู้ใช้วางที่นั่น
dragleave รายการที่ลากจะถูกลากออกจาก dropArea และไปยังองค์ประกอบอื่น ทำให้เป็นเป้าหมายสำหรับเหตุการณ์การดรอปแทน
dragover ทุกๆ สองสามร้อยมิลลิวินาที ในขณะที่รายการที่ลากอยู่เหนือ dropArea และกำลังเคลื่อนที่
drop ผู้ใช้ปล่อยปุ่มเมาส์ วางรายการที่ลากไปยัง dropArea

โปรดทราบว่ารายการที่ลากจะถูกลากไปทับรายการย่อยของ dropArea , dragleave จะยิงบน dropArea และ dragenter จะยิงไปที่องค์ประกอบย่อยนั้นเนื่องจากเป็น target ใหม่ เหตุการณ์การดรอปจะขยายไป dropArea drop เว้นแต่การขยายจะถูกหยุดโดยผู้ฟังเหตุการณ์อื่นก่อนที่จะไปถึงที่นั่น) ดังนั้นมันจะยังคงเริ่มทำงานบน dropArea แม้ว่าจะไม่ใช่ target ของกิจกรรมก็ตาม

นอกจากนี้ โปรดทราบด้วยว่าในการสร้างการโต้ตอบแบบลากและวางที่กำหนดเอง คุณจะต้องเรียก event.preventDefault() ในตัวฟังแต่ละตัวสำหรับเหตุการณ์เหล่านี้ หากไม่ทำเช่นนั้น เบราว์เซอร์จะเปิดไฟล์ที่คุณทิ้งแทนที่จะ drop ไปที่ตัวจัดการเหตุการณ์การดรอป

การตั้งค่าแบบฟอร์มของเรา

ก่อนที่เราจะเริ่มเพิ่มฟังก์ชันการลากและวาง เราจำเป็นต้องมีรูปแบบพื้นฐานที่มีอินพุต file มาตรฐาน ในทางเทคนิคไม่จำเป็น แต่ควรจัดหาเป็นทางเลือกในกรณีที่ผู้ใช้มีเบราว์เซอร์ที่ไม่รองรับ API แบบลากแล้ววาง

 <div> <form class="my-form"> <p>Upload multiple files with the file dialog or by dragging and dropping images onto the dashed region</p> <input type="file" multiple accept="image/*" onchange="handleFiles(this.files)"> <label class="button" for="fileElem">Select some files</label> </form> </div>

โครงสร้างค่อนข้างง่าย คุณอาจสังเกตเห็นตัวจัดการ onchange บน input เราจะมาดูกันในภายหลัง จะเป็นความคิดที่ดีที่จะเพิ่มการ action ลงใน form และปุ่ม submit เพื่อช่วยเหลือผู้ที่ไม่ได้เปิดใช้งาน JavaScript จากนั้นคุณสามารถใช้ JavaScript เพื่อกำจัดมันเพื่อให้มีรูปแบบที่สะอาดขึ้น ไม่ว่าในกรณีใด คุณ จะ ต้องมีสคริปต์ฝั่งเซิร์ฟเวอร์เพื่อยอมรับการอัปโหลด ไม่ว่าจะเป็นสิ่งที่พัฒนาขึ้นภายในองค์กร หรือคุณกำลังใช้บริการเช่น Cloudinary เพื่อทำสิ่งนั้นให้คุณ นอกจากโน้ตเหล่านั้น ไม่มีอะไรพิเศษในที่นี้ ลองใส่สไตล์บางอย่างลงใน:

 #drop-area { border: 2px dashed #ccc; border-radius: 20px; width: 480px; font-family: sans-serif; margin: 100px auto; padding: 20px; } #drop-area.highlight { border-color: purple; } p { margin-top: 0; } .my-form { margin-bottom: 10px; } #gallery { margin-top: 10px; } #gallery img { width: 150px; margin-bottom: 10px; margin-right: 10px; vertical-align: middle; } .button { display: inline-block; padding: 10px; background: #ccc; cursor: pointer; border-radius: 5px; border: 1px solid #ccc; } .button:hover { background: #ddd; } #fileElem { display: none; }

หลายสไตล์เหล่านี้ยังไม่มีให้เล่น แต่ก็ไม่เป็นไร ในตอนนี้ ไฮไลท์อยู่ที่การป้อน file ถูกซ่อนไว้ แต่ label ถูกจัดรูปแบบให้ดูเหมือนปุ่ม ดังนั้นผู้คนจะรู้ว่าสามารถคลิกไฟล์นั้นเพื่อเปิดกล่องโต้ตอบการเลือกไฟล์ได้ เรากำลังดำเนินการตามข้อตกลงโดยสรุปพื้นที่ดรอปด้วยเส้นประ

การเพิ่มฟังก์ชันการลากและวาง

ตอนนี้เรามาถึงเนื้อของสถานการณ์แล้ว: ลากและวาง ให้ใส่สคริปต์ที่ด้านล่างของหน้าหรือในไฟล์แยกกัน แล้วแต่คุณจะรู้สึกอยากทำ สิ่งแรกที่เราต้องการในสคริปต์คือการอ้างอิงถึงพื้นที่ดรอปเพื่อให้เราสามารถแนบเหตุการณ์บางอย่างกับมันได้:

 let dropArea = document.getElementById('drop-area')

ตอนนี้ขอเพิ่มบางเหตุการณ์ เราจะเริ่มต้นด้วยการเพิ่มตัวจัดการให้กับเหตุการณ์ทั้งหมด เพื่อป้องกันพฤติกรรมเริ่มต้นและหยุดเหตุการณ์ไม่ให้เดือดดาลเกินความจำเป็น:

 ;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropArea.addEventListener(eventName, preventDefaults, false) }) function preventDefaults (e) { e.preventDefault() e.stopPropagation() }

ตอนนี้ มาเพิ่มตัวบ่งชี้เพื่อแจ้งให้ผู้ใช้ทราบว่าพวกเขาได้ลากรายการไปบนพื้นที่ที่ถูกต้องแล้วโดยใช้ CSS เพื่อเปลี่ยนสีของสีเส้นขอบของพื้นที่ดรอป สไตล์ควรมีอยู่แล้วภายใต้ตัวเลือก #drop-area.highlight ดังนั้นให้ใช้ JS เพื่อเพิ่มและลบคลาส highlight นั้นเมื่อจำเป็น

 ;['dragenter', 'dragover'].forEach(eventName => { dropArea.addEventListener(eventName, highlight, false) }) ;['dragleave', 'drop'].forEach(eventName => { dropArea.addEventListener(eventName, unhighlight, false) }) function highlight(e) { dropArea.classList.add('highlight') } function unhighlight(e) { dropArea.classList.remove('highlight') }

เราต้องใช้ทั้ง dragenter และ dragover สำหรับการเน้นเนื่องจากสิ่งที่ฉันได้กล่าวไว้ก่อนหน้านี้ หากคุณเริ่มวางเมาส์เหนือ dropArea แล้ววางเมาส์ไว้เหนือลูกๆ ของมัน จากนั้น dragleave จะถูกไล่ออกและไฮไลต์จะถูกลบออก เหตุการณ์ dragover เริ่มทำงานหลังจากเหตุการณ์ dragenter และ dragleave ดังนั้นไฮไลต์จะถูกเพิ่มกลับเข้าไปใน dropArea ก่อนที่เราจะเห็นว่าจะถูกลบออก

นอกจากนี้เรายังลบไฮไลท์เมื่อรายการที่ลากออกจากพื้นที่ที่กำหนดหรือเมื่อคุณวางรายการ

ตอนนี้ สิ่งที่เราต้องทำคือหาว่าต้องทำอย่างไรเมื่อไฟล์บางไฟล์หลุด:

 dropArea.addEventListener('drop', handleDrop, false) function handleDrop(e) { let dt = e.dataTransfer let files = dt.files handleFiles(files) }

การดำเนินการนี้ไม่ได้ทำให้เราใกล้จะเสร็จสมบูรณ์ แต่ทำสิ่งสำคัญสองประการ:

  1. สาธิตวิธีการรับข้อมูลสำหรับไฟล์ที่ถูกทิ้ง
  2. นำเราไปยังที่เดียวกับที่ input file อยู่ที่ตัวจัดการ onchange : รอ handleFiles

โปรดทราบว่า files ไม่ใช่อาร์เรย์ แต่เป็น FileList ดังนั้น เมื่อเราใช้งาน handleFiles เราจะต้องแปลงเป็นอาร์เรย์เพื่อให้ทำซ้ำได้ง่ายขึ้น:

 function handleFiles(files) { ([...files]).forEach(uploadFile) }

นั่นเป็นการต่อต้านไคลแมกซ์ เข้าสู่การ uploadFile สำหรับสิ่งที่มีเนื้อ จริง

 function uploadFile(file) { let url = 'YOUR URL HERE' let formData = new FormData() formData.append('file', file) fetch(url, { method: 'POST', body: formData }) .then(() => { /* Done. Inform the user */ }) .catch(() => { /* Error. Inform the user */ }) }

ที่นี่เราใช้ FormData ซึ่งเป็น API ของเบราว์เซอร์ในตัวสำหรับสร้างข้อมูลแบบฟอร์มเพื่อส่งไปยังเซิร์ฟเวอร์ จากนั้นเราใช้การ fetch API เพื่อส่งภาพไปยังเซิร์ฟเวอร์จริง ตรวจสอบให้แน่ใจว่าคุณเปลี่ยน URL เพื่อทำงานกับส่วนหลังหรือบริการของคุณ และ formData.append ข้อมูลแบบฟอร์มเพิ่มเติมใดๆ ที่คุณอาจต้องใช้เพื่อให้เซิร์ฟเวอร์มีข้อมูลทั้งหมดที่จำเป็น หรือหากคุณต้องการสนับสนุน Internet Explorer คุณอาจต้องการใช้ XMLHttpRequest ซึ่งหมายความว่า uploadFile จะมีลักษณะดังนี้:

 function uploadFile(file) { var url = 'YOUR URL HERE' var xhr = new XMLHttpRequest() var formData = new FormData() xhr.open('POST', url, true) xhr.addEventListener('readystatechange', function(e) { if (xhr.readyState == 4 && xhr.status == 200) { // Done. Inform the user } else if (xhr.readyState == 4 && xhr.status != 200) { // Error. Inform the user } }) formData.append('file', file) xhr.send(formData) }

คุณอาจต้องตรวจสอบช่วงหมายเลข status ต่างๆ มากกว่าแค่ 200 ทั้งนี้ขึ้นอยู่กับวิธีตั้งค่าเซิร์ฟเวอร์ของคุณ แต่วิธีนี้ใช้ได้ผลสำหรับจุดประสงค์ของเรา

คุณลักษณะเพิ่มเติม

นั่นคือฟังก์ชันพื้นฐานทั้งหมด แต่บ่อยครั้งที่เราต้องการฟังก์ชันเพิ่มเติม โดยเฉพาะอย่างยิ่ง ในบทช่วยสอนนี้ เราจะเพิ่มบานหน้าต่างแสดงตัวอย่างที่แสดงรูปภาพที่เลือกทั้งหมดแก่ผู้ใช้ จากนั้นเราจะเพิ่มแถบความคืบหน้าที่ช่วยให้ผู้ใช้เห็นความคืบหน้าของการอัปโหลด มาเริ่มกันด้วยการแสดงตัวอย่างรูปภาพกัน

ภาพตัวอย่าง

มีสองวิธีที่คุณสามารถทำได้: คุณสามารถรอจนกว่ารูปภาพจะถูกอัปโหลดและขอให้เซิร์ฟเวอร์ส่ง URL ของรูปภาพ แต่นั่นหมายความว่าคุณต้องรอและรูปภาพอาจมีขนาดใหญ่ในบางครั้ง ทางเลือกที่เรากำลังจะสำรวจในวันนี้คือการใช้ FileReader API กับข้อมูลไฟล์ที่เราได้รับจากเหตุการณ์การ drop อป นี่เป็นแบบอะซิงโครนัส และคุณสามารถใช้ FileReaderSync ได้ แต่เราอาจพยายามอ่านไฟล์ขนาดใหญ่หลาย ๆ ไฟล์ติดต่อกัน ดังนั้นสิ่งนี้จึงสามารถบล็อกเธรดได้ชั่วขณะหนึ่งและทำลายประสบการณ์การใช้งานจริง ๆ มาสร้างฟังก์ชัน previewFile และดูว่ามันทำงานอย่างไร:

 function previewFile(file) { let reader = new FileReader() reader.readAsDataURL(file) reader.onloadend = function() { let img = document.createElement('img') img.src = reader.result document.getElementById('gallery').appendChild(img) } }

ที่นี่เราสร้าง new FileReader และเรียก readAsDataURL ด้วยวัตถุ File ดังที่กล่าวไว้ นี่เป็นแบบอะซิงโครนัส ดังนั้นเราจึงจำเป็นต้องเพิ่มตัวจัดการเหตุการณ์ onloadend เพื่อให้ได้ผลลัพธ์ของการอ่าน จากนั้น เราใช้ URL ข้อมูลฐาน 64 เป็น src สำหรับองค์ประกอบรูปภาพใหม่และเพิ่มลงในองค์ประกอบ gallery มีเพียงสองอย่างที่ต้องทำเพื่อดำเนินการนี้: เพิ่มองค์ประกอบ gallery และตรวจสอบให้แน่ใจ previewFile มีการเรียกไฟล์ตัวอย่าง

ขั้นแรก เพิ่ม HTML ต่อไปนี้หลังส่วนท้ายของแท็ก form :

 <div></div>

ไม่มีอะไรพิเศษ; มันเป็นแค่ div มีการระบุสไตล์สำหรับมันและรูปภาพในนั้นแล้ว ดังนั้นจึงไม่มีอะไรเหลือให้ทำ ตอนนี้เรามาเปลี่ยนฟังก์ชัน handleFiles ดังต่อไปนี้:

 function handleFiles(files) { files = [...files] files.forEach(uploadFile) files.forEach(previewFile) }

มีสองสามวิธีที่คุณสามารถทำได้ เช่น การเรียบเรียง หรือการเรียกกลับครั้งเดียวไปยัง forEach ที่รัน uploadFile และ previewFile ในนั้น แต่สิ่งนี้ก็ใช้ได้เช่นกัน และด้วยเหตุนี้ เมื่อคุณวางหรือเลือกภาพบางภาพ ภาพเหล่านั้นควรปรากฏขึ้นใต้แบบฟอร์มเกือบจะในทันที สิ่งที่น่าสนใจเกี่ยวกับเรื่องนี้ก็คือ — ในบางแอพพลิเคชั่น — คุณอาจไม่ต้องการอัพโหลดภาพจริง ๆ แต่เก็บ URL ข้อมูลของพวกมันไว้ใน localStorage หรือแคชฝั่งไคลเอนต์อื่น ๆ เพื่อเข้าถึงโดยแอพในภายหลัง ส่วนตัวฉันไม่สามารถนึกถึงกรณีการใช้งานที่ดีสำหรับสิ่งนี้ แต่ฉันยินดีที่จะเดิมพันว่ามีบางกรณี

ติดตามความคืบหน้า

หากบางสิ่งอาจใช้เวลาสักครู่ แถบความคืบหน้าสามารถช่วยให้ผู้ใช้ทราบว่ามีความคืบหน้าจริง และระบุระยะเวลาที่ต้องดำเนินการให้เสร็จสิ้น การเพิ่มตัวบ่งชี้ความคืบหน้านั้นค่อนข้างง่ายด้วยแท็ก progress HTML5 เริ่มต้นด้วยการเพิ่มสิ่งนั้นลงในโค้ด HTML ในครั้งนี้

 <progress max=100 value=0></progress>

คุณสามารถใส่สิ่งนั้นได้ทันทีหลังจาก label หรือระหว่าง form และแกลเลอรี div แล้วแต่ว่าคุณต้องการอะไรมากกว่านี้ สำหรับเรื่องนั้น คุณสามารถวางไว้ที่ใดก็ได้ตามต้องการภายในแท็ก body ไม่มีการเพิ่มสไตล์สำหรับตัวอย่างนี้ ดังนั้นจะแสดงการใช้งานเริ่มต้นของเบราว์เซอร์ซึ่งให้บริการได้ ตอนนี้ มาทำงานเกี่ยวกับการเพิ่ม JavaScript กัน ก่อนอื่นเราจะดูการใช้งานโดยใช้การ fetch จากนั้นเราจะแสดงเวอร์ชันสำหรับ XMLHttpRequest ในการเริ่มต้น เราจำเป็นต้องมีตัวแปรใหม่สองสามตัวที่ด้านบนของสคริปต์ :

 let filesDone = 0 let filesToDo = 0 let progressBar = document.getElementById('progress-bar')

เมื่อใช้การ fetch เราสามารถระบุได้เมื่อการอัปโหลดเสร็จสิ้นเท่านั้น ดังนั้นข้อมูลเดียวที่เราติดตามคือจำนวนไฟล์ที่ถูกเลือกให้อัปโหลด (เป็น filesToDo ) และจำนวนไฟล์ที่อัปโหลดเสร็จแล้ว (เมื่อ filesDone ) เรากำลังอ้างอิงถึงองค์ประกอบ #progress-bar เพื่อให้สามารถอัปเดตได้อย่างรวดเร็ว ตอนนี้ มาสร้างฟังก์ชันสองสามอย่างสำหรับจัดการความคืบหน้า:

 function initializeProgress(numfiles) { progressBar.value = 0 filesDone = 0 filesToDo = numfiles } function progressDone() { filesDone++ progressBar.value = filesDone / filesToDo * 100 }

เมื่อเราเริ่มอัปโหลด initializeProgress จะถูกเรียกเพื่อรีเซ็ตแถบความคืบหน้า จากนั้น ในการอัปโหลดแต่ละครั้ง เราจะเรียก progressDone เพื่อเพิ่มจำนวนการอัปโหลดที่เสร็จสิ้น และอัปเดตแถบความคืบหน้าเพื่อแสดงความคืบหน้าในปัจจุบัน เรามาเรียกใช้ฟังก์ชันเหล่านี้โดยอัปเดตฟังก์ชันเก่าสองสามรายการ:

 function handleFiles(files) { files = [...files] initializeProgress(files.length) // <- Add this line files.forEach(uploadFile) files.forEach(previewFile) } function uploadFile(file) { let url = 'YOUR URL HERE' let formData = new FormData() formData.append('file', file) fetch(url, { method: 'POST', body: formData }) .then(progressDone) // <- Add `progressDone` call here .catch(() => { /* Error. Inform the user */ }) }

และนั่นแหล่ะ ทีนี้มาดูการใช้งาน XMLHttpRequest เราสามารถอัปเดต uploadFile ได้อย่างรวดเร็ว แต่จริง ๆ แล้ว XMLHttpRequest ให้ฟังก์ชันการทำงานมากกว่า fetch กล่าวคือ เราสามารถเพิ่มตัวฟังเหตุการณ์สำหรับความคืบหน้าในการอัปโหลดในแต่ละคำขอ ซึ่งจะให้ข้อมูลเกี่ยวกับคำขอเป็นระยะ เสร็จ. ด้วยเหตุนี้ เราจึงต้องติดตามเปอร์เซ็นต์ที่เสร็จสมบูรณ์ของคำขอแต่ละรายการ แทนที่จะติดตามเพียงจำนวนที่ดำเนินการเสร็จ เรามาเริ่มด้วยการแทนที่การประกาศสำหรับ filesDone และ filesToDo ด้วยสิ่งต่อไปนี้:

 let uploadProgress = []

จากนั้นเราก็ต้องปรับปรุงฟังก์ชั่นของเราเช่นกัน เราจะเปลี่ยนชื่อ progressDone เป็น updateProgress และเปลี่ยนชื่อเป็นดังนี้:

 function initializeProgress(numFiles) { progressBar.value = 0 uploadProgress = [] for(let i = numFiles; i > 0; i--) { uploadProgress.push(0) } } function updateProgress(fileNumber, percent) { uploadProgress[fileNumber] = percent let total = uploadProgress.reduce((tot, curr) => tot + curr, 0) / uploadProgress.length progressBar.value = total }

ตอนนี้ initializeProgress เริ่มต้นอาร์เรย์ที่มีความยาวเท่ากับ numFiles ที่เต็มไปด้วยศูนย์ ซึ่งแสดงว่าแต่ละไฟล์นั้นสมบูรณ์ 0% ใน updateProgress เราจะค้นหาว่ารูปภาพใดมีความคืบหน้าในการอัปเดตและเปลี่ยนค่าที่ดัชนีนั้นเป็น percent ที่ให้ไว้ จากนั้นเราจะคำนวณเปอร์เซ็นต์ความคืบหน้าทั้งหมดโดยใช้ค่าเฉลี่ยของเปอร์เซ็นต์ทั้งหมดและอัปเดตแถบความคืบหน้าเพื่อสะท้อนถึงยอดรวมที่คำนวณได้ เรายังคงเรียก initializeProgress ใน handleFiles เหมือนกับที่เราเรียกในตัวอย่างการ fetch ดังนั้นตอนนี้สิ่งที่เราต้องอัปเดตคือ uploadFile เพื่อเรียก updateProgress

 function uploadFile(file, i) { // <- Add `i` parameter var url = 'YOUR URL HERE' var xhr = new XMLHttpRequest() var formData = new FormData() xhr.open('POST', url, true) // Add following event listener xhr.upload.addEventListener("progress", function(e) { updateProgress(i, (e.loaded * 100.0 / e.total) || 100) }) xhr.addEventListener('readystatechange', function(e) { if (xhr.readyState == 4 && xhr.status == 200) { // Done. Inform the user } else if (xhr.readyState == 4 && xhr.status != 200) { // Error. Inform the user } }) formData.append('file', file) xhr.send(formData) }

สิ่งแรกที่ควรทราบคือเราได้เพิ่มพารามิเตอร์ i นี่คือดัชนีของไฟล์ในรายการไฟล์ เราไม่จำเป็นต้องอัปเดต handleFiles เพื่อส่งพารามิเตอร์นี้เพราะใช้ forEach ซึ่งให้ดัชนีขององค์ประกอบเป็นพารามิเตอร์ที่สองสำหรับการโทรกลับ นอกจากนี้เรายังเพิ่มตัวฟังเหตุการณ์ progress ใน xhr.upload เพื่อให้เราสามารถเรียก updateProgress พร้อมความคืบหน้า ออบเจ็กต์เหตุการณ์ (เรียกว่า e ในโค้ด) มีข้อมูลที่เกี่ยวข้องสองส่วน: loaded ซึ่งมีจำนวนไบต์ที่ได้รับการอัปโหลดจนถึงขณะนี้ และ total ที่มีจำนวนไบต์ของไฟล์ทั้งหมด

|| 100 || 100 ชิ้นอยู่ในนั้นเพราะบางครั้งหากมีข้อผิดพลาด e.loaded และ e.total จะเป็นศูนย์ ซึ่งหมายความว่าการคำนวณจะออกมาเป็น NaN ดังนั้นจึงใช้ 100 แทนเพื่อรายงานว่าไฟล์เสร็จสิ้น คุณยังสามารถใช้ 0 ไม่ว่าในกรณีใด ข้อผิดพลาดจะแสดงขึ้นในตัวจัดการ readystatechange เพื่อให้คุณสามารถแจ้งให้ผู้ใช้ทราบได้ นี่เป็นเพียงเพื่อป้องกันข้อยกเว้นจากการพยายามทำคณิตศาสตร์ด้วย NaN

บทสรุป

นั่นเป็นชิ้นสุดท้าย ขณะนี้คุณมีหน้าเว็บที่คุณสามารถอัปโหลดภาพผ่านการลากและวาง ดูตัวอย่างภาพที่กำลังอัปโหลดทันที และดูความคืบหน้าของการอัปโหลดในแถบความคืบหน้า คุณสามารถดูเวอร์ชันสุดท้าย (ด้วย XMLHttpRequest ) ที่ทำงานบน CodePen ได้ แต่โปรดทราบว่าบริการที่ฉันอัปโหลดไฟล์นั้นมีข้อจำกัด ดังนั้นหากผู้คนจำนวนมากทดสอบใช้งาน อาจหยุดทำงานชั่วขณะหนึ่ง