Bu codelab'de, web'de Instagram Hikayeleri gibi bir deneyim oluşturmayı öğreneceksiniz. Bileşeni, HTML ile başlayıp CSS ve JavaScript ile devam ederek adım adım oluşturacağız.
Bu bileşeni oluştururken yapılan aşamalı iyileştirmeler hakkında bilgi edinmek için Hikayeler bileşeni oluşturma başlıklı blog yayınımı inceleyin.
Kurulum
- Projeyi düzenlenebilir hale getirmek için Düzenlemek için Remix'i tıklayın.
app/index.html
adlı kişiyi aç.
HTML
Her zaman semantik HTML kullanmayı hedeflerim.
Her arkadaşın istediği sayıda hikayesi olabileceğinden her arkadaş için bir <section>
öğesi, her hikaye için de bir <article>
öğesi kullanmanın anlamlı olacağını düşündüm.
Ancak baştan başlayalım. Öncelikle, hikayeler bileşenimiz için bir kapsayıcıya ihtiyacımız var.
<body>
öğenize <div>
öğesi ekleyin:
<div class="stories">
</div>
Arkadaşları temsil etmek için <section>
öğeler ekleyin:
<div class="stories">
<section class="user"></section>
<section class="user"></section>
<section class="user"></section>
<section class="user"></section>
</div>
Hikayeleri temsil etmek için bazı <article>
öğeleri ekleyin:
<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>
- Hikayelerin prototipini oluşturmaya yardımcı olması için bir resim hizmeti (
picsum.com
) kullanıyoruz. - Her
<article>
öğesindekistyle
özelliği, yer tutucu yükleme tekniğinin bir parçasıdır. Bu teknik hakkında daha fazla bilgiyi sonraki bölümde edinebilirsiniz.
CSS
İçeriğimiz stil için hazır. Bu iskeletleri, kullanıcıların etkileşimde bulunmak isteyeceği bir şeye dönüştürelim. Bugün mobil öncelikli olarak çalışacağız.
.stories
<div class="stories">
kapsayıcımız için yatay kaydırma kapsayıcısı istiyoruz.
Bunu başarmak için:
- Kapsayıcıyı ızgara yapma
- Her alt öğeyi satır parçasını dolduracak şekilde ayarlama
- Her alt öğenin genişliğini mobil cihazın görünüm alanı genişliği yapma
Izgara, işaretlemenizdeki tüm HTML öğelerini yerleştirene kadar önceki sütunun sağ tarafına yeni 100vw
genişliğinde sütunlar yerleştirmeye devam eder.

