انتخاب تکنیک نماد
ذخیرهسازی و پردازش نماد انتخابهای جداگانهای هستند. برای مثال، یک 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 را متورم میکند و نمیتواند بهطور جداگانه ذخیره شود، بنابراین برای مجموعه کوچکی از نمادها که در هر صفحه ظاهر میشوند مناسب است. فایلهای تکی بهطور مستقل ذخیره موقت میشوند و درصورت درخواست، هرکدام با هزینه یک درخواست بار میشوند. اسپرایت یک درخواست قابل ذخیره در حافظه پنهان برای کل مجموعه است، اما هر نماد را چه استفاده شود یا نه بارگیری میکند، و <use> فقط میتواند به اسپرایتی از همان مبدأ ارجاع دهد. برای تعداد کمی از نمادهای اصلی، جاسازی را ترجیح دهید و برای مجموعههای بزرگتر، از فایلهای تکی یا اسپریت استفاده کنید.
اگر صفحهای هزاران نماد را بهطور همزمان ارائه میکند، صفحه و داراییهای واقعی را محک بزنید. در آزمون تنش، 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("/icons/trash.svg")) تعریف کنید. وقتی نمادی از فایل SVG خارجی بار میشود، از نشانی وب نسبی ریشه یا URI دادههای جاسازیشده استفاده کنید. نشانی وب نسبی که ازطریق ویژگی سفارشی استفاده میشود میتواند نسبت به برگه سبک نگارشی که نماد در آن استفاده میشود حلوفصل شود، نه برگه سبک نگارشی که ویژگی سفارشی در آن تعریف شده است. اگر آن برگههای سبک نگارش در دایرکتوریهای متفاوتی باشند، ممکن است نشانی وب به فایل اشتباهی تجزیه شود.
برای حفظ نشانی وب نسبتبه برگه سبک نگارش که کد نماد را تعریف میکند، یک ویژگی ثبتشده برای کمک درنظر گرفته شده است: مقدار محاسبهشده <url> یا <image> ثبتشده نسبتبه برگه سبک نگارشی که آن را اعلام کرده است حلوفصل میشود. به ثبت کردن کد بهعنوان <image> با نشانی وب در initial-value اتکا نکنید. Chrome و Safari نشانیهای وب initial-value را در سند حل میکنند، بنابراین فقط در Firefox کار میکند. Chrome همچنین مقادیر اعلامشده در :root را دربرابر سند حل میکند، مگراینکه نحو با <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");
}
اکنون هر نماد در دو مکان قرار دارد و کدهای تکراری بسیار بیشتری وجود دارد، بنابراین این کار فقط زمانی ارزش دارد که پروژه دارای برگه سبک نگارش در دایرکتوریهای مختلف و تعداد قابلمدیری از نمادها باشد. برای بیشاز دوازده نماد، بهجای نوشتن دستی هر @property، کدهای --icon-* (بهاستثنای --icon-style-*) را از یک اسکریپت با CSS.registerProperty و بااستفاده از همان نحو ثبت کنید. ویژگیهای ثبتشده سراسری هستند و در ریشههای سایه کار نمیکنند، بنابراین ممکن است برای هر پروژهای مناسب نباشند. اگر نشانیهای وب نسبی ریشه یا 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>داخل یک اسپریت خارجی برای صفحه اعمال نمیشود، و برگههای سبک نگارش صفحه داخل درخت ارجاعشده اعمال نمیشوند. فقط ویژگیهای موروثی و ویژگیهای سفارشی منتقل میشوند. - ارجاعهای خارجی فقط هممبدأ هستند؛ اسپرایتهای میانمبدئی بدون اطلاعرسانی ناموفق میشوند.
- اسپرایتهای درونخطیشده در سند، جاینام
idصفحه را همرسانی میکنند، بنابراین شناسههای نماد جاینام برای جلوگیری از تداخل.
SVGs تزئینی را با aria-hidden="true" علامتگذاری کنید و به کنترل حاوی آن نام دسترسپذیر خودش را بدهید، مانند سیستم نماد CSS.
راهبردهای جایگزین
داراییهای سفارشی ثبتشده
بدون @property، --icon-start و --icon-end مانند خصوصیات سفارشی ارثبری معمولی رفتار میکنند، بنابراین نمادی که روی یک عنصر تنظیم شده است، روی فرزندان آن نیز نمایش داده میشود. با بازنشانی ویژگیهای فرزندان در پُرسمان سبک (@container style(--icon-start) { * { --icon-start: initial; } }) از این مشکل جلوگیری کنید، همان رویکردی که راهحل Safari در زیر استفاده میکند.
پُرسمانهای سبک محتوا
برای پشتیبانی از مرورگرهای بدون Container Style Queries، از کلاسها یا انتخابگرهای مؤلفه بهعنوان قلابهای بازگشتی استفاده کنید. انتخاب نماد را در ویژگیهای سفارشی نگه دارید؛ گزینشگرهای سبک درونخطی بهتنهایی نمادهای اختصاصدادهشده ازطریق قوانین 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-end با margin-inline-start روی محتوای غیرخالی اعمال کنید.
این الگوی 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 تداخلهای پویانمایی را کاهش میدهد.
اشکال 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;
}
}
}
}
بازنشانی کردن مانع از انتشار مقدار نماد به فرزندان میشود. نگه داشتن آن در یک لایه باعث میشود که اعلانهای نماد بدون لایه بر بازنشانی اولویت داشته باشند.