داخل پرس و جوی ظرف polyfill

جرالد موناکو
Gerald Monaco

جستجوهای کانتینر یک ویژگی جدید CSS است که به شما امکان می‌دهد منطق استایلی بنویسید که ویژگی‌های یک عنصر والد (مثلاً عرض یا ارتفاع آن) را هدف قرار می‌دهد تا به فرزندانش استایل دهید. اخیراً به‌روزرسانی بزرگی برای polyfill منتشر شد که همزمان با فرود پشتیبانی در مرورگرها بود.

در این پست، می‌توانید نگاهی به نحوه عملکرد پلی‌فیل، چالش‌هایی که بر آن غلبه می‌کند و بهترین شیوه‌های استفاده از آن برای ارائه یک تجربه کاربری عالی برای بازدیدکنندگان خود بیاندازید.

زیر کاپوت

انتقال

هنگامی که تجزیه کننده CSS در داخل یک مرورگر با یک قانون ناشناخته at-rule، مانند قانون جدید @container مواجه می شود، آن را به گونه ای کنار می گذارد که گویی هرگز وجود نداشته است. بنابراین، اولین و مهمترین کاری که polyfill باید انجام دهد این است که یک پرس و جو @container را به چیزی که دور انداخته نمی شود، تبدیل کند.

اولین گام در ترجمه، تبدیل قاعده @container سطح بالا به یک پرسش رسانه @ است. این بیشتر تضمین می کند که محتوا با هم گروه بندی می شود. به عنوان مثال، هنگام استفاده از CSSOM API و هنگام مشاهده منبع CSS.

قبل از
@container (width > 300px) {
  /* content */
}
بعد از
@media all {
  /* content */
}

قبل از پرس و جوهای کانتینر، CSS راهی برای یک نویسنده برای فعال یا غیرفعال کردن گروه‌هایی از قوانین دلخواه نداشت. برای پر کردن این رفتار، قوانین داخل یک پرس و جوی کانتینر نیز باید تغییر کند. به هر @container شناسه منحصر به فرد خود داده می شود (مثلاً 123 )، که برای تبدیل هر انتخابگر به گونه ای استفاده می شود که فقط زمانی اعمال شود که عنصر دارای ویژگی cq-XYZ از جمله این شناسه باشد. این ویژگی توسط polyfill در زمان اجرا تنظیم می شود.

قبل از
@container (width > 300px) {
  .card {
    /* ... */
  }
}
بعد از
@media all {
  .card:where([cq-XYZ~="123"]) {
    /* ... */
  }
}

به استفاده از کلاس شبه :where(...) توجه کنید. به طور معمول، گنجاندن یک انتخابگر ویژگی اضافی، ویژگی انتخابگر را افزایش می دهد. با شبه کلاس، شرایط اضافی را می توان با حفظ ویژگی اصلی اعمال کرد. برای اینکه بفهمید چرا این مهم است، به مثال زیر توجه کنید:

@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}

با توجه به این CSS، یک عنصر با کلاس .card باید همیشه color: red ، زیرا قانون بعدی همیشه قانون قبلی را با همان انتخابگر و ویژگی لغو می کند. تبدیل قانون اول و اضافه کردن یک انتخابگر ویژگی اضافی بدون :where(...) بنابراین ویژگی را افزایش می دهد و باعث می شود که color: blue به اشتباه اعمال شود.

با این حال، شبه کلاس :where(...) نسبتا جدید است. برای مرورگرهایی که از آن پشتیبانی نمی‌کنند، polyfill یک راه‌حل امن و آسان ارائه می‌کند: می‌توانید عمداً با افزودن دستی یک انتخاب‌گر ساختگی :not(.container-query-polyfill) به قوانین @container ، ویژگی قوانین خود را افزایش دهید:

قبل از
@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}
بعد از
@container (width > 300px) {
  .card:not(.container-query-polyfill) {
    color: blue;
  }
}

.card {
  color: red;
}

این یک سری فواید دارد:

  • انتخابگر در منبع CSS تغییر کرده است، بنابراین تفاوت در ویژگی به صراحت قابل مشاهده است. این همچنین به عنوان سند عمل می کند، بنابراین می دانید زمانی که دیگر نیازی به پشتیبانی از راه حل یا polyfill ندارید، چه چیزی تحت تأثیر قرار می گیرد.
  • ویژگی قوانین همیشه یکسان خواهد بود، زیرا polyfill آن را تغییر نمی دهد.

در طول انتقال، polyfill این ساختگی را با انتخابگر ویژگی با همان ویژگی جایگزین می‌کند. برای جلوگیری از هرگونه غافلگیری، polyfill از هر دو انتخابگر استفاده می کند: انتخابگر منبع اصلی برای تعیین اینکه آیا عنصر باید ویژگی polyfill را دریافت کند یا خیر، و انتخابگر transpiled برای یک ظاهر طراحی استفاده می شود.

