اصنع لوحات المحتوى الموسعة والمتعاقد عليها
نشرت: 2022-03-10لقد أطلقنا عليها اسم "لوحة الفتح والإغلاق" حتى الآن ، ولكن يتم وصفها أيضًا على أنها لوحات توسعة ، أو ببساطة توسيع لوحات.
لتوضيح ما نتحدث عنه بالضبط ، انتقل إلى هذا المثال على CodePen:
هذا ما سنبنيه في هذا البرنامج التعليمي القصير.
من وجهة نظر وظيفية ، هناك عدة طرق لتحقيق فتح وإغلاق متحرك الذي نبحث عنه. كل نهج له فوائده والمقايضات. سأشارك تفاصيل طريقة "go-to" الخاصة بي بالتفصيل في هذه المقالة. دعونا ننظر في الأساليب الممكنة أولا.
اقتراب
هناك اختلافات في هذه الأساليب ، ولكن بشكل عام ، تندرج الأساليب في واحدة من ثلاث فئات:
- حرك / انقل
height
أوmax-height
المحتوى. - استخدم
transform: translateY
Y لنقل العناصر إلى موضع جديد ، مما يعطي انطباعًا بإغلاق اللوحة ثم إعادة تقديم DOM بمجرد اكتمال التحويل مع العناصر الموجودة في موضعها النهائي. - استخدم مكتبة تقوم ببعض الدمج / الاختلاف بين 1 أو 2!
اعتبارات كل نهج
من منظور الأداء ، يعد استخدام التحويل أكثر فاعلية من تحريك أو نقل الارتفاع / الحد الأقصى للارتفاع. باستخدام التحويل ، يتم تحويل العناصر المتحركة إلى نقطية ويتم تغييرها بواسطة وحدة معالجة الرسومات. هذه عملية رخيصة وسهلة لوحدة معالجة الرسومات ، لذا يميل الأداء إلى أن يكون أفضل بكثير.
الخطوات الأساسية عند استخدام نهج التحويل هي:
- احصل على ارتفاع المحتوى ليتم تصغيره.
- انقل المحتوى وكل شيء بعده بارتفاع المحتوى ليتم تصغيره باستخدام
transform: translateY(Xpx)
. قم بتشغيل التحويل مع انتقال الاختيار لإعطاء تأثير مرئي ممتع. - استخدم JavaScript للاستماع إلى حدث
transitionend
. عند إطلاقه ، قمdisplay: none
المحتوى وإزالة التحويل وكل شيء يجب أن يكون في المكان المناسب.
لا يبدو سيئا للغاية ، أليس كذلك؟
ومع ذلك ، هناك عدد من الاعتبارات المتعلقة بهذه التقنية ، لذلك أميل إلى تجنبها للتطبيقات غير الرسمية ما لم يكن الأداء حاسمًا للغاية.
على سبيل المثال ، باستخدام أسلوب transform: translateY
، تحتاج إلى مراعاة z-index
للعناصر. بشكل افتراضي ، تكون العناصر التي يتم تحويلها بعد عنصر المشغل في DOM ، وبالتالي تظهر فوق الأشياء التي أمامها عند ترجمتها.
تحتاج أيضًا إلى التفكير في عدد الأشياء التي تظهر بعد المحتوى الذي تريد تصغيره في DOM. إذا كنت لا تريد فجوة كبيرة في تخطيطك ، فقد تجد أنه من الأسهل استخدام JavaScript لف كل ما تريد نقله في عنصر الحاوية ونقله فقط. يمكن التحكم فيه ولكننا قدمنا للتو المزيد من التعقيد! ومع ذلك ، هذا هو نوع النهج الذي اتبعته عند تحريك اللاعبين لأعلى ولأسفل في In / Out. يمكنك أن ترى كيف تم ذلك هنا.
لمزيد من الاحتياجات غير الرسمية ، أميل إلى الانتقال إلى max-height
للمحتوى. هذا النهج لا يعمل بشكل جيد مثل التحويل. والسبب هو أن المتصفح يتأرجح بين ارتفاع عنصر الانهيار خلال الانتقال ؛ هذا يسبب الكثير من حسابات التخطيط التي ليست رخيصة للكمبيوتر المضيف.
ومع ذلك ، فإن هذا النهج يفوز من وجهة نظر البساطة. إن مردود معاناة النتيجة الحسابية المذكورة أعلاه هو أن إعادة تدفق DOM يعتني بموضع وهندسة كل شيء. لدينا القليل جدًا من العمليات الحسابية للكتابة بالإضافة إلى أن JavaScript اللازم لسحبها جيدًا بسيط نسبيًا.
الفيل في الغرفة: التفاصيل وعناصر الملخص
أولئك الذين لديهم معرفة وثيقة بعناصر HTML سيعرفون أن هناك حل HTML أصلي لهذه المشكلة في شكل details
وعناصر summary
. إليك بعض الأمثلة على الترميز:
<details> <summary>Click to open/close</summary> Here is the content that is revealed when clicking the summary... </details>
بشكل افتراضي ، توفر المستعرضات مثلث كشف صغير بجوار عنصر الملخص ؛ انقر فوق الملخص وسيتم الكشف عن المحتويات الموجودة أسفل الملخص.
عظيم ، مهلا؟ تدعم التفاصيل حتى حدث toggle
في JavaScript حتى تتمكن من القيام بهذا النوع من الأشياء لأداء أشياء مختلفة بناءً على ما إذا كان مفتوحًا أو مغلقًا (لا تقلق إذا كان هذا النوع من تعبير JavaScript يبدو غريبًا ؛ سنصل إلى ذلك في المزيد التفاصيل بعد قليل):
details.addEventListener("toggle", () => { details.open ? thisCoolThing() : thisOtherThing(); })
حسنًا ، سأوقف حماستك هناك. لا يتم تحريك التفاصيل وعناصر التلخيص. ليس افتراضيًا ولا يمكن حاليًا جعلها متحركة / تنقل مفتوحة ومغلقة باستخدام CSS وجافا سكريبت إضافيين.
إذا كنت تعرف خلاف ذلك ، فأنا أحب أن أكون مخطئًا.
للأسف ، نظرًا لأننا بحاجة إلى جمالية افتتاحية وختامية ، سيتعين علينا أن نشمر عن سواعدنا ونقوم بأفضل الوظائف وأكثرها سهولة باستخدام الأدوات الأخرى المتاحة لنا.
حسنًا ، مع خروج الأخبار المحزنة من الطريق ، دعنا نواصل تحقيق هذا الشيء.
نمط التوصيف
ستبدو الترميز الأساسي كما يلي:
<div class="container"> <button type="button" class="trigger">Show/Hide content</button> <div class="content"> All the content here </div> </div>
لدينا حاوية خارجية لف الموسع والعنصر الأول هو الزر الذي يعمل كمحفز للإجراء. لاحظ سمة النوع في الزر؟ أقوم دائمًا بتضمين ذلك كزر افتراضي داخل النموذج سيؤدي إلى إرسال. إذا وجدت نفسك تضيع بضع ساعات في التساؤل عن سبب عدم عمل النموذج الخاص بك وتشارك الأزرار في النموذج الخاص بك ؛ تأكد من التحقق من سمة النوع!
العنصر التالي بعد الزر هو درج المحتوى نفسه ؛ كل ما تريد إخفاءه وعرضه.
لإضفاء الحيوية على الأشياء ، سنستخدم خصائص CSS المخصصة وانتقالات CSS وقليلًا من JavaScript.
المنطق الأساسي
المنطق الأساسي هو هذا:
- دع الصفحة يتم تحميلها ، قم بقياس ارتفاع المحتوى.
- عيّن ارتفاع المحتوى على الحاوية كقيمة خاصية CSS المخصصة.
- قم بإخفاء المحتوى فورًا عن طريق إضافة سمة
aria-hidden: "true"
إليه. يضمن استخدامaria-hidden
أن التكنولوجيا المساعدة تعلم أن المحتوى مخفي أيضًا. - اربط CSS بحيث يكون
max-height
لفئة المحتوى هو قيمة الخاصية المخصصة. - يؤدي الضغط على زر المشغل إلى تبديل خاصية aria-hidden من صواب إلى خطأ ، مما يؤدي بدوره إلى تبديل
max-height
للمحتوى بين0
والارتفاع المعين في الخاصية المخصصة. يوفر الانتقال على تلك الخاصية الذوق البصري - التكيف مع الذوق!
ملاحظة: الآن ، ستكون هذه حالة بسيطة لتبديل فئة أو سمة إذا كان max-height: auto
يساوي ارتفاع المحتوى. للأسف لا. اذهب وأصرخ حول ذلك إلى W3C هنا.
دعونا نلقي نظرة على كيفية ظهور هذا النهج في الكود. تُظهر التعليقات المرقمة خطوات المنطق المكافئة من أعلى في الكود.
هنا JavaScript:
// Get the containing element const container = document.querySelector(".container"); // Get content const content = document.querySelector(".content"); // 1. Get height of content you want to show/hide const heightOfContent = content.getBoundingClientRect().height; // Get the trigger element const btn = document.querySelector(".trigger"); // 2. Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e => { document.documentElement.classList.add("height-is-set"); 3. content.setAttribute("aria-hidden", "true"); }, 0); btn.addEventListener("click", function(e) { container.setAttribute("data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true"); // 5. Toggle aria-hidden content.setAttribute("aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true"); })
CSS:
.content { transition: max-height 0.2s; overflow: hidden; } .content[aria-hidden="true"] { max-height: 0; } // 4. Set height to value of custom property .content[aria-hidden="false"] { max-height: var(--containerHeight, 1000px); }
نقاط ملحوظة
ماذا عن الأدراج المتعددة؟
عندما يكون لديك عدد من الأدراج التي يمكن فتحها وإخفائها في إحدى الصفحات ، ستحتاج إلى المرور عبرها جميعًا حيث من المحتمل أن تكون بأحجام مختلفة.
للتعامل مع ذلك سنحتاج إلى إجراء querySelectorAll
الكل للحصول على جميع الحاويات ثم إعادة تشغيل إعدادك للمتغيرات المخصصة لكل محتوى داخل forEach
.
أن تعيين المهلة
لديّ setTimeout
بمدة 0
قبل تعيين الحاوية لتكون مخفية. يمكن القول إن هذا غير ضروري ولكني استخدمه كنهج "حزام وأقواس" للتأكد من أن الصفحة قد تم عرضها أولاً بحيث تكون ارتفاعات المحتوى متاحة للقراءة.
أطلق هذا فقط عندما تكون الصفحة جاهزة
إذا كانت لديك أشياء أخرى جارية ، فقد تختار التفاف رمز الدرج الخاص بك في وظيفة يتم تهيئتها عند تحميل الصفحة. على سبيل المثال ، افترض أن وظيفة الدرج قد اختُتمت في وظيفة تسمى initDrawers
يمكننا القيام بذلك:
window.addEventListener("load", initDrawers);
في الواقع ، سنضيف ذلك بعد قليل.
بيانات إضافية- سمات على الحاوية
توجد سمة بيانات على الحاوية الخارجية يتم تبديلها أيضًا. تتم إضافة هذا في حالة وجود أي شيء يحتاج إلى تغيير مع المشغل أو الحاوية أثناء فتح / إغلاق الدرج. على سبيل المثال ، ربما نريد تغيير لون شيء ما أو الكشف عن رمز أو تبديله.
القيمة الافتراضية في الخاصية المخصصة
هناك قيمة افتراضية معينة على الخاصية المخصصة في CSS تبلغ 1000px
. هذا هو البت بعد الفاصلة داخل القيمة: var(--containerHeight, 1000px)
. هذا يعني أنه في حالة --containerHeight
بطريقة ما ، فلا يزال يتعين عليك الانتقال بشكل لائق. من الواضح أنه يمكنك ضبط ذلك على كل ما هو مناسب لحالة الاستخدام الخاصة بك.
لماذا لا تستخدم فقط قيمة افتراضية تبلغ 100000 بكسل؟
بالنظر إلى أن max-height: auto
، فقد تتساءل عن سبب عدم اختيار ارتفاع معين لقيمة أكبر مما قد تحتاج إليه في أي وقت مضى. على سبيل المثال ، 10000000 بكسل؟
تكمن مشكلة هذا النهج في أنه سينتقل دائمًا من هذا الارتفاع. إذا تم تعيين مدة الانتقال على ثانية واحدة ، فسيسافر النقل 10000000 بكسل في الثانية. إذا كان المحتوى الخاص بك يبلغ ارتفاعه 50 بكسل فقط ، فستحصل على تأثير فتح / إغلاق سريع!
عامل التشغيل الثلاثي للتبديل
لقد استخدمنا عامل التشغيل الثلاثي عدة مرات لتبديل السمات. بعض الناس يكرهونهم ولكن أنا والآخرون يحبونهم. قد تبدو غريبة بعض الشيء وقليلًا من "لعبة الكود" في البداية ، لكن بمجرد أن تعتاد على بناء الجملة ، أعتقد أنها قراءة أكثر وضوحًا من قراءة قياسية if / else.
بالنسبة للمبتدئين ، فإن المعامل الثلاثي هو صيغة مكثفة من if / else. لقد تم كتابتها بحيث يكون الشيء الذي يجب التحقق منه أولاً ، ثم ?
يفصل ما يجب تنفيذه إذا كان الشيك صحيحًا ، ثم :
لتمييز ما يجب تشغيله إذا كان الاختيار خطأ.
isThisTrue ? doYesCode() : doNoCode();
تعمل مفاتيح تبديل السمات الخاصة بنا عن طريق التحقق مما إذا تم تعيين سمة على "true"
وإذا كان الأمر كذلك ، فاضبطها على "false"
، وإلا فاضبطها على "true"
.
ماذا يحدث عند تغيير حجم الصفحة؟
إذا قام المستخدم بتغيير حجم نافذة المتصفح ، فهناك احتمال كبير أن تتغير ارتفاعات المحتوى الخاص بنا. لذلك قد ترغب في إعادة تشغيل ضبط ارتفاع الحاويات في هذا السيناريو. الآن نحن نفكر في مثل هذه الاحتمالات ، يبدو أن الوقت مناسب لإعادة تشكيل الأشياء قليلاً.
يمكننا عمل وظيفة واحدة لضبط الارتفاعات ووظيفة أخرى للتعامل مع التفاعلات. ثم أضف مستمعين على النافذة ؛ واحد عند تحميل المستند ، كما هو مذكور أعلاه ، ثم آخر للاستماع إلى حدث تغيير الحجم.
A11Y اضافية قليلا
من الممكن إضافة القليل من الاهتمام الإضافي لإمكانية الوصول من خلال الاستفادة من السمات aria-expanded
aria-labelledby
aria-controls
في الصوت والسمات التي يتم تسميتها. سيعطي هذا مؤشرًا أفضل للتقنية المساعدة عند فتح / توسيع الأدراج. نضيف aria-expanded="false"
إلى ترميز الأزرار جنبًا إلى جنب مع aria-controls="IDofcontent"
، حيث IDofcontent
هي قيمة المعرف الذي نضيفه إلى حاوية المحتوى.
ثم نستخدم عامل تشغيل ثلاثي آخر لتبديل السمة aria-expanded
عند النقر في JavaScript.
جميعا
مع تحميل الصفحة ، والأدراج المتعددة ، وعمل A11Y الإضافي ، والتعامل مع أحداث تغيير الحجم ، يبدو كود JavaScript الخاص بنا كما يلي:
var containers; function initDrawers() { // Get the containing elements containers = document.querySelectorAll(".container"); setHeights(); wireUpTriggers(); window.addEventListener("resize", setHeights); } window.addEventListener("load", initDrawers); function setHeights() { containers.forEach(container => { // Get content let content = container.querySelector(".content"); content.removeAttribute("aria-hidden"); // Height of content to show/hide let heightOfContent = content.getBoundingClientRect().height; // Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e => { container.classList.add("height-is-set"); content.setAttribute("aria-hidden", "true"); }, 0); }); } function wireUpTriggers() { containers.forEach(container => { // Get each trigger element let btn = container.querySelector(".trigger"); // Get content let content = container.querySelector(".content"); btn.addEventListener("click", () => { btn.setAttribute("aria-expanded", btn.getAttribute("aria-expanded") === "false" ? "true" : "false"); container.setAttribute( "data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true" ); content.setAttribute( "aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true" ); }); }); }
يمكنك أيضًا اللعب بها على CodePen هنا:
ملخص
من الممكن أن تستمر لبعض الوقت في مزيد من التحسين وتقديم الطعام للمزيد والمزيد من المواقف ، لكن الآليات الأساسية لإنشاء درج فتح وإغلاق موثوق به للمحتوى الخاص بك يجب أن تكون الآن في متناول يدك. نأمل أيضًا أن تكون على دراية ببعض المخاطر. لا يمكن تحريك عنصر details
، max-height: auto
ما كنت تأمله ، ولا يمكنك إضافة قيمة ضخمة للارتفاع الأقصى بشكل موثوق وتتوقع فتح جميع لوحات المحتوى كما هو متوقع.
لإعادة تكرار نهجنا هنا: قم بقياس الحاوية ، وتخزين ارتفاعها كخاصية مخصصة لـ CSS ، وإخفاء المحتوى ثم استخدام مفتاح تبديل بسيط للتبديل بين max-height
والارتفاع الذي قمت بتخزينه في الخاصية المخصصة.
قد لا تكون الطريقة الأفضل أداءً على الإطلاق ، لكنني وجدت أنها مناسبة تمامًا في معظم الحالات وتستفيد من كونها سهلة التنفيذ نسبيًا.