استخدام الرموز وإدارتها

اختيار أسلوب الرمز

يُعدّ تخزين الرموز وعرضها خيارَين منفصلَين. على سبيل المثال، يمكن أن يوفّر ملف SVG sprite رمزًا لنظام رموز CSS أو يمكن الإشارة إليه باستخدام <use>.

  • نظام رموز CSS: يُستخدَم عندما يكون على CSS اختيار وقت عرض الرمز والرمز الذي يجب عرضه. استخدِم قناعًا لرمز أحادي اللون أو صورة خلفية لرمز ملوّن بالكامل.
  • ملف SVG مع <use>: يُستخدَم لإعادة استخدام رمز عندما تكون خيارات التصميم المضمّنة كافية. لا يمكن لـ CSS تصميم محتوى SVG المُشار إليه أو تحريكه بشكل عشوائي، ولكن يمكن تمرير الخصائص الموروثة والخصائص المخصّصة عندما يكون الرمز مصمّمًا لاستخدامها. على سبيل المثال، يمكن أن يستخدم المسار fill="var(--icon-accent, currentColor)" للسماح للغة CSS بتلوين هذا الجزء من الرمز.
  • ملفات SVG المضمّنة: يمكنك استخدامها عندما تحتاج إلى تصميم عشوائي أو حركة لمحتوى ملف SVG.
  • <img> مع فلاتر CSS: تجنَّب ذلك لإعادة تلوين الرموز. لا يمكن للفلاتر تطبيق قيمة لون CSS عشوائية على الصورة بشكل موثوق، لذا فإنّ إعادة التلوين تكون صعبة وغير مرنة.

لاتخاذ القرار، اطرح سؤالين. هل تحتاج CSS إلى اختيار الرمز أو تبديله (الإعدادات التلقائية للمكوّنات، تغييرات الحالة، المظاهر)؟ إذا كانت الإجابة بنعم، استخدِم نظام رموز CSS. هل تحتاج إلى الوصول إلى داخل الرمز خارج اللون؟ إذا كانت الإجابة نعم وكنت تتحكّم في مصدر الرمز، صمِّم الرمز باستخدام خصائص مخصّصة واستخدِم <use>. وإذا لم تكن تتحكّم في المصدر، استخدِم SVG مضمّنًا. نادرًا ما تعرض مجموعات الرموز التابعة لجهات خارجية خصائص مخصّصة، لذا فإنّ استخدام <use> معها يمنحك اللون والقليل من الخصائص الأخرى.

الرموز المضمّنة أو البعيدة

يمكن لكلّ من نظام الرموز في CSS و<use> الحصول على الرموز من معرّف موارد منتظم (URI) للبيانات أو ملف فردي أو صورة مجمّعة. يؤدي تضمين الرموز (عناوين URI للبيانات أو ملفات SVG المضمّنة) إلى تجنُّب إرسال طلب وعرض الرموز مع أول عملية عرض للمحتوى، ولكنّه يؤدي إلى تضخيم ملفات CSS أو HTML ولا يمكن تخزينه مؤقتًا بشكل منفصل، لذا فهو مناسب لمجموعة صغيرة من الرموز التي تظهر في كل صفحة. يتم تخزين الملفات الفردية مؤقتًا بشكل مستقل ويتم تحميلها عند الطلب، بتكلفة طلب واحد لكل منها. يُعدّ ملف sprite طلبًا واحدًا قابلاً للتخزين المؤقت للمجموعة بأكملها، ولكنّه ينزّل كل رمز سواء تم استخدامه أم لا، ولا يمكن لـ <use> الإشارة إلا إلى ملف sprite من المصدر نفسه. يُفضَّل تضمين عدد قليل من الرموز الأساسية واستخدام ملف sprite أو ملفات فردية للمجموعات الأكبر.

إذا كانت الصفحة تعرض آلاف الرموز في آنٍ واحد، يمكنك قياس أداء الصفحة ومواد العرض الفعلية. في اختبار الضغط، كان لملفات SVG المضمّنة ميزة أداء صغيرة بهذا المقياس، ولكن النتائج تعتمد على المتصفّح والتنفيذ. اختبار الضغط لرموز SVG من Cloud Four

