حقل تحميل الملف الذي نستحقه

كل شيء يتدفق ، كل شيء يتغير ، ولكن فقط input[type=file] لأنه يفسد أعصاب جميع مطوري الويب المبتدئين ، ويستمر في القيام بذلك حتى الآن. تذكر نفسك قبل N سنوات ، عندما كنت قد بدأت للتو في فهم أساسيات إنشاء مواقع الويب. كنت شابًا وعديم الخبرة ، فوجئت حقًا عندما رفض زر اختيار الملف تمامًا تغيير لون الخلفية إلى لون الخوخ المفضل لديك. في تلك اللحظة واجهت لأول مرة هذا الجبل الجليدي غير القابل للتدمير المسمى "تنزيل الملفات" ، والذي يستمر حتى يومنا هذا في "إغراق" مطوري الويب المبتدئين.

باستخدام مثال إنشاء حقل تحميل ملف ، سأوضح لك كيفية إخفاء input[type=file] بشكل صحيح ، وتعيين التركيز على كائن لا يمكن أن يكون به تركيز ، والتعامل مع أحداث السحب والإفلات وإرسال الملفات عبر AJAX. وسأقدم لك أيضًا بضع أخطاء في المتصفح وطرقًا للتغلب عليها. تم كتابة المقالة للمبتدئين ، ولكن في بعض النقاط يمكن أن تكون مفيدة ومسلية حتى للمطورين المخضرمين.

الترميز والأنماط الأساسية


لنبدأ بترميز 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> 

ربما يكون العنصر الرئيسي الذي يجب الانتباه إليه هو

 <label for="file-input"> </label> 

لا تسمح لنا مواصفات HTML بفرض الخصائص المرئية مباشرة على input[type=file] ، ولكن لدينا علامة label ، والتي تؤدي إلى النقر على عنصر النموذج الذي يتم إرفاقه به. لفرحتنا ، هذه العلامة ليس لها قيود في الأسلوب: يمكننا أن نفعل ما نريد به.

تظهر خطة عمل: نقوم بتصميم التسمية كما نشاء ونخفي input[type=file] بعيدًا عن الأنظار. أولاً ، قم بإعداد أنماط الصفحة العامة:

 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; } 

الآن أسلوب تسمية لدينا:

 #upload-container label { font-weight: bold; } #upload-container label:hover { cursor: pointer; text-decoration: underline; } 

ما نسعى إليه (تمت إزالة input[type=file] من الترميز):

بالطبع ، يمكنك توسيط التسمية ، وإضافة خلفية وحدود ، والحصول على زر كامل ، ولكن أولويتنا هي السحب والإفلات.

إخفاء المدخلات


نحتاج الآن إلى إخفاء input[type=file] . أول شيء يضرب الرأس هو display: none visibility: hidden خصائص visibility: hidden . لكن هذا ليس بهذه البساطة. في بعض المتصفحات القديمة ، لن يكون للنقر على الملصق أي تأثير بعد الآن. لكن هذا ليس كل شيء كما تعلم ، لا يمكن للعناصر غير المرئية أن تتلقى التركيز ، وبغض النظر عما يقولون ، فإن التركيز مهم ، لأن هذه هي الطريقة الوحيدة للتفاعل مع الموقع بالنسبة لبعض الأشخاص. لذلك هذه الطريقة لا تناسبنا. دعنا نذهب حول هذا:

 #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; } 

نحن 0.1px input[type=file] بالنسبة إلى الكتلة الرئيسية الخاصة بها ، 0.1px إلى 0.1px ، 0.1px شفافة 0.1px z-index أقل من الأصل ، لذلك ، إذا جاز التعبير ، بالتأكيد.

تهانينا ، لقد حققنا ما أردنا: مجالنا يبدو تمامًا في الصورة السابقة.

ضبط التركيز


نظرًا لأن input[type=file] موجودة فعليًا على الصفحة ، فإن لديها القدرة على تلقي التركيز. أي إذا ضغطنا على المفتاح Tab في الصفحة ، فحينئذٍ سينتقل التركيز إلى input[type=file] . لكن المشكلة هي أننا لن نرى هذا: المجال الذي أخفيناه سيبرز. نعم ، إذا ضغطنا في هذه اللحظة على Enter ، فسيتم فتح مربع الحوار وسيعمل كل شيء كما ينبغي ، ولكن كيف نفهم أن الوقت قد حان للضغط؟

