Bu codelab'de, web'de Instagram Hikayeleri gibi bir deneyimin nasıl oluşturulacağı açıklanmaktadır. HTML, CSS ve JavaScript'den başlayarak bileşeni 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 remiks oluştur'u tıklayın.
app/index.html
adlı kişiyi aç.
HTML
Her zaman semantik HTML kullanmayı hedefliyorum.
Her arkadaş istediği sayıda hikaye paylaşabileceğinden, her arkadaş için bir <section>
öğesi ve her hikaye için bir <article>
öğesi kullanmanın anlamlı olacağını düşündüm.
Baştan başlayalım. Öncelikle, hikayeler bileşenimiz için bir kapsayıcıya ihtiyacımız var.
<body>
öğenize bir <div>
öğesi ekleyin:
<div class="stories">
</div>
Arkadaşları temsil etmek için bazı <section>
öğeleri 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>
- Hikayeler için prototip oluşturmaya yardımcı olmak amacıyla 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 bulabilirsiniz.
CSS
İçeriklerimiz stil için hazır. Bu temel bilgileri, kullanıcıların etkileşimde bulunmak isteyeceği bir şeye dönüştürelim. Bugün mobil öncelikli çalışmaya başlayacağız.
.stories
<div class="stories">
kapsayıcımız için yatay kaydırmalı bir kapsayıcı istiyoruz.
Bunu aşağıdaki yöntemlerle yapabiliriz:
- Kapsayıcıyı ızgara yapma
- Her çocuğu satır kanalını dolduracak şekilde ayarlama
- Her alt öğenin genişliğini bir mobil cihazın görüntü alanının genişliğine ayarlama
HTML öğelerinizin tümü işaretlemenize yerleştirilene kadar ızgara, öncekinin sağ tarafına 100vw
genişliğinde yeni sütunlar yerleştirmeye devam eder.
app/css/index.css
dosyasının alt kısmına aşağıdaki CSS'yi ekleyin:
.stories {
display: grid;
grid: 1fr / auto-flow 100%;
gap: 1ch;
}
Görüntü alanının dışına çıkan bir içeriğimiz olduğuna göre, bu kapsayıcıya içeriği nasıl işleyeceğini söylemenin zamanı geldi. Vurgulanan kod 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 istiyoruz. Bu nedenle overflow-x
değerini auto
olarak ayarlıyoruz. Kullanıcı kaydırdığında bileşenin bir sonraki hikayeye yumuşak bir şekilde yerleşmesini istediğimizden scroll-snap-type: x mandatory
değerini kullanırız. Bu CSS hakkında daha fazla bilgiyi blog yayınımın CSS Kaydırma Sabitleme Noktaları ve overscroll-behavior bölümlerinde bulabilirsiniz.
Kaydırma yapmayı kabul etmeleri için hem üst kapsayıcının hem de alt öğelerin kabul edilmesi gerekir, o yüzden bunu şimdi ele alalım. app/css/index.css
dosyasının altına aşağıdaki kodu ekleyin:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
}
Uygulamanız henüz çalışmıyor ancak scroll-snap-type
etkinleştirildiğinde ve devre dışı bırakıldığında ne olacağı aşağıdaki videoda gösterilmektedir. Bu özellik etkinleştirildiğinde, her yatay kaydırma işlemi bir sonraki habere gider. Devre dışı bırakıldığında tarayıcı, varsayılan kaydırma davranışını kullanır.
Bu işlem, arkadaşlarınızın arasında gezinmenizi sağlar ancak hikayelerle ilgili çözmemiz gereken bir sorun var.
.user
.user
bölümünde, bu alt hikaye öğelerini yerine yerleştirecek bir düzen oluşturalım. Bu sorunu çözmek için kullanışlı bir yığın oluşturma hilesi kullanacağız.
Esasen, satır ve sütunun aynı [story]
ızgaraya sahip olduğu 1x1'lik bir ızgara oluşturuyoruz. Her bir hikaye tablosu öğesi, söz konusu alanı talep etmeye çalışacak ve bu da bir yığın ortaya çıkacak.
Vurgulanan kodu .user
kural kümenize ekleyin:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
display: grid;
grid: [story] 1fr / [story] 1fr;
}
app/css/index.css
dosyasının en altına aşağıdaki kural kümesini ekleyin:
.story {
grid-area: story;
}
Şimdi, mutlak konumlandırma, yüzer öğeler veya bir öğeyi akıştan çıkaran diğer düzen yönergeleri olmadan akışta olmaya devam ediyoruz. Üstelik, neredeyse hiç kod yok. Şuna bakın! Bu konu, videoda ve blog yayınında daha ayrıntılı olarak ele alınmıştır.
.story
Artık hikaye öğesinin stilini belirlememiz gerekiyor.
Daha önce, her <article>
öğesindeki style
özelliğinin yer tutucu yükleme tekniğinin bir parçası olduğundan bahsetmiş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ı resmimizin en üstte olması ve
yükleme tamamlandığında otomatik olarak görünmesi için bunları sıralayabiliriz. Bunu etkinleştirmek için resim URL'mizi özel bir mülke (--bg
) yerleştirip yükleme yer tutucusuyla katman oluşturmak için CSS'mizde kullanırız.
Öncelikle, .story
kural kümesini, yükleme işlemi tamamlandıktan sonra degradeyi arka plan resmiyle değiştirecek şekilde güncelleyelim. Vurgulanan kodu .story
kural grubunuza 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
değerini cover
olarak ayarlamak, görüntümüzün tüm alanı dolduracağı için görüntüleme alanında boş alan kalmamasını sağlar. 2 arka plan resmi tanımlamak, yükleniyor mezar taşı adlı güzel 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. - 2. arka plan resmi (
linear-gradient(to top, lch(98 0 0), lch(90 0 0))
, URL yüklenirken gösterilecek bir degradedir.
Resim indirildikten sonra CSS, degradeyi otomatik olarak resimle değiştirir.
Ardından, bazı davranışları kaldırmak için CSS ekleyeceğiz. Böylece tarayıcı daha hızlı hareket edebilecek.
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 engellertouch-action: manipulation
, tarayıcıya bu etkileşimlerin dokunma etkinlikleri olarak değerlendirilmesi gerektiğini bildirir. Bu sayede tarayıcı, bir URL'yi tıklayıp tıklamadığınıza karar vermek zorunda kalmaz.
Son olarak, hikayeler arasındaki geçişi canlandırmak için küçük bir 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ış gerektiren bir hikayeye eklenecek.
Özel yumuşatma işlevini (cubic-bezier(0.4, 0.0, 1,1)
) Materyal Tasarım'ın Yumuşak Geçiş kılavuzundan aldım (kaydırarak Hızlandırılmış yumuşatma bölümüne gidin).
Dikkatli bir gözünüz varsa muhtemelen pointer-events: none
beyanını fark etmiş ve şu anda kafanızı kaşıyorsunuzdur. Bence çözümün şu ana kadar
tek dezavantajı bu. .seen.story
öğesi üstte olacağı ve görünmez olsa bile dokunma alacağı için buna ihtiyacımız vardır. pointer-events
değerini none
olarak ayarlayarak cam hikayesini pencereye dönüştürür ve kullanıcı etkileşimlerini artık çalmazız. Bu durum çok da kötü değil. Şu anda CSS'mizde bunu yönetmek çok zor değil. z-index
ile ilgilenmiyoruz. Bu konuda hâlâ iyi hissediyorum.
JavaScript
Hikayeler bileşeninin etkileşimleri kullanıcı için oldukça basittir: İlerlemek için sağ tarafa, geri dönmek için sol tarafa dokunun. Kullanıcılar için basit olan şeyler, geliştiriciler için zor olabilir. Ancak çoğunu biz halledeceğiz.
Kurulum
Başlangıç olarak mümkün olduğunca fazla bilgi 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)
İlk JavaScript satırımız, birincil HTML öğe kökümüze referans alır ve bu referansı depolar. Sonraki satırda, öğemizin ortasının nerede olduğu hesaplanır. Böylece, bir dokunuşun ileri mi yoksa geri mi gideceğine 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 ilgileniriz. HTML işaretlememizde, 1. arkadaşı ve en son hikayesini alarak bu bilgilere erişebiliriz. Vurgulanan kodu app/js/index.js
'ünüze 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ıksal yapıya sahibiz.
fare
Hikayeler kapsayıcımızdaki 'click'
etkinliğini dinleyerek başlayalım.
Vurgulanan kodu app/js/index.js
kampanyası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şirse ve bu tıklama <article>
öğesinde değilse işlemden vazgeçer ve hiçbir şey yapmayız.
Makale ise clientX
ile farenin veya parmağın yatay konumunu alırız. navigateStories
henüz uygulanmadı ancak aldığı bağımsız değişken, hangi yönde ilerlememiz gerektiğini belirtir. Söz konusu kullanıcı konumu ortanca değerin üzerindeyse next
'e, aksi takdirde prev
'e (önceki) gitmemiz gerektiğini biliriz.
Klavye
Şimdi klavye tuşlarına basma işlemlerini dinleyelim. Aşağı ok tuşuna basılırsa next
'ye gideriz. Yukarı Ok'u gösteriyorsa prev
'ye gideceğiz.
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')
})
Hikayeler'de gezinme
Artık hikayelerin benzersiz iş mantığını ve bu içeriklerin sağladığı kullanıcı deneyimini ele almanın zamanı geldi. Bu kod blok halinde ve karmaşık görünüyor ancak satır satır incelerseniz oldukça anlaşılır olduğunu göreceksiniz.
Öncelikle, bir arkadaşa gidip gitmeyeceğimiz veya bir hikayeyi gösterip göstermeyeceğimiz konusunda karar vermemize yardımcı olan bazı seçicileri saklıyoruz. HTML'de çalıştığımız için HTML'yi arkadaş (kullanıcı) veya hikaye (hikaye) varlığı için sorgulayacağız.
Bu değişkenler, "x hikayesi için "sonraki", aynı arkadaştan başka bir hikayeye mi yoksa başka bir arkadaşa mı geçmek anlamına geliyor?" Kendi geliştirdiğimiz ağaç yapısını kullanarak ebeveynlere ve çocuklarına ulaşarak yaptım.
app/js/index.js
dosyasının altına aşağıdaki kodu 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ığı hedefimiz, mümkün olduğunca doğal dile yakın şekilde şöyledir:
- Dokunmanın nasıl gerçekleştirileceğine karar verme
- Sonraki/önceki bir hikaye varsa: İlgili hikayeyi göster
- Bu, arkadaşınızın son/ilk hikayesiyse: Yeni bir arkadaşınıza gösterin
- Bu yönde gidecek bir hikaye yoksa: hiçbir şey yapmayın
- Yeni mevcut hikayeyi
state
klasörüne sakla
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 düğmesine basın.
Sonuç
Bu e-posta, bileşenle ilgili ihtiyaçlarımı özetlemiş oldu. Bunun üzerine inşa etmekten, verilerden yola çıkarak artırmaktan ve genel olarak kendinize özel hale getirmekten çekinmeyin.