النهج الأساسي: محرّك الرموز المستند إلى CSS

الرموز هي رموز تصميم: كل رمز هو خاصية مخصّصة تحتوي على صورة، ويمكن لأي عنصر عرض رمز من خلال ضبط --icon-start أو --icon-end. يجمع المحرّك بين ثلاث ميزات في CSS:

  • تمنع الخصائص المخصّصة المسجّلة (@property) اكتساب --icon-start و--icon-end، وبالتالي لا يتم تطبيق الرمز الذي تم ضبطه على مكوّن معيّن على المكوّنات الفرعية.
  • طلبات البحث عن نمط الحاوية (@container style(...)) ترصد متى يتم تعيين رمز لعنصر معيّن وتعرضه، بدون أي تغييرات في الفئة أو الترميز.
  • أقنعة CSS وcurrentColor تعرض الرمز في عنصر زائف يتبع لون نص العنصر. تتم قراءة الحجم واللون والتباعد والإزاحة العمودية من سمات --icon-style-* القابلة للتوريث مع قيم احتياطية معقولة، بحيث يمكن إعادة تصميم شجرة فرعية كاملة من عنصر رئيسي. تمنع البادئة --icon-style- تعارض هذه الإعدادات مع رموز الرموز المميزة نفسها، والتي تقع أيضًا ضمن --icon-*.

تحديد الرموز المميّزة لتصميم الرموز

حدِّد رموز SVG وأي عمليات إلغاء لـ --icon-style-* كخصائص مخصّصة قابلة لإعادة الاستخدام باستخدام معرّفات الموارد المنتظمة (URI) لبيانات SVG المضمّنة أو عناوين URL النسبية للجذر (مثل url("/icons/trash.svg")). عند تحميل رمز من ملف SVG خارجي، استخدِم عنوان URL نسبيًا للجذر أو معرّف موارد منتظم (URI) للبيانات المضمّنة. يمكن أن يتم تحليل عنوان URL نسبي مستخدَم من خلال خاصية مخصّصة بالنسبة إلى ورقة الأنماط التي يتم فيها استخدام الرمز، بدلاً من ورقة الأنماط التي يتم فيها تحديد الخاصية المخصّصة. إذا كانت أوراق الأنماط هذه في أدلة مختلفة، قد يؤدي عنوان URL إلى الملف غير الصحيح.

للحفاظ على عنوان URL نسبيًا لورقة الأنماط التي تحدّد رمز الرمز المميّز، من المفترض أن تساعد السمة المسجّلة: يتم حلّ القيمة المحسوبة لسمة <url> أو <image> مسجّلة مقابل ورقة الأنماط التي أعلنت عنها. لا تعتمد على تسجيل الرمز المميّز كـ <image> مع عنوان URL في initial-value. يحلّ Chrome وSafari عناوين URL لـ initial-value في المستند، لذا لا تعمل هذه الميزة إلا في Firefox. يحلّ Chrome أيضًا القيم المُعلَن عنها في :root مقابل المستند ما لم يبدأ البناء اللغوي بـ <url>.

عندما لا يمكن تجنُّب عناوين URL النسبية، مثل أوراق الأنماط في أدلة مختلفة، سجِّل الرمز المميّز باستخدام بنية تبدأ بـ <url> وأعلِن عن عنوان URL الفعلي في :root. يتم حلّ هذا المسار وفقًا لورقة الأنماط المُعلَنة في كل متصفّح:

@property --icon-caret {
  /* The syntax must start with <url>. A syntax that starts with <image> resolves against the document in Chrome. */
  syntax: "<url> | <image> | none";
  inherits: true;
  initial-value: none;
}

:root {
  /* Declared here, the URL resolves against this stylesheet */
  --icon-caret: url("icons/caret.svg");
}