مهمتنا هي تحديد علامة بطريقة معينة في اللحظة التي يكون فيها التركيز على حقل تحميل الملف. ولكن كيف يمكننا القيام بذلك إذا كانت العلامة لا يمكنها التركيز؟ سيفكر خبراء CSS3 على الفور في الفئة الزائفة :focus ، التي تحدد أنماط العناصر في التركيز ، و + أو ~ المحددات التي تحدد الجيران المناسبين: العناصر الموجودة في نفس مستوى التعشيش ، بعد العنصر المحدد. مع الأخذ في الاعتبار أنه في input[type=file] الترميز input[type=file] يقع مباشرة أمام علامة label ، يتم الإدخال التالي:

 #upload-container input[type=file]:focus + label { /*  */ } 

ولكن مرة أخرى ، ليس بهذه البساطة. للبدء ، دعنا نناقش كيف يجب أن نختار تسمية. كما تعلم ، تحتوي جميع المتصفحات الحديثة وغير ذلك على خصائص افتراضية فريدة للعناصر محل التركيز. بشكل أساسي ، هذه هي خاصية outline ، والتي تنشئ حدًا حول عنصر يختلف عن border في أنه لا يغير حجم العنصر ويمكن نقله بعيدًا عنه. كقاعدة ، يستخدم الأشخاص متصفحًا واحدًا فقط ، حتى يعتادوا على معاييره. لتسهيل تنقل الأشخاص على موقعنا ، يجب أن نحاول ضبط التركيز بحيث يبدو طبيعيًا قدر الإمكان لمعظم المتصفحات الحديثة الشائعة. نظريًا ، باستخدام JavaScript ، يمكنك الحصول على معلومات حول المتصفح الذي فتح المستخدم الموقع فيه ، وتخصيص الأنماط وفقًا لذلك ، ولكن هذا الموضوع معقد للغاية ومرهق كجزء من مقالة مخصصة بشكل أساسي للمبتدئين. سنحاول الحصول على القليل من الدماء.

في المتصفحات المستندة إلى محرك WebKet (Google Chrome و Opera و Safari) ، تكون الخاصية الافتراضية للعناصر محل التركيز هي:

 :focus { outline: -webkit-focus-ring-color auto 5px; } 

هنا -webkit-focus-ring-color هو -webkit-focus-ring-color الخاص بهذا المحرك فقط. أي أن هذا الخط سيعمل حصريًا في متصفحات WebKit ، وهذا بالضبط ما نحتاجه. حدد هذه الخاصية لملصقنا:

 #upload-container input[type=file]:focus + label { outline: -webkit-focus-ring-color auto 5px; } 

نفتح جوجل كروم أو أوبرا ، ننظر. كل شيء يعمل كما ينبغي:

دعونا نرى كيف تسير الأمور مع التركيز في Mozilla Firefox و Microsoft Edge. بالنسبة لهذه المتصفحات ، الخاصية الافتراضية هي:

 :focus { outline: 1px solid #0078d7; } 

و

 :focus { outline: 1px solid #212121; } 

وفقًا لذلك.

لسوء الحظ ، لن تعمل البادئة -moz- مع خاصية outline . لذلك ، سيتعين علينا اختيار أي من هاتين الخاصيتين اللتين نختارهما. نظرًا لأن عدد مستخدمي Firefox أعلى بكثير ، فمن الأكثر عقلانية إعطاء الأفضلية لهذا المتصفح المعين. هذا لا يعني أننا نحرم مستخدمي Edge والمتصفحات الأخرى من الفرصة لمعرفة مكان التركيز الآن ، فإنها ستبدو "مختلفة" عنهم. حسنا ، عليك أن تضحي.

أضف نمطًا من Mozilla Firefox قبل النمط الخاص بـ WebKit: أولاً ستطبق جميع المتصفحات الخاصية الأولى ، ثم تلك التي يمكنها (Google Chrome ، Opera ، Safari ، إلخ) ستطبق الثانية.

 #upload-container input[type=file]:focus + label { outline: 1px solid #0078d7; outline: -webkit-focus-ring-color auto 5px; } 

وهنا يبدأ الغريب: يعمل كل شيء في Edge بشكل جيد ، ولكن Firefox لسبب غير معروف يرفض تطبيق الخصائص على الملصق مع التركيز على input[type=file] . وحدث focus نفسه يحدث - يتم فحصه من خلال JavaScript. علاوة على ذلك ، إذا فرضت التركيز على حقل اختيار الملف من خلال أدوات المطور ، فسيتم تطبيق الخاصية وستظهر حدودنا! على ما يبدو ، هذا خطأ في المتصفح نفسه ، ولكن إذا كان لدى شخص أفكار عن سبب حدوث ذلك - اكتب في التعليقات.

حسنًا ، لا شيء ، الأبطال العاديون يتجولون دائمًا كما قلت سابقًا ، يحدث حدث focus ، مما يعني أنه يمكننا تعديل الخصائص مباشرة من JavaScript. ولكن لهذا يجب علينا تغيير منطق محددنا:

 #upload-container label.focus { outline: 1px solid #0078d7; outline: -webkit-focus-ring-color auto 5px; } 

.focus فئة .focus وسنقوم بإضافتها في كل مرة عندما يتلقى input[type=file] التركيز وإزالته عندما يفقد.

 $('#file-input').focus(function() { $('label').addClass('focus'); }) .focusout(function() { $('label').removeClass('focus'); }); 

الآن كل شيء يعمل كما ينبغي. تهانينا ، لقد اكتشفنا التركيز.

قم بالسحب والإفلات


يتم العمل مع السحب والإفلات من خلال تتبع أحداث المتصفح الخاصة: drag, dragstart, dragend, dragover, dragenter, dragleave, drop . يمكنك بسهولة العثور على وصف تفصيلي لكل منها على الإنترنت. سنتتبع بعض منهم فقط.

أولاً ، حدد عنصر السحب والإفلات:
 var dropZone = $('#upload-container'); 

ثم وصفنا في CSS فئة خاصة dropZone عندما يكون المؤشر الذي يسحب الملف فوقه فوقه مباشرة. يعد ذلك ضروريًا لإعلام المستخدم بصريًا أنه يمكن بالفعل تحرير الملف.

 #upload-container.dragover { background-color: #fafafa; outline-offset: -17px; } 

الآن دعنا ننتقل إلى ملف JS. للبدء ، نحتاج إلى التراجع عن جميع الإجراءات الافتراضية لأحداث السحب والإفلات. على سبيل المثال ، أحد هذه الأحداث هو فتح ملف يلقيه المستعرض. لا نحتاج هذا على الإطلاق ، لذا سنكتب الأسطر التالية:

 dropZone.on('drag dragstart dragend dragover dragenter dragleave drop', function(){ return false; }); 

في jQuery ، فإن استدعاء return false يعادل استدعاء دالتين في وقت واحد: e.preventDefault() و e.stopPropagation() .

نبدأ في وصف معالج الأحداث الخاص بنا. سنعمل بالطريقة نفسها التي عملنا بها مع التركيز ، ولكن هذه المرة dragover لإضافة فئة dragleave لإزالته:

 dropZone.on('dragover dragenter', function() { dropZone.addClass('dragover'); }); dropZone.on('dragleave', function(e) { dropZone.removeClass('dragover'); }); 

ومرة أخرى ، تنتظرنا مفاجأة غير سارة: عندما تتحرك على طول dropZone باستخدام الماوس مع الملف ، يبدأ الحقل في الوميض. يحدث هذا في متصفحات Microsoft Edge و WebKit. بالمناسبة ، معظم متصفحات WebKit هذه تعمل حاليًا على محرك Blink (تقدر السخرية ، هاه؟). ولكن في موزيلا ، لا شيء يتأرجح. على ما يبدو ، قررت إصلاحه بعد أخطاء التركيز.

ويحدث هذا الوميض بسبب حقيقة أنك عندما تحوم فوق dropZone لمنطقة dropZone ، سواء كانت صورة أو div مع حقل اختيار ملف dragleave ، لسبب ما ، يتم dragleave حدث dragleave . من الواضح لنا أننا لا نغادر الحقل ، ولكن المتصفحات ، لسبب ما ، لا تغادر ، وبسبب هذا فإنها تزيل .focus فئة dropZone من dropZone .

ومرة أخرى ، علينا الخروج بطريقة أو بأخرى. إذا لم يفهم المتصفح نفسه أننا لن نغادر الحقل ، فسنضطر إلى مساعدته. وسنفعل ذلك من خلال شروط إضافية: نحن نحسب إحداثيات الفأرة بالنسبة إلى dropZone ، ثم نتحقق مما إذا كان المؤشر خارج الكتلة. إذا غادر ، ثم نزيل النمط:

 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'); }; }); 

وهذا كل شيء ، تم حل المشكلة! هذا ما يبدو عليه مجالنا مع الملف بداخله:


دعنا ننتقل إلى التعامل مع حدث drop نفسه. ولكن أولاً ، تذكر أنه بالإضافة إلى السحب والإفلات ، لدينا input[type=file] ، وكل طريقة من هذه الطرق مستقلة في جوهرها ، ولكن يجب أن تؤدي نفس الإجراءات: تحميل الملفات. لذلك ، أقترح إنشاء وظيفة منفصلة عالمية لكلتا الطريقتين ، حيث سننقل الملفات ، وسوف تقرر بالفعل ما يجب فعله بها. سوف نسميها sendFiles() ، لكننا sendFiles() لاحقًا. للبدء ، تعامل مع حدث drop :

 dropZone.on('drop', function(e) { dropZone.removeClass('dragover'); let files = e.originalEvent.dataTransfer.files; sendFiles(files); }); 

أولاً ، .dragover نزيل فئة dropZone من dropZone . ثم نحصل على مصفوفة تحتوي على الملفات. إذا كنت تستخدم jQuery ، فسيكون المسار e.originalEvent.dataTransfer.files ، إذا كتبت في JS خالص ، ثم e.dataTransfer.files . حسنًا ، نمرر المصفوفة إلى وظيفتنا التي لم تتحقق بعد.

الآن سنعمل على طريقة التحميل عبر input[type=file] :

 $('#file-input').change(function() { let files = this.files; sendFiles(files); }); 

نحن نتتبع حدث change على زر اختيار الملف ، ونحصل على الصفيف من خلال هذه this.files إلى الوظيفة.

إرسال الملفات عبر AJAX


الخطوة الأخيرة - وصف وظيفة معالجة الملفات - فريدة للجميع. سيعتمد بشكل مباشر على الهدف الذي تسعى إليه. على سبيل المثال ، سأوضح كيفية إرسال الملفات إلى الخادم من خلال AJAX.

لنفترض أننا قمنا بإنشاء حقل لتحميل الصور. لا نريد شيئًا آخر للوصول إلى خادمنا ، لذلك سنقرر أنواع الملفات: فليكن PNG و JPEG. من الجدير أيضًا تنظيم الحجم الأقصى لصورة واحدة يمكن للمستخدم إرسالها. تقتصر على خمسة ميغا بايت. نبدأ في وصف وظيفتنا:

 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); } }); }; 

