ساختن عنصر «داستان‌ها»

نمای کلی پایه‌ای از نحوه ساختن تجربه‌ای مشابه «داستان‌های اینستاگرام» در وب.

In this post I want to share thinking on building a Stories component for the web that is responsive, supports keyboard navigation, and works across browsers.

نمونه

اگر ترجیح می‌دهید خودتان به‌صورت عملی ساختن این مؤلفه «داستان‌ها» را امتحان کنید، کدآزمای مؤلفه «داستان‌ها» را بررسی کنید.

اگر ویدیو را ترجیح می‌دهید، نسخه YouTube این پست را در اینجا ببینید:

نمای کلی

دو نمونه پرطرفدار از تجربه کاربری «داستان‌ها» عبارتند از «داستان‌های Snapchat» و «داستان‌های Instagram» (و البته «فلیت‌ها»). در اصطلاحات کلی تجربه کاربری، «داستان‌ها» معمولاً الگویی مختص تلفن همراه و مبتنی بر ضربه زدن برای پیمایش اشتراک‌های متعدد است. برای مثال، در اینستاگرام، کاربران داستان دوستشان را باز می‌کنند و عکس‌های آن را مرور می‌کنند. معمولاً این کار را با تعداد زیادی از دوستان به‌طور هم‌زمان انجام می‌دهند. با ضربه زدن روی سمت راست دستگاه، کاربر به داستان بعدی آن دوست می‌رود. با تند کشیدن به راست، کاربر به دوست دیگری می‌رود. عنصر «داستان» تا حدودی شبیه به گردونه است، اما امکان پیمایش آرایه چندبعدی را برخلاف آرایه تک‌بعدی فراهم می‌کند. مثل این است که داخل هر گردونه، گردونه دیگری وجود دارد. 🤯

آرایه چندبعدی دیداری‌شده بااستفاده از کارت‌ها. از چپ به راست، پشته‌ای از کارت‌های با حاشیه بنفش است و داخل هر کارت، ۱ تا چند کارت با حاشیه فیروزه‌ای وجود دارد. فهرست در فهرست.
گردونه اول دوستان
گردونه دوم «پشته‌ای» داستان‌ها
👍 فهرست در فهرست، یا: آرایه چندبعدی

انتخاب ابزار مناسب برای کار

All in all I found this component pretty straightforward to build, thanks to a few critical web platform features. بیایید آن‌ها را بپوشانیم!

شبکه CSS

چیدمان ما برای CSS Grid کار سختی نبود چون این ابزار به روش‌های قدرتمندی برای مدیریت محتوا مجهز است.

چیدمان دوستان

پوشاننده مؤلفه .stories اصلی ما یک نمای پیمایش افقی اولویت‌دار برای تلفن همراه است:

.stories {
  inline-size: 100vw;
  block-size: 100vh;

  display: grid;
  grid: 1fr / auto-flow 100%;
  gap: 1ch;

  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior: contain;
  touch-action: pan-x;
}

/* desktop constraint */
@media (hover: hover) and (min-width: 480px) {
  max-inline-size: 480px;
  max-block-size: 848px;
}
استفاده از حالت دستگاه Chrome DevTools برای برجسته کردن ستون‌های ایجادشده توسط Grid

بیایید چیدمان grid را تجزیه کنیم:

  • ما به‌طور صریح ناحیه نمایش را در تلفن همراه با 100vh و 100vw پر می‌کنیم و اندازه را در رایانه محدود می‌کنیم
  • / الگوهای ردیف و ستون ما را از هم جدا می‌کند
  • auto-flow به grid-auto-flow: column ترجمه می‌شود
  • الگوی جریان خودکار 100% است که در این مورد هرچه پهنای پنجره پیمایش باشد همان است

در تلفن همراه، این را به‌صورت اندازه ردیف درنظر بگیرید که ارتفاع ناحیه نمایش است و هر ستون پهنای ناحیه نمایش است. با ادامه دادن مثال «داستان‌های Snapchat» و «داستان‌های Instagram»، هر ستون داستان یکی از دوستان خواهد بود. می‌خواهیم داستان‌های دوستان در خارج از نمای دید ادامه پیدا کند تا جایی برای پیمایش داشته باشیم. Grid هر تعداد ستونی را که برای چیدمان HTML شما برای هر داستان دوست نیاز دارد، ایجاد می‌کند و یک ظرف پیمایش پویا و واکنش‌گرا برای ما ایجاد می‌کند. شبکه به ما امکان داد کل جلوه را متمرکز کنیم.