كل رمز الآن موجود في مكانين، وهناك الكثير من النماذج المعيارية، لذا فإن هذا يستحق فقط عندما يحتوي المشروع على أوراق أنماط في أدلة مختلفة وعدد يمكن إدارته من الرموز. بعد تجاوز عشرة رموز تقريبًا، سجِّل الرموز المميّزة --icon-* (باستثناء --icon-style-*) من نص برمجي باستخدام CSS.registerProperty، مع استخدام البنية نفسها، بدلاً من كتابة كل @property يدويًا. تكون السمات المسجّلة عامة ولا تعمل في جذور الظل، لذا قد لا يناسب ذلك كل المشاريع. إذا كانت عناوين URL النسبية للجذر أو معرّفات الموارد المنتظمة (URI) للبيانات خيارًا متاحًا، فهي أبسط وتعمل في كل مكان.

:root {
  /* Optional: override the built-in fallbacks (1em, currentColor, 0.3em, -0.02em) */
  --icon-style-size: 1em;
  --icon-style-spacing: 0.4em;
  --icon-trash: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Cpath d='M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0-1 14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2L4 6h16Z'/%3E%3C/svg%3E");
  --icon-favorite: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z'/%3E%3C/svg%3E");
}

عرض الرموز من الرموز المميّزة

يعرض العنصر رمزًا من خلال ضبط --icon-start (في البداية) أو --icon-end (في النهاية) على أحد الرموز المميّزة أعلاه. يحوّل جزءان من CSS هذا التعيين إلى رمز معروض.

أولاً، سجِّل --icon-start و--icon-end باستخدام @property. تقبل بنية "*" أي قيمة، وinherits: false هو الجزء المهم: بدونه، سيتم توريث مجموعة رموز على زر إلى كل عنصر فرعي، وسيعرض كل منها الرمز أيضًا.

@property --icon-start {
  syntax: "*";
  inherits: false;
}

@property --icon-end {
  syntax: "*";
  inherits: false;
}

ثانيًا، يرصد طلب البحث عن نمط الحاوية وقت ضبط السمة على عنصر معيّن ويعرض الرمز في عنصر زائف. بما أنّ العنصر الزائف لا يكتسب السمة --icon-start (تم تسجيله على أنّه غير قابل للاكتساب)، يعيد البيان الأول توجيه القيمة صراحةً باستخدام inherit. تتعامل التعريفات المتبقية مع الحجم والقناع والمحاذاة والتباعد. بما أنّ التعبئة تكون تلقائيًا currentColor، يعتمد الرمز لون نص العنصر ويتّبع انتقالاته.

/* Style query for elements configuring a start icon */
@container style(--icon-start) {
  ::before {
    --icon-start: inherit; /* Forward the value inside the query block */
    content: "" / var(--icon-style-alt, "");
    display: inline-block;
    width: var(--icon-style-size, 1em);
    height: var(--icon-style-size, 1em);
    background-color: var(--icon-style-color, currentColor);
    mask: var(--icon-start) no-repeat center / contain;
    /* Center on the cap height, nudged slightly up for optical balance */
    vertical-align: calc(0.5cap - var(--icon-style-size, 1em) / 2 + var(--icon-style-voffset, -0.02em));
    flex: none;
    pointer-events: none;
    forced-color-adjust: none;
  }

  /* Automatically add spacing ONLY if the element has other text or sibling content */
  :where(:not(.icon, :empty))::before {
    margin-inline-end: var(--icon-style-spacing, 0.3em);
  }
}