في المتغير maxFileSize الحد الأقصى لحجم الملف الذي maxFileSize إلى الخادم. FormData() ، FormData كائنًا جديدًا من فئة FormData ، مما يتيح لنا إنشاء مجموعات من أزواج القيمة الرئيسية. يمكن إرسال هذا الشيء بسهولة عبر AJAX. بعد ذلك ، نستخدم jQuery .each لمجموعة files ، والتي ستطبق الوظيفة التي قمنا بتعيينها لكل عنصر من عناصره. سيتم تمرير رقم العنصر والعنصر نفسه كوسيطات للدالة ، والتي سنعالجها على هيئة index file على التوالي. في الوظيفة نفسها ، سوف نتحقق من الملف وفقًا لمعاييرنا: الحجم أقل من خمسة ميغابايت ، والنوع هو PNG أو JPEG. إذا اجتاز الملف الاختبار ، FormData كائن FormData عن طريق استدعاء الدالة append() . المفتاح هو سلسلة 'photos[]' ، والأقواس المربعة في نهايتها تشير إلى أنه صفيف يمكن أن يكون فيه العديد من الكائنات. سيكون الكائن نفسه file .

الآن كل شيء جاهز لإرسال الملفات من خلال AJAX. أضف الأسطر التالية إلى وظيفتنا:

 $.ajax({ url: dropZone.attr('action'), type: dropZone.attr('method'), data: Data, contentType: false, processData: false, success: function(data) { alert('   '); } }); 

بصفتنا url للمعلمات type فإننا نشير إلى قيم action وخصائص input[type=file] على التوالي. لتمرير AJAX سنكون كائن Data . المعلمات contentType: false و processData: false مطلوبة حتى لا يقوم المتصفح عن غير قصد بترجمة ملفاتنا إلى تنسيق آخر. في معلمة success ، نحدد الوظيفة التي سيتم تنفيذها إذا تم نقل الملفات إلى الخادم بنجاح. تعتمد محتوياته على خيالك ، ولكن سأقتصر على إخراج متواضع لرسالة حول تنزيل ناجح.

تهانينا ، يمكنك الآن إنشاء حقل تحميل ملفك الخاص! بالطبع ، أنا لا أضع طريقي على أنه الوحيد الصحيح والصحيح. كان هدفي هو إظهار المسار العام لحل هذه المشكلة ، وهو مناسب بشكل أساسي للمبتدئين. إذا كنت تعتقد أنه في مكان ما يمكنك أن تفعل شيئًا أفضل - اكتب في التعليقات ، سنناقش!

هذا كل شيء. شكرا لكم على اهتمامكم!

تنزيل:

  1. النسخة النهائية
  2. مشكلة التركيز
  3. مشكلة الخفقان

المس:

  1. النسخة النهائية
  2. مشكلة التركيز
  3. مشكلة الخفقان

Source: https://habr.com/ru/post/ar423035/


All Articles