این کد لبه به شما می آموزد که چگونه تجربه ای مانند استوری اینستاگرام در وب بسازید. ما کامپوننت را همانطور که پیش میرویم، با HTML، سپس CSS و سپس جاوا اسکریپت شروع میکنیم.
برای آشنایی با پیشرفتهای پیشروندهای که در ساخت این مؤلفه ایجاد شده است، پست وبلاگ من «ساخت یک داستان» را بررسی کنید.
راه اندازی
- روی Remix to Edit کلیک کنید تا پروژه قابل ویرایش باشد.
-
app/index.html
باز کنید.
HTML
هدف من همیشه استفاده از HTML معنایی است. از آنجایی که هر دوست میتواند هر تعداد داستان داشته باشد، من فکر کردم استفاده از عنصر <section>
برای هر دوست و عنصر <article>
برای هر داستان معنیدار است. بیایید از ابتدا شروع کنیم. اول، ما به یک ظرف برای جزء داستان های خود نیاز داریم.
یک عنصر <div>
را به <body>
خود اضافه کنید:
<div class="stories">
</div>
برخی از عناصر <section>
را برای نشان دادن دوستان اضافه کنید:
<div class="stories">
<section class="user"></section>
<section class="user"></section>
<section class="user"></section>
<section class="user"></section>
</div>
برخی از عناصر <article>
را برای نمایش داستان ها اضافه کنید:
<div class="stories">
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>
<article class="story" style="--bg: url(https://picsum.photos/480/841);"></article>
</section>
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/481/840);"></article>
</section>
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/481/841);"></article>
</section>
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/482/840);"></article>
<article class="story" style="--bg: url(https://picsum.photos/482/843);"></article>
<article class="story" style="--bg: url(https://picsum.photos/482/844);"></article>
</section>
</div>
- ما از یک سرویس تصویر (
picsum.com
) برای کمک به ساختن داستانهای اولیه استفاده میکنیم. - ویژگی
style
در هر<article>
بخشی از تکنیک بارگیری مکان نگهدار است که در بخش بعدی بیشتر با آن آشنا خواهید شد.
CSS
محتوای ما برای سبک آماده است. بیایید آن استخوان ها را به چیزی تبدیل کنیم که مردم می خواهند با آن تعامل داشته باشند. ما امروز با موبایل اول کار خواهیم کرد.
.stories
برای کانتینر <div class="stories">
ما یک ظرف اسکرول افقی می خواهیم. ما می توانیم به این هدف دست پیدا کنیم:
- ساخت ظرف یک شبکه
- تنظیم هر کودک برای پر کردن مسیر ردیف
- پهنای هر کودک را به اندازه عرض یک درگاه دید دستگاه تلفن همراه قرار دهید
Grid به قرار دادن ستونهای جدید 100vw
در سمت راست ستون قبلی ادامه میدهد تا زمانی که تمام عناصر HTML را در نشانهگذاری شما قرار دهد.
CSS زیر را به پایین app/css/index.css
اضافه کنید:
.stories {
display: grid;
grid: 1fr / auto-flow 100%;
gap: 1ch;
}
اکنون که محتوایی فراتر از viewport را داریم، زمان آن رسیده است که به آن ظرف بگوییم چگونه آن را مدیریت کند. خطوط کد برجسته شده را به مجموعه قوانین .stories
خود اضافه کنید:
.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;
}
ما پیمایش افقی می خواهیم، بنابراین overflow-x
را روی auto
تنظیم می کنیم. وقتی کاربر پیمایش میکند، میخواهیم کامپوننت به آرامی روی داستان بعدی قرار گیرد، بنابراین scroll-snap-type: x mandatory
استفاده میکنیم. اطلاعات بیشتر در مورد این CSS را در CSS Scroll Snap Points و بخش overscroll-behavior پست وبلاگ من بخوانید.
هم ظرف والد و هم فرزندان لازم است که با اسکرول کردن موافقت کنند، پس بیایید اکنون به آن رسیدگی کنیم. کد زیر را به پایین app/css/index.css
اضافه کنید:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
}
برنامه شما هنوز کار نمیکند، اما ویدیوی زیر نشان میدهد که وقتی scroll-snap-type
فعال و غیرفعال میشود چه اتفاقی میافتد. وقتی فعال باشد، هر پیمایش افقی به داستان بعدی میخورد. وقتی غیرفعال است، مرورگر از رفتار پیمایش پیشفرض خود استفاده میکند.
این باعث میشود که دوستانتان را مرور کنید، اما ما هنوز مشکلی با داستانها داریم که باید حل کنیم.
.user
بیایید یک طرحبندی در بخش .user
ایجاد کنیم که آن عناصر داستان کودک را در جای خود قرار میدهد. برای حل این مشکل از یک ترفند انباشته استفاده می کنیم. ما اساساً یک شبکه 1x1 ایجاد می کنیم که در آن سطر و ستون دارای نام مستعار Grid یکسانی از [story]
هستند، و هر آیتم شبکه داستانی سعی می کند آن فضا را ادعا کند و در نتیجه یک پشته ایجاد می شود.
کد برجسته شده را به مجموعه قوانین .user
خود اضافه کنید:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
display: grid;
grid: [story] 1fr / [story] 1fr;
}
مجموعه قوانین زیر را به پایین app/css/index.css
اضافه کنید:
.story {
grid-area: story;
}
اکنون، بدون موقعیتیابی مطلق، شناورها یا سایر دستورالعملهای چیدمان که یک عنصر را از جریان خارج میکند، ما هنوز در جریان هستیم. به علاوه، تقریباً شبیه هر کدی است، به آن نگاه کنید! این در ویدیو و پست وبلاگ با جزئیات بیشتر تجزیه می شود.
.story
اکنون فقط باید به خود آیتم داستان استایل دهیم.
قبلاً اشاره کردیم که ویژگی style
در هر عنصر <article>
بخشی از تکنیک بارگذاری مکاننما است:
<article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>
ما از ویژگی background-image
CSS استفاده میکنیم که به ما امکان میدهد بیش از یک تصویر پسزمینه را مشخص کنیم. ما میتوانیم آنها را به ترتیبی قرار دهیم تا تصویر کاربر ما در بالا باشد و پس از بارگیری به طور خودکار نشان داده شود. برای فعال کردن این کار، URL تصویر خود را در یک ویژگی سفارشی ( --bg
) قرار می دهیم و از آن در CSS خود برای لایه بندی با محل بارگذاری استفاده می کنیم.
ابتدا، اجازه دهید مجموعه قوانین .story
را بهروزرسانی کنیم تا پس از بارگیری یک گرادینت با یک تصویر پسزمینه جایگزین شود. کد برجسته شده را به مجموعه قوانین .story
خود اضافه کنید:
.story {
grid-area: story;
background-size: cover;
background-image:
var(--bg),
linear-gradient(to top, lch(98 0 0), lch(90 0 0));
}
تنظیم background-size
برای cover
تضمین میکند که فضای خالی در نما وجود ندارد زیرا تصویر ما آن را پر میکند. تعریف 2 تصویر پس زمینه ما را قادر می سازد تا یک ترفند وب CSS منظم به نام سنگ قبر بارگذاری کنیم :
- تصویر پسزمینه 1 (
var(--bg)
) آدرس اینترنتی است که ما در HTML به صورت خطی ارسال کردیم - تصویر پسزمینه 2 (
linear-gradient(to top, lch(98 0 0), lch(90 0 0))
یک گرادیان است که هنگام بارگیری URL نشان داده میشود
پس از دانلود کردن تصویر، CSS بهطور خودکار شیب را با تصویر جایگزین میکند.
در مرحله بعد مقداری CSS اضافه می کنیم تا برخی رفتارها را حذف کنیم و مرورگر را آزاد کنیم تا سریعتر حرکت کند. کد برجسته شده را به مجموعه قوانین .story
خود اضافه کنید:
.story {
grid-area: story;
background-size: cover;
background-image:
var(--bg),
linear-gradient(to top, lch(98 0 0), lch(90 0 0));
user-select: none;
touch-action: manipulation;
}
-
user-select: none
مانع از انتخاب تصادفی متن توسط کاربران نمی شود -
touch-action: manipulation
به مرورگر دستور می دهد که این تعاملات باید به عنوان رویدادهای لمسی در نظر گرفته شوند، که مرورگر را از تلاش برای تصمیم گیری در مورد اینکه آیا روی یک URL کلیک می کنید یا نه آزاد می کند.
در آخر، بیایید کمی CSS اضافه کنیم تا انتقال بین داستانها را متحرک کنیم. کد برجسته شده را به مجموعه قوانین .story
خود اضافه کنید:
.story {
grid-area: story;
background-size: cover;
background-image:
var(--bg),
linear-gradient(to top, lch(98 0 0), lch(90 0 0));
user-select: none;
touch-action: manipulation;
transition: opacity .3s cubic-bezier(0.4, 0.0, 1, 1);
&.seen {
opacity: 0;
pointer-events: none;
}
}
کلاس .seen
به داستانی اضافه می شود که نیاز به خروج دارد. من تابع آسانسازی سفارشی ( cubic-bezier(0.4, 0.0, 1,1)
) را از راهنمای آسانسازی متریال دیزاین دریافت کردم (به بخش کاهش سرعت تسریع شده بروید).
اگر چشم تیزبین داشته باشید، احتمالاً متوجه pointer-events: none
اعلامیه ای وجود ندارد و در حال حاضر سر خود را می خارید. من می توانم بگویم این تنها نقطه ضعف راه حل تا کنون است. ما به این نیاز داریم زیرا یک عنصر .seen.story
در بالای صفحه قرار میگیرد و ضربهها را دریافت میکند، حتی اگر نامرئی باشد. با تنظیم pointer-events
روی none
، داستان شیشهای را به یک پنجره تبدیل میکنیم و دیگر تعاملات کاربر را نمیدزدیم. خیلی بد نیست، مدیریت اینجا در CSS ما در حال حاضر خیلی سخت نیست. ما با z-index
اشتباه نمی کنیم. هنوز در این مورد احساس خوبی دارم.
جاوا اسکریپت
تعاملات یک جزء Stories برای کاربر بسیار ساده است: برای رفتن به جلو روی سمت راست ضربه بزنید، برای بازگشت به سمت چپ روی آن ضربه بزنید. کارهای ساده برای کاربران معمولا برای توسعه دهندگان کار سختی است. با این حال، ما از بسیاری از آن مراقبت خواهیم کرد.
راه اندازی
برای شروع، بیایید تا آنجا که می توانیم اطلاعات را محاسبه و ذخیره کنیم. کد زیر را به app/js/index.js
اضافه کنید:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
اولین خط ما از جاوا اسکریپت مرجعی به ریشه عنصر اصلی HTML ما می گیرد و ذخیره می کند. خط بعدی محاسبه می کند که وسط عنصر ما کجاست، بنابراین ما می توانیم تصمیم بگیریم که یک ضربه به جلو یا عقب برود.
ایالت
سپس یک شی کوچک با حالتی مرتبط با منطق ما می سازیم. در این مورد، ما فقط به داستان فعلی علاقه مند هستیم. در نشانهگذاری HTML ما، میتوانیم با گرفتن اولین دوست و آخرین داستان آنها به آن دسترسی پیدا کنیم. کد برجسته شده را به app/js/index.js
خود اضافه کنید:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
const state = {
current_story: stories.firstElementChild.lastElementChild
}
شنوندگان
ما اکنون منطق کافی داریم تا شروع به گوش دادن به رویدادهای کاربر و هدایت آنها کنیم.
موش
بیایید با گوش دادن به رویداد 'click'
در ظرف داستانهایمان شروع کنیم. کد برجسته شده را به app/js/index.js
اضافه کنید:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
const state = {
current_story: stories.firstElementChild.lastElementChild
}
stories.addEventListener('click', e => {
if (e.target.nodeName !== 'ARTICLE')
return
navigateStories(
e.clientX > median
? 'next'
: 'prev')
})
اگر کلیکی اتفاق بیفتد و روی عنصر <article>
نباشد، ما ضمانت میکنیم و کاری انجام نمیدهیم. اگر مقاله باشد، موقعیت افقی ماوس یا انگشت را با clientX
می گیریم. ما هنوز navigateStories
پیادهسازی نکردهایم، اما استدلالی که طول میکشد، مشخص میکند که باید به چه سمتی برویم. اگر موقعیت کاربر بزرگتر از میانه باشد، می دانیم که باید به next
برویم، در غیر این صورت prev
(قبلی).
صفحه کلید
حالا بیایید به فشارهای صفحه کلید گوش دهیم. اگر فلش رو به پایین فشار داده شود به next
می رویم. اگر فلش رو به بالا باشد، به prev
می رویم.
کد برجسته شده را به app/js/index.js
اضافه کنید:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
const state = {
current_story: stories.firstElementChild.lastElementChild
}
stories.addEventListener('click', e => {
if (e.target.nodeName !== 'ARTICLE')
return
navigateStories(
e.clientX > median
? 'next'
: 'prev')
})
document.addEventListener('keydown', ({key}) => {
if (key !== 'ArrowDown' || key !== 'ArrowUp')
navigateStories(
key === 'ArrowDown'
? 'next'
: 'prev')
})
پیمایش داستان ها
وقت آن است که با منطق تجاری منحصر به فرد داستان ها و تجربه کاربری که به خاطر آن مشهور شده اند مقابله کنیم. این به نظر درشت و مشکل به نظر می رسد، اما فکر می کنم اگر خط به خط آن را بررسی کنید، متوجه خواهید شد که کاملا قابل هضم است.
در ابتدا، برخی از انتخابگرها را ذخیره میکنیم که به ما کمک میکنند تصمیم بگیریم به یک دوست برویم یا داستانی را نشان دهیم/پنهان کنیم. از آنجایی که HTML جایی است که ما کار می کنیم، برای حضور دوستان (کاربران) یا داستان ها (داستان) از آن پرس و جو می کنیم.
این متغیرها به ما کمک میکنند تا به سؤالاتی پاسخ دهیم مانند: "با توجه به داستان x، آیا "بعدی" به معنای انتقال به داستان دیگری از همان دوست است یا به یک دوست دیگر؟" من این کار را با استفاده از ساختار درختی که ساختیم انجام دادم و به والدین و فرزندانشان رسیدم.
کد زیر را به پایین app/js/index.js
اضافه کنید:
const navigateStories = direction => {
const story = state.current_story
const lastItemInUserStory = story.parentNode.firstElementChild
const firstItemInUserStory = story.parentNode.lastElementChild
const hasNextUserStory = story.parentElement.nextElementSibling
const hasPrevUserStory = story.parentElement.previousElementSibling
}
در اینجا هدف منطق کسب و کار ما، تا حد امکان نزدیک به زبان طبیعی است:
- تصمیم بگیرید که چگونه با شیر برخورد کنید
- اگر داستان بعدی/قبلی وجود دارد: آن داستان را نشان دهید
- اگر آخرین/اولین داستان دوست است: یک دوست جدید را نشان دهید
- اگر داستانی برای رفتن به آن سمت وجود ندارد: هیچ کاری نکنید
- داستان فعلی جدید را در
state
ذخیره کنید
کد برجسته شده را به تابع navigateStories
خود اضافه کنید:
const navigateStories = direction => {
const story = state.current_story
const lastItemInUserStory = story.parentNode.firstElementChild
const firstItemInUserStory = story.parentNode.lastElementChild
const hasNextUserStory = story.parentElement.nextElementSibling
const hasPrevUserStory = story.parentElement.previousElementSibling
if (direction === 'next') {
if (lastItemInUserStory === story && !hasNextUserStory)
return
else if (lastItemInUserStory === story && hasNextUserStory) {
state.current_story = story.parentElement.nextElementSibling.lastElementChild
story.parentElement.nextElementSibling.scrollIntoView({
behavior: 'smooth'
})
}
else {
story.classList.add('seen')
state.current_story = story.previousElementSibling
}
}
else if(direction === 'prev') {
if (firstItemInUserStory === story && !hasPrevUserStory)
return
else if (firstItemInUserStory === story && hasPrevUserStory) {
state.current_story = story.parentElement.previousElementSibling.firstElementChild
story.parentElement.previousElementSibling.scrollIntoView({
behavior: 'smooth'
})
}
else {
story.nextElementSibling.classList.remove('seen')
state.current_story = story.nextElementSibling
}
}
}
آن را امتحان کنید
- برای پیش نمایش سایت، View App را فشار دهید. سپس تمام صفحه را فشار دهید .
نتیجه گیری
این یک جمع بندی برای نیازهایی است که من با کامپوننت داشتم. با خیال راحت بر روی آن بسازید، آن را با داده هدایت کنید، و به طور کلی آن را مال خود کنید!