شبه عناصر

یک سوالی که ممکن است از خود بپرسید این است: اگر polyfill مقداری مشخصه cq-XYZ را روی یک عنصر تنظیم کند تا شناسه کانتینر منحصر به فرد 123 را شامل شود، چگونه می‌توان از شبه عناصری که نمی‌توانند ویژگی‌هایی را روی آنها تنظیم کرده باشند پشتیبانی کرد؟

عناصر شبه همیشه به یک عنصر واقعی در DOM متصل می شوند که عنصر مبدا نامیده می شود. در طول انتقال، انتخابگر شرطی به جای این عنصر واقعی اعمال می شود:

قبل از
@container (width > 300px) {
  #foo::before {
    /* ... */
  }
}
بعد از
@media all {
  #foo:where([cq-XYZ~="123"])::before {
    /* ... */
  }
}

به جای تبدیل شدن به #foo::before:where([cq-XYZ~="123"]) (که نامعتبر است)، انتخابگر شرطی به انتهای عنصر مبدا، #foo منتقل می‌شود.

با این حال، این تمام چیزی نیست که لازم است. یک کانتینر مجاز نیست هر چیزی را که در آن وجود ندارد تغییر دهد (و یک کانتینر نمی‌تواند در درون خود باشد)، اما در نظر بگیرید که اگر #foo خود عنصر کانتینری باشد که مورد پرسش قرار می‌گیرد، این دقیقاً همان چیزی است که اتفاق می‌افتد. ویژگی #foo[cq-XYZ] به اشتباه تغییر می‌کند و هر قانون #foo به اشتباه اعمال می‌شود.

برای تصحیح این موضوع، polyfill در واقع از دو ویژگی استفاده می‌کند: یکی که فقط می‌تواند برای یک عنصر توسط والد اعمال شود، و دیگری که یک عنصر می‌تواند برای خودش اعمال کند. ویژگی دوم برای انتخابگرهایی که شبه عناصر را هدف قرار می دهند استفاده می شود.

قبل از
@container (width > 300px) {
  #foo,
  #foo::before {
    /* ... */
  }
}
بعد از
@media all {
  #foo:where([cq-XYZ-A~="123"]),
  #foo:where([cq-XYZ-B~="123"])::before {
    /* ... */
  }
}

از آنجایی که یک کانتینر هرگز اولین ویژگی ( cq-XYZ-A ) را برای خود اعمال نمی‌کند، اولین انتخابگر تنها در صورتی مطابقت می‌کند که یک کانتینر والد دیگر شرایط ظرف را داشته باشد و آن را اعمال کند.

واحدهای نسبی کانتینری

پرس و جوهای کانتینر همچنین با چند واحد جدید ارائه می شوند که می توانید از آنها در CSS خود استفاده کنید، مانند cqw و cqh برای 1٪ از عرض و ارتفاع (به ترتیب) نزدیکترین محفظه والد مناسب. برای پشتیبانی از اینها، واحد با استفاده از CSS Custom Properties به یک عبارت calc(...) تبدیل می شود. polyfill مقادیر این ویژگی ها را از طریق سبک های درون خطی در عنصر ظرف تنظیم می کند.

قبل از
.card {
  width: 10cqw;
  height: 10cqh;
}
بعد از
.card {
  width: calc(10 * --cq-XYZ-cqw);
  height: calc(10 * --cq-XYZ-cqh);
}

همچنین واحدهای منطقی مانند cqi و cqb برای اندازه درون خطی و اندازه بلوک (به ترتیب) وجود دارد. اینها کمی پیچیده‌تر هستند، زیرا محورهای درون خطی و بلوک توسط writing-mode عنصر با استفاده از واحد تعیین می‌شوند، نه عنصر مورد پرسش. برای پشتیبانی از این موضوع، polyfill یک سبک درون خطی را برای هر عنصری که writing-mode آن با والد آن متفاوت است اعمال می کند.

/* Element with a horizontal writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqw);
--cq-XYZ-cqb: var(--cq-XYZ-cqh);

/* Element with a vertical writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqh);
--cq-XYZ-cqb: var(--cq-XYZ-cqw);

اکنون، واحدها می توانند مانند قبل به ویژگی سفارشی CSS مناسب تبدیل شوند.

خواص

پرس و جوهای Container همچنین چند ویژگی CSS جدید مانند container-type و container-name اضافه می کنند. از آنجایی که API هایی مانند getComputedStyle(...) نمی توان با ویژگی های ناشناخته یا نامعتبر استفاده کرد، این ها نیز پس از تجزیه به ویژگی های سفارشی CSS تبدیل می شوند. اگر یک ویژگی قابل تجزیه نباشد (مثلاً به دلیل اینکه حاوی یک مقدار نامعتبر یا ناشناخته است)، به سادگی برای مرورگر به حال خود رها می شود.

قبل از
.card {
  container-name: card-container;
  container-type: inline-size;
}
بعد از
.card {
  --cq-XYZ-container-name: card-container;
  --cq-XYZ-container-type: inline-size;
}

این ویژگی‌ها هر زمان که کشف شوند تغییر می‌کنند و به polyfill اجازه می‌دهند تا با سایر ویژگی‌های CSS مانند @supports به خوبی بازی کند. این عملکرد اساس بهترین روش‌ها برای استفاده از پلی‌فیل است که در زیر توضیح داده شده است.

قبل از
@supports (container-type: inline-size) {
  /* ... */
}
بعد از
@supports (--cq-XYZ-container-type: inline-size) {
  /* ... */
}

