วิธีการสร้างไฟล์อัพโหลดแบบลากและวางด้วย JavaScript วานิลลา
เผยแพร่แล้ว: 2022-03-10เป็นที่ทราบกันดีอยู่แล้วว่าอินพุตสำหรับการเลือกไฟล์นั้นยากต่อการจัดรูปแบบตามที่นักพัฒนาต้องการ หลายคนจึงซ่อนไฟล์และสร้างปุ่มที่เปิดกล่องโต้ตอบการเลือกไฟล์แทน แม้ว่าในปัจจุบันนี้ เรามีวิธีจัดการการเลือกไฟล์ที่สนุกยิ่งขึ้นไปอีก นั่นคือ การลากและวาง
ในทางเทคนิค สิ่งนี้เป็นไปได้แล้วเนื่องจากการใช้งานอินพุตการเลือกไฟล์ส่วนใหญ่ (ถ้าไม่ใช่ ทั้งหมด ) อนุญาตให้คุณลากไฟล์ไปทับเพื่อเลือกไฟล์เหล่านั้น แต่สิ่งนี้ต้องการให้คุณแสดงองค์ประกอบ 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) }
การดำเนินการนี้ไม่ได้ทำให้เราใกล้จะเสร็จสมบูรณ์ แต่ทำสิ่งสำคัญสองประการ:
- สาธิตวิธีการรับข้อมูลสำหรับไฟล์ที่ถูกทิ้ง
- นำเราไปยังที่เดียวกับที่
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 ได้ แต่โปรดทราบว่าบริการที่ฉันอัปโหลดไฟล์นั้นมีข้อจำกัด ดังนั้นหากผู้คนจำนวนมากทดสอบใช้งาน อาจหยุดทำงานชั่วขณะหนึ่ง