Aşağıdaki CSS'yi app/css/index.css
dosyasının en altına ekleyin:
.stories {
display: grid;
grid: 1fr / auto-flow 100%;
gap: 1ch;
}
Artık görüntü alanının dışına taşan içeriğimiz olduğuna göre, kapsayıcıya bu içeriği nasıl işleyeceğini söyleme zamanı geldi. Kodun vurgulanan satırlarını .stories
kural kümenize ekleyin:
.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;
}
Yatay kaydırma istediğimiz için overflow-x
değerini auto
olarak ayarlayacağız. Kullanıcı kaydırdığında bileşenin bir sonraki hikayeye sorunsuz bir şekilde geçmesini istiyoruz. Bu nedenle scroll-snap-type: x mandatory
kullanacağız. Bu CSS hakkında daha fazla bilgiyi blog yayınımın CSS Scroll Snap Points ve overscroll-behavior bölümlerinde bulabilirsiniz.
Kaydırma yapışma özelliğinin kullanılabilmesi için hem üst kapsayıcının hem de alt öğelerin kabul etmesi gerekir. Bu nedenle, bu durumu şimdi ele alalım. Aşağıdaki kodu app/css/index.css
dosyasının en altına ekleyin:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
}
Uygulamanız henüz çalışmıyor ancak aşağıdaki videoda scroll-snap-type
etkinleştirildiğinde ve devre dışı bırakıldığında ne olduğu gösteriliyor. Etkinleştirildiğinde, her yatay kaydırma bir sonraki hikayeye geçer. Devre dışı bırakıldığında tarayıcı, varsayılan kaydırma davranışını kullanır.
Bu işlemle arkadaşlarınız arasında gezinebilirsiniz ancak çözmemiz gereken bir hikayeler sorunu var.
.user
.user
bölümünde, alt öğe hikaye öğelerini yerlerine yerleştiren bir düzen oluşturalım. Bu sorunu çözmek için kullanışlı bir üst üste yerleştirme hilesi kullanacağız.
Temel olarak, satır ve sütunun aynı [story]
Grid takma adını kullandığı 1x1'lik bir ızgara oluşturuyoruz. Her hikaye ızgarası öğesi bu alanı talep etmeye çalışarak bir yığın oluşturuyor.
Vurgulanan kodu .user
kural kümenize ekleyin:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
display: grid;
grid: [story] 1fr / [story] 1fr;
}
Aşağıdaki kural grubunu app/css/index.css
dosyasının en altına ekleyin:
.story {
grid-area: story;
}
Artık mutlak konumlandırma, kaydırma veya bir öğeyi akıştan çıkaran diğer düzen yönergeleri olmadan akıştayız. Ayrıca, neredeyse hiç kod yok. Bakın! Bu konu, videoda ve blog yayınında daha ayrıntılı olarak ele alınmaktadır.
.story
Şimdi de hikaye öğesini stilize etmemiz gerekiyor.
Daha önce, her <article>
öğesindeki style
özelliğinin yer tutucu yükleme tekniğinin bir parçası olduğunu belirtmiştik:
<article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>
Birden fazla arka plan resmi belirtmemize olanak tanıyan CSS'nin background-image
özelliğini kullanacağız. Kullanıcı resmimiz en üstte olacak ve yükleme tamamlandığında otomatik olarak görünecek şekilde sıralayabiliriz. Bunu etkinleştirmek için resim URL'mizi özel bir özelliğe (--bg
) yerleştirip yükleme yer tutucusuyla katman oluşturmak üzere CSS'mizde kullanacağız.
Öncelikle, yükleme tamamlandıktan sonra gradyanı arka plan resmiyle değiştirmek için .story
kural kümesini güncelleyelim. Vurgulanan kodu .story
kural kümenize ekleyin:
.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
ayarını cover
olarak belirlemek, resmimiz bu alanı dolduracağı için görünüm penceresinde boş alan olmamasını sağlar. 2 arka plan resmi tanımlamak, yükleme mezar taşı adı verilen şık bir CSS web hilesi yapmamızı sağlar:
- Arka plan resmi 1 (
var(--bg)
), HTML'de satır içi olarak ilettiğimiz URL'dir. - Arka plan resmi 2 (
linear-gradient(to top, lch(98 0 0), lch(90 0 0))
, URL yüklenirken gösterilecek bir gradyandır)
Resim indirildikten sonra CSS, gradyanı otomatik olarak resimle değiştirir.
Ardından, bazı davranışları kaldırmak için CSS ekleyerek tarayıcının daha hızlı hareket etmesini sağlayacağız.
Vurgulanan kodu .story
kural kümenize ekleyin:
.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
, kullanıcıların yanlışlıkla metin seçmesini önler.touch-action: manipulation
, tarayıcıya bu etkileşimlerin dokunma etkinlikleri olarak değerlendirilmesi gerektiğini bildirir. Böylece tarayıcı, bir URL'yi tıklayıp tıklamadığınıza karar vermeye çalışmaktan kurtulur.
Son olarak, hikayeler arasındaki geçişi canlandırmak için biraz CSS ekleyelim. Vurgulanan kodu .story
kural kümenize ekleyin:
.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
sınıfı, çıkış yapılması gereken bir hikayeye eklenir.
Özel yumuşatma işlevini (cubic-bezier(0.4, 0.0, 1,1)
), Materyal Tasarım'ın Yumuşatma kılavuzundan (Hızlandırılmış yumuşatma bölümüne gidin) aldım.
Dikkatliyseniz pointer-events: none
beyanı fark etmiş ve şu anda kafanız karışmış olabilir. Şu ana kadar çözümün tek dezavantajı bu gibi görünüyor. Görünmez olsa bile .seen.story
öğesi üstte olacağı ve dokunmaları alacağı için bu bilgiye ihtiyacımız var. pointer-events
öğesini none
olarak ayarlayarak cam hikayeyi pencereye dönüştürüyor ve kullanıcı etkileşimlerini çalmıyoruz. Bu, çok kötü bir değişim değil ve CSS'mizde şu anda yönetilmesi çok zor bir durum değil. z-index
ile uğraşmıyoruz. Bu konuda hâlâ iyi hissediyorum.
JavaScript
Hikayeler bileşeninin etkileşimleri kullanıcı için oldukça basittir: İleri gitmek için sağa, geri gitmek için sola dokunun. Kullanıcılar için basit olan şeyler, geliştiriciler için zorlu bir iş olabilir. Ancak bu işlemlerin büyük bir kısmını biz hallederiz.
Kurulum
Başlamak için mümkün olduğunca fazla bilgiyi hesaplayıp depolayalım.
Aşağıdaki kodu app/js/index.js
dosyasına ekleyin:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
JavaScript'imizin ilk satırı, birincil HTML öğesi kökümüzün referansını alıp depolar. Bir sonraki satır, öğemizin ortasının nerede olduğunu hesaplar. Böylece, dokunmanın ileri mi yoksa geri mi gitmek için yapıldığına karar verebiliriz.
Eyalet
Ardından, mantığımızla alakalı bazı durumları içeren küçük bir nesne oluştururuz. Bu durumda yalnızca mevcut hikayeyle ilgileniyoruz. HTML işaretlememizde, 1. arkadaşı ve en son hikayesini alarak buna erişebiliriz. Vurgulanan kodu app/js/index.js
adresinize ekleyin:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
const state = {
current_story: stories.firstElementChild.lastElementChild
}
Dinleyiciler
Artık kullanıcı etkinliklerini dinlemeye ve yönlendirmeye başlamak için yeterli mantığa sahibiz.
fare
Hikayeler kapsayıcımızdaki 'click'
etkinliğini dinleyerek başlayalım.
Vurgulanan kodu app/js/index.js
dosyasına ekleyin:
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')
})
Bir tıklama gerçekleşir ve bu tıklama <article>
öğesinde olmazsa hiçbir şey yapmayız.
Makale ise fare veya parmağın yatay konumunu clientX
ile alırız. navigateStories
henüz uygulanmamıştır ancak bağımsız değişken, hangi yöne gitmemiz gerektiğini belirtir. Kullanıcı konumu ortalamadan büyükse next
'ye, aksi takdirde prev
'ye (önceki) gitmemiz gerekir.
Klavye
Şimdi klavye tuşlarına basıldığında sesi dinleyelim. Aşağı ok tuşuna basıldığında next
konumuna gidilir. Yukarı Ok ise prev
'ye gideriz.
Vurgulanan kodu app/js/index.js
dosyasına ekleyin:
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')
})
Hikayelerde gezinme
Hikayelerin benzersiz iş mantığı ve ünlü oldukları kullanıcı deneyimiyle ilgili sorunları ele alma zamanı. Bu işlem biraz karışık gibi görünse de satır satır incelediğinizde aslında oldukça kolay olduğunu göreceksiniz.
Başlangıçta, bir arkadaşa kaydırıp kaydırmayacağımıza veya bir hikayeyi gösterip göstermeyeceğimize karar vermemize yardımcı olan bazı seçicileri saklarız. HTML üzerinde çalıştığımız için arkadaşların (kullanıcılar) veya hikayelerin (hikaye) varlığını sorgulayacağız.
Bu değişkenler, "x hikayesi verildiğinde "sonraki" seçeneği, aynı arkadaşın başka bir hikayesine mi yoksa farklı bir arkadaşın hikayesine mi geçileceğini ifade ediyor?" gibi soruları yanıtlamamıza yardımcı olur. Bunu, oluşturduğumuz ağaç yapısını kullanarak, ebeveynlere ve çocuklarına ulaşarak yaptım.
Aşağıdaki kodu app/js/index.js
dosyasının en altına ekleyin:
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
}
İş mantığı hedefimizi mümkün olduğunca doğal dile yakın bir şekilde aşağıda paylaşıyoruz:
- Dokunma işleminin nasıl ele alınacağına karar verin.
- Sonraki/önceki bir hikaye varsa o hikayeyi göster
- Arkadaşın son/ilk hikayesiyse: Yeni bir arkadaş göster
- O yönde gidilecek bir hikaye yoksa: hiçbir şey yapmayın
- Yeni mevcut hikayeyi
state
klasörüne kaydedin.
Vurgulanan kodu navigateStories
işlevinize ekleyin:
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
}
}
}
Deneyin
- Siteyi önizlemek için Uygulamayı Görüntüle'ye basın. Ardından Tam Ekran'a basın.
Sonuç
Bileşenle ilgili ihtiyaçlarımın özeti bu şekilde. Bu şablonu geliştirebilir, verilerle destekleyebilir ve kendi ihtiyaçlarınıza göre uyarlayabilirsiniz.