كل تعريف له مكانه الخاص:

  • ‫vertical-align: تتم محاذاة middle مع نصف ارتفاع الحرف x، وهو منخفض بشكل واضح في معظم مجموعات الرموز. يؤدي التوسيط على 0.5cap إلى إبقاء مركز الرمز ثابتًا مع زيادة حجم --icon-style-size، ويعوّض --icon-style-voffset السالب الصغير عن تركيز العين على المساحة فوق الرمز أكثر من المساحة تحته.
  • ‫flex: none: يحتوي العنصر الزائف الفارغ على حد أدنى للحجم يساوي صفرًا، لذا داخل زر flex، يكون أول شيء يتقلّص عندما يكون التصنيف طويلاً، وقد لا يظهر على الإطلاق. حجم الرمز هو قرار تصميمي، وليس شيئًا يجب أن يتفاوض عليه ضغط التخطيط.
  • pointer-events: none: يمنع الرمز من حجب النقرات أو أن يصبح event.target.
  • forced-color-adjust: none: في وضع الألوان المفروضة، يستبدل المتصفّح السمة background-color، ما يؤدي إلى اختفاء الرمز المقنَّع. يتم الاحتفاظ بتعبئة القناع، ويظلّ currentColor يتّبع لون النص المفروض.
  • content: "" / var(--icon-style-alt, ""): يتيح النص البديل للمحتوى الذي تم إنشاؤه لعنصر تحكّم يتضمّن رمزًا فقط الحصول على اسم العنصر الظاهر على واجهة المستخدم من الرمز عبر --icon-style-alt (يُرجى الاطّلاع على "عناصر التحكّم التي تتضمّن رمزًا فقط" أدناه). يؤدي النص البديل الفارغ التلقائي إلى إبقاء الرموز الزخرفية مخفية عن التكنولوجيا المساعِدة.

بالنسبة إلى الرمز اللاحق، اتّبِع النمط نفسه مع --icon-end و::after بدلاً من --icon-start و::before. استخدِم mask: var(--icon-end) وطبِّق التباعد باستخدام margin-inline-start بدلاً من margin-inline-end.

العناصر المستقلة والعناصر التي تحتوي على رموز فقط

بالنسبة إلى الرموز المستقلة أو الأزرار التي تحتوي على رموز فقط، طبِّق السمة .icon مباشرةً على العنصر الفارغ (الزر نفسه لعنصر تحكّم يحتوي على رمز فقط) واضبط رمزه باستخدام السمة --icon. في هذا المثال، --icon هي السمة ذات المستوى الأعلى، وتعمل القاعدة .icon:empty على ربطها بـ --icon-start لرمز بادئ:

.icon:empty {
  --icon-start: var(--icon);
}

أمثلة على استخدام هذه العبارة في جملة

الإعدادات التلقائية للمكوّنات في ورقة الأنماط

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

.button-delete {
  --icon-start: var(--icon-trash);
}

a[href^="http"]:not([href*="example.com"]) {
  --icon-end: var(--icon-external);
}
<button class="button-delete">Delete item</button>

تغيير الرموز حسب الحالة

بما أنّ الرمز هو مجرد سمة، فإنّ تغيير الحالة هو تجاوز عادي لـ CSS. ما مِن عنصر ثانٍ لعرضه أو إخفائه وما مِن فئة لتبديلها:

.button-expand {
  --icon-start: var(--icon-caret-right);
}

.button-expand[aria-expanded="true"] {
  --icon-start: var(--icon-caret-down);
}

وينطبق الأمر نفسه على العرض التقديمي: يمكنك ضبط --icon-style-size أو --icon-style-color أو --icon-style-spacing على حاوية مثل شريط الأدوات، وسيتم تعديل كل رمز بداخله.

رموز فريدة بأنماط مضمّنة

يمكن لأي عنصر لا تغطيه أي قاعدة مكوّن أن يضبط السمة نفسها بشكل مضمّن، على سبيل المثال <button style="--icon-start: var(--icon-trash)">. يجب التعامل مع هذا الخيار كحلّ بديل وليس كخيار تلقائي: بعد ظهور الرمز نفسه على نوع العنصر نفسه أكثر من مرة، يجب نقله إلى قاعدة ورقة أنماط.

عناصر تحكّم بالرموز فقط