به طور پیش‌فرض، ویژگی‌های سفارشی CSS به ارث می‌رسد، به این معنی که برای مثال، هر فرزند .card مقدار --cq-XYZ-container-name و --cq-XYZ-container-type به خود می‌گیرد. قطعاً خواص بومی اینگونه نیست. برای حل این مشکل، polyfill قانون زیر را قبل از هر سبک کاربر وارد می‌کند، و اطمینان حاصل می‌کند که هر عنصر مقادیر اولیه را دریافت می‌کند، مگر اینکه عمداً توسط قانون دیگری لغو شود.

* {
  --cq-XYZ-container-name: none;
  --cq-XYZ-container-type: normal;
}

بهترین شیوه ها

در حالی که انتظار می رود اکثر بازدیدکنندگان زودتر مرورگرهایی را با پشتیبانی از پرس و جوی کانتینر داخلی اجرا کنند، اما همچنان مهم است که به بازدیدکنندگان باقیمانده خود تجربه خوبی ارائه دهید.

در طول بارگذاری اولیه، قبل از اینکه polyfill بتواند صفحه را چیدمان کند، اتفاقات زیادی باید بیفتد:

  • polyfill باید بارگذاری و مقداردهی اولیه شود.
  • استایل شیت ها باید تجزیه و ترجمه شوند. از آنجایی که هیچ API برای دسترسی به منبع خام یک شیوه نامه خارجی وجود ندارد، ممکن است نیاز باشد که به صورت ناهمزمان دوباره واکشی شود، هرچند در حالت ایده آل فقط از حافظه پنهان مرورگر.

اگر این نگرانی ها به دقت توسط polyfill برطرف نشود، به طور بالقوه می تواند Core Web Vitals شما را پس بزند.

برای سهولت در ارائه تجربه دلپذیر به بازدیدکنندگان، پلی‌فیل برای اولویت‌بندی تاخیر ورودی اول (FID) و جابجایی چیدمان تجمعی (CLS) طراحی شده است، که به طور بالقوه به قیمت بزرگ‌ترین رنگ محتوایی (LCP) تمام می‌شود. به طور مشخص، polyfill هیچ تضمینی نمی کند که درخواست های ظرف شما قبل از اولین رنگ ارزیابی شود . این بدان معناست که برای بهترین تجربه کاربری، باید اطمینان حاصل کنید که هر محتوایی که اندازه یا موقعیت آن با استفاده از جستجوهای کانتینر تحت تأثیر قرار می‌گیرد، تا زمانی که polyfill بارگیری و CSS شما را ترجمه نکرده است، پنهان می‌شود. یکی از راه‌های انجام این کار استفاده از قانون @supports است:

@supports not (container-type: inline-size) {
  #content {
    visibility: hidden;
  }
}

توصیه می شود این را با یک انیمیشن بارگیری CSS خالص، کاملاً روی محتوای (پنهان) خود ترکیب کنید تا به بازدیدکننده بگویید که چیزی در حال رخ دادن است. شما می توانید نسخه ی نمایشی کامل این رویکرد را در اینجا پیدا کنید.

این روش به چند دلیل توصیه می شود:

  • یک لودر CSS خالص هزینه های اضافی را برای کاربران با مرورگرهای جدیدتر به حداقل می رساند، در حالی که بازخورد سبکی را برای مرورگرهای قدیمی و شبکه های کندتر ارائه می دهد.
  • با ترکیب موقعیت مطلق لودر با visibility: hidden ، از تغییر طرح جلوگیری می کنید.
  • پس از بارگیری polyfill، این شرط @supports متوقف می شود و محتوای شما آشکار می شود.
  • در مرورگرهایی با پشتیبانی داخلی از پرس و جوهای کانتینر، این شرایط هرگز عبور نمی کند، و بنابراین صفحه همانطور که انتظار می رود در اولین رنگ نمایش داده می شود.

نتیجه گیری

اگر علاقه مند به استفاده از پرس و جوهای کانتینر در مرورگرهای قدیمی هستید، polyfill را امتحان کنید. در صورت مواجهه با هر گونه مشکلی از ثبت مشکل دریغ نکنید.

ما بی صبرانه منتظر دیدن و تجربه چیزهای شگفت انگیزی هستیم که با آن خواهید ساخت.