پشته‌سازی

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

با شبکه CSS، می‌توانیم یک شبکه تک‌سلولی (یعنی یک مربع) تعریف کنیم که در آن ردیف‌ها و ستون‌ها یک نام مستعار ([story]) را به اشتراک می‌گذارند، و سپس هر فرزند به آن فضای تک‌سلولی با نام مستعار اختصاص داده می‌شود:

.user {
  display: grid;
  grid: [story] 1fr / [story] 1fr;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
.story {
  grid-area: story;
  background-size: cover;
  …
}

این کار باعث می‌شود HTML ما ترتیب چیدمان را کنترل کند و همچنین همه عناصر را در جریان نگه می‌دارد. توجه کنید که چطور لازم نبود کاری با موقعیت‌یابی absolute یا z-index انجام دهیم و لازم نبود با height: 100% یا width: 100% جعبه را اصلاح کنیم. شبکه والد ازقبل اندازه نمای داستان را تعریف کرده است، بنابراین هیچ‌کدام از این مؤلفه‌های داستان نیازی به پر کردن آن ندارند!

نقاط میزان کردن پیمایش CSS

مشخصات CSS Scroll Snap Points قفل کردن عناصر در نمای پورت هنگام پیمایش را بسیار آسان می‌کند. قبل‌از اینکه این ویژگی‌های CSS وجود داشته باشند، باید از جاوا اسکریپت استفاده می‌کردید، و این کار… حداقلش این است که بگوییم دشوار بود. برای آشنایی با نحوه استفاده از آن‌ها، معرفی نقاط میزان کردن پیمایش CSS نوشته سارا دراسنر را بررسی کنید.

پیمایش افقی بدون و با سبک‌های scroll-snap-points. بدون آن، کاربران می‌توانند به‌طور معمول آزادانه پیمایش کنند. با این کار، مرورگر به‌آرامی روی هر مورد قرار می‌گیرد.
ولی
.stories {
  display: grid;
  grid: 1fr / auto-flow 100%;
  gap: 1ch;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior: contain;
  touch-action: pan-x;
}
Parent with overscroll defines snap behavior.
فرزند
.user {
  display: grid;
  grid: [story] 1fr / [story] 1fr;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
کودکان انتخاب می‌کنند که هدف عکس‌برداری باشند.

من «نقاط میزان کردن پیمایش» را به چند دلیل انتخاب کردم:

  • دسترس‌پذیری رایگان. مشخصات Scroll Snap Points بیان می‌کند که فشار دادن کلیدهای چپ‌بَر و راست‌بَر باید به‌طور پیش‌فرض در نقاط چسباندن حرکت کند.
  • مشخصات درحال رشد. مشخصات Scroll Snap Points همواره ویژگی‌ها و بهبودهای جدیدی دریافت می‌کند، که یعنی مؤلفه «داستان‌ها» من احتمالاً از این به بعد فقط بهتر خواهد شد.
  • سهولت پیاده‌سازی. Scroll Snap Points are practically built for the touch-centric horizontal-pagination use case.
  • اینرسی سبک پلاتفرم رایگان. هر پلاتفرم به سبک خودش پیمایش و توقف می‌کند، برخلاف لختی نرمال‌شده که می‌تواند سبک پیمایش و توقف غیرعادی داشته باشد.

سازگاری بین مرورگرها

ما در Opera،‏ Firefox،‏ Safari، و Chrome، به‌علاوه Android و iOS آزمایش کردیم. در اینجا خلاصه‌ای از ویژگی‌های وب که در قابلیت‌ها و پشتیبانی آن‌ها تفاوت‌هایی پیدا کردیم ارائه شده است.

البته برخی‌از CSSها اعمال نشدند، بنابراین برخی‌از پلاتفرم‌ها درحال‌حاضر بهینه‌سازی‌های UX را ازدست می‌دهند. از اینکه نیازی به مدیریت این ویژگی‌ها نداشتم لذت بردم و مطمئن هستم که درنهایت به مرورگرها و پلاتفرم‌های دیگر هم خواهند رسید.

scroll-snap-stop

گردونه‌ها یکی از موارد استفاده اصلی UX بودند که باعث ایجاد مشخصات CSS Scroll Snap Points شدند. برخلاف «داستان‌ها»، گردونه همیشه نیازی به توقف روی هر تصویر پس‌از تعامل کاربر با آن ندارد. ممکن است مشکلی نداشته باشد یا حتی تشویق شود که به‌سرعت در گردونه جابه‌جا شوید. ازسوی دیگر، داستان‌ها را بهتر است یکی‌یکی پیمایش کرد، و این دقیقاً همان چیزی است که scroll-snap-stop ارائه می‌دهد.

.user {
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

درحال‌حاضر که این پست را می‌نویسم، scroll-snap-stop فقط در مرورگرهای مبتنی بر Chromium پشتیبانی می‌شود. برای اطلاع از به‌روزرسانی‌ها، سازگاری مرورگر را بررسی کنید. اما این موضوع مانع نیست. این فقط به این معنی است که در مرورگرهای پشتیبانی‌نشده کاربران ممکن است به‌طور تصادفی از یک دوست رد شوند. بنابراین کاربران باید بیشتر مراقب باشند، یا باید جاوا اسکریپت بنویسیم تا مطمئن شویم دوست ردشده به‌عنوان دیده‌شده علامت‌گذاری نشود.

اگر علاقه‌مندید، در مشخصات بیشتر بخوانید.

overscroll-behavior

آیا تا‌به‌حال درحال پیمایش در یک مدال بوده‌اید که ناگهان شروع به پیمایش محتوای پشت مدال کنید؟ overscroll-behavior به توسعه‌دهنده اجازه می‌دهد آن پیمایش را به‌دام بیندازد و هرگز اجازه ندهد از آن خارج شود. برای انواع مناسبت‌ها خوب است. جزء «داستان‌های من» از آن استفاده می‌کند تا از خروج اشاره‌های تند کشیدن و پیمایش اضافی از جزء جلوگیری کند.

.stories {
  overflow-x: auto;
  overscroll-behavior: contain;
}

Safari و Opera دو مرورگری بودند که از این ویژگی پشتیبانی نمی‌کردند و این کاملاً طبیعی است. این کاربران تجربه پیمایش بیش‌ازحدی را که به آن عادت دارند دریافت خواهند کرد و ممکن است هرگز متوجه این بهبود نشوند. من شخصاً طرفدار پروپاقرص آن هستم و دوست دارم آن را به‌عنوان بخشی از تقریباً هر ویژگی فراتوماری که پیاده‌سازی می‌کنم اضافه کنم. این یک افزونه بی‌ضرر است که فقط می‌تواند به بهبود تجربه کاربری منجر شود.

scrollIntoView({behavior: 'smooth'})

وقتی کاربر تک‌ضرب می‌زند یا کلیک می‌کند و به پایان مجموعه داستان‌های دوستش می‌رسد، وقت آن است که به دوست بعدی در مجموعه نقطه میزان کردن پیمایش برود. با جاوا اسکریپت، توانستیم به دوست بعدی ارجاع دهیم و درخواست کنیم که به‌سمت نمای پیمایش برود. پشتیبانی از اصول اولیه این عالی است؛ هر مرورگری آن را به نمای پیمایش‌شده منتقل کرد. اما همه مرورگرها این کار را 'smooth' انجام ندادند. این فقط به این معنی است که به‌جای اینکه به نمای موردنظر بچسبد، به آن پیمایش می‌شود.

element.scrollIntoView({
  behavior: 'smooth'
})

Safari تنها مرورگری بود که اینجا از behavior: 'smooth' پشتیبانی نمی‌کرد. برای اطلاع از به‌روزرسانی‌ها، سازگاری مرورگر را بررسی کنید.

عملی

حالا که می‌دانید من چطور این کار را کردم، شما چطور این کار را می‌کنید؟! بیایید رویکردهایمان را متنوع کنیم و همه روش‌های ساختن در وب را یاد بگیریم. یک نسخه نمایشی بسازید، نسخه خود را برای من توییت کنید، و من آن را به بخش بازآمیزی‌های انجمن در زیر اضافه خواهم کرد.

ریمیکس‌های انجمن