تستخدم الأزرار التي تحتوي على رمز فقط الفئة .icon و--icon الموضّحة أعلاه. امنح الزر اسم العنصر الظاهر على واجهة المستخدم باستخدام aria-label، أو اسمح للرمز بتقديم اسم من خلال --icon-style-alt. وبدون أي منهما، يكون الرمز الذي تم إنشاؤه باستخدام CSS مزخرفًا. بالنسبة إلى الرموز المعلوماتية المستقلة، اعرض العنصر .icon باستخدام role="img" وaria-label:

.button-close {
  --icon: var(--icon-close);
  --icon-style-alt: "Close";
}
<!-- Icon-only interactive control, icon and name from the stylesheet -->
<button class="icon button-close"></button>

<!-- Icon-only interactive control, named via aria-label -->
<button class="icon" style="--icon: var(--icon-trash);" aria-label="Delete item"></button>

<!-- Standalone informative icon -->
<span class="icon" style="--icon: var(--icon-favorite);" role="img" aria-label="Favorite"></span>

التعامل مع SVG مباشرةً

عندما لا يكون نظام رموز CSS مناسبًا، استخدِم SVG المضمّن و<use> معًا بدلاً من استخدامهما كبديلَين. يمكنك تضمين ملف SVG في المرة الأولى التي يظهر فيها رمز على صفحة والإشارة إليه باستخدام <use href="#id"> لكل تكرار، فالمرجع المحلي لا يتطلب أي طلب وليس له قيود متعدّدة المصادر. يمكنك حجز صورة مجمّعة خارجية (<use href="/icons.svg#id">) للرموز التي تتم مشاركتها على صفحات متعددة.

باستخدام <use>، يتم استنساخ المحتوى المُشار إليه في شجرة ظل، لذا لا يمكن لصفحة CSS اختيار عناصرها. ما يصل إليه هو التوريث: color وcurrentColor وأي خاصية مخصصة. بالإضافة إلى سمات العرض في SVG، يمكن لـ CSS الحديثة تغيير العديد من سمات الهندسة أيضًا، لذا يمكن لرمز تتحكّم فيه أن يعرض بالضبط الروابط التي تحتاجها، على سبيل المثال stroke-width="var(--icon-stroke, 2)" أو حتى d: var(--icon-path, path(…)) في مسار معيّن.

لا يتوافق CSS d مع Safari، لذا لا تستخدِم هذا التخصيص إلا عندما تكون فجوة الدعم هذه مقبولة. إذا لم يكن بإمكانك التحكّم في الرمز، يمكنك تضمينه.

<use> ملاحظات مهمة:

  • يجب أن يكون viewBox على <symbol> أو <svg> المُشار إليه، لأنّ <svg> الخارجي الذي يلتف حول <use> لا يلتقطه من المرجع، لذا اضبط width وheight على الغلاف واحتفظ بـ viewBox مع الرمز.
  • لا تتجاوز الأنماط الحدود في أي من الاتجاهين: لا ينطبق <style> داخل sprite خارجي على الصفحة، ولا تنطبق أوراق أنماط الصفحة داخل الشجرة المُشار إليها. يتم تمرير الخصائص المكتسَبة والخصائص المخصّصة فقط.
  • تكون المراجع الخارجية من المصدر نفسه فقط، وتتعذّر معالجة الصور المجمّعة المتعدّدة المصادر بدون إرسال إشعار.
  • تشارك الصور المتحركة المضمّنة في المستند مساحة الاسم id الخاصة بالصفحة، لذا يجب استخدام معرّفات رموز مساحة الاسم لتجنُّب التعارضات.

ضَع علامة على ملفات SVG الزخرفية باستخدام aria-hidden="true" وأعطِ عنصر التحكّم الذي يحتوي عليها اسم العنصر الظاهر على واجهة المستخدم، كما هو الحال مع نظام رموز CSS.

الاستراتيجيات الاحتياطية

الخصائص المخصّصة المسجّلة

بدون @property، تعمل السمتان --icon-start و--icon-end كخصائص مخصّصة عادية يتم اكتسابها، لذا فإنّ الرمز الذي يتم ضبطه على عنصر سيظهر أيضًا على العناصر التابعة له. يمكنك منع ذلك من خلال إعادة ضبط السمات على العناصر الفرعية داخل طلب البحث عن الأنماط (@container style(--icon-start) { * { --icon-start: initial; } })، وهو الأسلوب نفسه الذي يستخدمه الحلّ البديل في Safari أدناه.

طلبات البحث عن أنماط الحاويات

لدعم المتصفّحات التي لا تتضمّن "طلبات البحث عن أنماط الحاويات"، استخدِم الفئات أو أدوات اختيار المكوّنات كخطافات احتياطية. يجب الاحتفاظ باختيار الرموز في الخصائص المخصّصة، لأنّ أدوات الاختيار المضمّنة وحدها لا ترصد الرموز المحدّدة من خلال قواعد CSS.

:where(.icon-start, .icon:empty)::before {
  content: image-set(linear-gradient(transparent 0 100%), var(--icon-start)) / var(--icon-style-alt, "");
  /* icon styles */
}

:where(.icon-start)::before {
  --icon-start: inherit;
}

:where(.icon:empty)::before {
  --icon-start: var(--icon);
}

:where(.icon-start:not(.icon, :empty))::before {
  margin-inline-end: var(--icon-style-spacing, 0.3em);
}

بالنسبة إلى الرموز اللاحقة، استخدِم الفئة الاحتياطية نفسها مع .icon-end و--icon-end و::after بدلاً من .icon-start و--icon-start و::before. يُرجى تطبيق التباعد على المحتوى غير الفارغ باستخدام margin-inline-start بدلاً من margin-inline-end.

يؤدي نمط image-set() هذا إلى جعل content غير صالح عندما لا يتم ضبط الخاصية المخصّصة، وبالتالي يعود إلى none ويختفي العنصر الزائف بدون إضافة فئة أو إزالتها. ولا تزيل الأنماط الأخرى للعنصر الزائف أو تمنع التعارضات مع قواعد ::before أو ::after الأخرى، لذا لا تستخدِم هذه الروابط إلا في حال توفّر هذه العناصر الزائفة للرموز.

الحلول البديلة لمتصفّح Safari

يتضمّن متصفّح Safari خطأين في العرض يؤثران في الرموز: قد يمنع WebKit #301609 ظهورها إلى أن يتفاعل المستخدم معها، ويؤثر #320220 أيضًا في عرضها. يعالج الحل البديل أدناه كلتا المشكلتين:

@keyframes webkit-301609 {}

@supports ((-webkit-nbsp-mode: normal) or (-webkit-touch-callout: none) or (font: -apple-system-body)) and (content-visibility: auto) {
  @layer webkit-301609-fix {
    ::before, ::after {
      animation: webkit-301609 0s;
    }
  }
}

يستهدف طلب البحث @supports متصفّح Safari فقط، ويقلّل @layer من التعارضات في الصور المتحركة.

يمكن أن يمنع الخطأ رقم 325900 في WebKit تطبيق التجاوز المباشر لـ --icon-start في بعض الحالات، مثل تغيير الرمز عند تمرير مؤشر الماوس فوقه. يؤثر ذلك في الحل البديل للمواقع المسجّلة غير الموروثة. بالنسبة إلى Safari، سجِّل --icon-start كسمة موروثة وأعِد ضبطها على العناصر التابعة داخل طلب البحث عن الأنماط:

@supports ((-webkit-nbsp-mode: normal) or
           (-webkit-touch-callout: none) or
           (font: -apple-system-body)) and (content-visibility: auto) {
  /* Override previous declaration */
  @property --icon-start {
    syntax: "*";
    inherits: true;
  }

  @layer webkit-325900-fix {
    @container style(--icon-start) {
      * {
        --icon-start: initial;
      }
    }
  }
}

تمنع إعادة الضبط قيمة الرمز من الانتشار إلى العناصر الفرعية. يؤدي الاحتفاظ به في طبقة إلى منح الأولوية لتعريفات الرموز غير المضمنة في طبقات على عملية إعادة الضبط.