Codelab ini mengajarkan cara membangun pengalaman seperti Instagram Stories di web. Kita akan membangun komponen sambil berjalan, dimulai dengan HTML, kemudian CSS, kemudian JavaScript.
Lihat postingan blog saya Membuat komponen Stories untuk mempelajari tentang peningkatan progresif yang dilakukan saat membangun komponen ini.
Penyiapan
- Klik Remix to Edit agar project dapat diedit.
- Buka
app/index.html
.
HTML
Saya selalu bertujuan menggunakan HTML semantik.
Karena setiap teman dapat memiliki sejumlah cerita, saya pikir akan sangat bermanfaat untuk menggunakan
Elemen <section>
untuk setiap teman dan elemen <article>
untuk setiap cerita.
Mari kita mulai dari awal. Pertama, kita membutuhkan
kontainer untuk
komponen cerita.
Tambahkan elemen <div>
ke <body>
Anda:
<div class="stories">
</div>
Tambahkan beberapa elemen <section>
untuk mewakili teman:
<div class="stories">
<section class="user"></section>
<section class="user"></section>
<section class="user"></section>
<section class="user"></section>
</div>
Tambahkan beberapa elemen <article>
untuk mewakili cerita:
<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>
- Kami menggunakan layanan gambar (
picsum.com
) untuk membantu membuat prototipe cerita. - Atribut
style
di setiap<article>
adalah bagian dari pemuatan placeholder teknik ini, yang akan Anda pelajari lebih lanjut di bagian selanjutnya.
CSS
Konten kita sudah siap untuk gaya. Mari kita ubah tulang itu menjadi sesuatu yang akan dilihat banyak orang mereka gunakan. Hari ini kita akan menggunakan {i>mobile-first<i}.
.stories
Untuk penampung <div class="stories">
, kita ingin container scroll horizontal.
Kita dapat mencapainya dengan:
- Membuat container menjadi Petak
- Menyetel setiap turunan untuk mengisi baris baris
- Membuat lebar setiap turunan selebar area pandang perangkat seluler
Petak akan terus menempatkan kolom baru selebar 100vw
di sebelah kanan kolom sebelumnya
satu, hingga semua elemen HTML ditempatkan di markup Anda.
Tambahkan CSS berikut ke bagian bawah app/css/index.css
:
.stories {
display: grid;
grid: 1fr / auto-flow 100%;
gap: 1ch;
}
Sekarang setelah kita memiliki konten yang melampaui
area pandang, saatnya untuk memberi tahu
container terkait cara menanganinya. Tambahkan baris kode yang ditandai ke kumpulan aturan .stories
Anda:
.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;
}
Kita ingin scroll horizontal, sehingga kita akan menetapkan overflow-x
ke
auto
. Saat pengguna men-scroll, kita ingin komponen
beristirahat dengan lembut di cerita berikutnya,
jadi kita akan
menggunakan scroll-snap-type: x mandatory
. Baca selengkapnya tentang hal ini
CSS di Snap Points Scroll CSS
dan perilaku-overscroll
bagian postingan blog saya.
Penampung induk dan turunan perlu setuju untuk mengesampingkan scroll, jadi
mari kita tangani itu sekarang. Tambahkan kode berikut ke bagian bawah app/css/index.css
:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
}
Aplikasi Anda belum berfungsi, tetapi video di bawah menunjukkan apa yang terjadi saat
scroll-snap-type
diaktifkan dan dinonaktifkan. Jika diaktifkan, setiap kolom horizontal
scroll akan langsung menuju ke cerita berikutnya. Jika dinonaktifkan, browser akan menggunakan
perilaku scroll default.
Anda akan menelusuri teman-teman Anda, tetapi kami masih mengalami masalah dengan cerita yang harus dipecahkan.
.user
Mari kita buat tata letak di bagian .user
yang menangani cerita turunan tersebut
elemen data pada tempatnya. Kita akan menggunakan trik menumpuk
yang praktis untuk menyelesaikan ini.
Pada dasarnya kita membuat kisi 1x1 di mana baris dan kolom memiliki {i>Grid<i} yang sama
alias dari [story]
, dan setiap item petak story akan mencoba dan mengklaim ruang tersebut,
yang menghasilkan stack.
Tambahkan kode yang ditandai ke kumpulan aturan .user
Anda:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
display: grid;
grid: [story] 1fr / [story] 1fr;
}
Tambahkan kumpulan aturan berikut ke bagian bawah app/css/index.css
:
.story {
grid-area: story;
}
Sekarang, tanpa posisi mutlak, {i>float<i}, atau perintah tata letak lain yang membutuhkan elemen keluar dari {i>flow<i} (alur), kita masih berada di aliran. Selain itu, ini tidak seperti kode apa pun, lihat itu! Informasi ini diuraikan dalam video dan postingan blog secara lebih mendetail.
.story
Sekarang kita hanya perlu memberi gaya pada item cerita itu sendiri.
Sebelumnya, kita menyebutkan bahwa atribut style
pada setiap elemen <article>
adalah bagian dari
teknik pemuatan placeholder:
<article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>
Kita akan menggunakan properti background-image
CSS, yang memungkinkan kita menentukan
lebih dari satu gambar latar. Kita bisa memasukkannya
dalam urutan sehingga pengguna
gambar ada di atas dan akan muncul secara otomatis setelah pemuatan selesai. Kepada
mengaktifkan ini, kita akan menempatkan URL gambar ke dalam properti khusus (--bg
), dan menggunakannya
dalam CSS kita untuk dilapisi dengan placeholder pemuatan.
Pertama, mari kita perbarui kumpulan aturan .story
untuk mengganti gradien dengan gambar latar
setelah pemuatan selesai. Tambahkan kode yang ditandai ke kumpulan aturan .story
Anda:
.story {
grid-area: story;
background-size: cover;
background-image:
var(--bg),
linear-gradient(to top, lch(98 0 0), lch(90 0 0));
}
Menyetel background-size
ke cover
akan memastikan tidak ada ruang kosong di
area pandang karena gambar kita akan memenuhinya. Menentukan 2 gambar latar
memungkinkan kita untuk menarik trik web CSS yang rapi yang disebut pemuatan tombstone:
- Gambar latar 1 (
var(--bg)
) adalah URL yang kami teruskan sejajar dalam HTML - Gambar latar 2 (
linear-gradient(to top, lch(98 0 0), lch(90 0 0))
adalah gradien yang ditampilkan saat URL sedang dimuat
CSS akan otomatis mengganti gradien dengan gambar, setelah gambar selesai didownload.
Berikutnya, kita akan menambahkan beberapa CSS untuk menghapus beberapa perilaku, sehingga browser dapat bergerak lebih cepat.
Tambahkan kode yang ditandai ke kumpulan aturan .story
Anda:
.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
mencegah pengguna memilih teks secara tidak sengajatouch-action: manipulation
menginstruksikan browser bahwa interaksi ini harus diperlakukan sebagai peristiwa sentuh, yang membebaskan browser dari mencoba memutuskan apakah Anda akan mengklik URL atau tidak
Terakhir, mari kita tambahkan sedikit CSS untuk menganimasikan transisi antar-cerita. Tambahkan
kode yang ditandai ke kumpulan aturan .story
Anda:
.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;
}
}
Class .seen
akan ditambahkan ke cerita yang memerlukan jalan keluar.
Saya mendapatkan fungsi easing kustom (cubic-bezier(0.4, 0.0, 1,1)
)
dari Easing Desain Material
panduan (scroll ke bagian Easing yang dipercepat).
Jika Anda memiliki mata yang tajam, Anda mungkin telah memperhatikan pointer-events: none
dan sedang menggaruk-garuk kepala Anda sekarang. Saya akan mengatakan
ini adalah satu-satunya
kekurangan dari solusi sejauh ini. Kita membutuhkannya karena elemen .seen.story
akan berada di atas dan akan menerima ketukan, meskipun tidak terlihat. Dengan menetapkan
pointer-events
untuk none
, kita mengubah kisah kaca menjadi jendela, dan tidak mencuri
lebih banyak interaksi pengguna. Keuntungannya tidak terlalu buruk, tidak terlalu sulit untuk dikelola di sini
di CSS kita sekarang. Kami tidak berpindah-pindah z-index
. Saya senang dengan hal ini
diam.
JavaScript
Interaksi komponen Stories cukup sederhana bagi pengguna: ketuk kanan untuk maju, ketuk di sebelah kiri untuk kembali. Hal yang sederhana bagi pengguna cenderung menjadi kerja keras bagi pengembang. Meskipun begitu, kita akan berurusan dengan banyak hal.
Penyiapan
Untuk memulainya, mari kita hitung dan
simpan informasi sebanyak yang kita bisa.
Tambahkan kode berikut ke app/js/index.js
:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
Baris pertama JavaScript kita mengambil dan menyimpan referensi ke HTML utama root elemen. Baris berikutnya menghitung di tengah elemen kita, jadi kita dapat memutuskan apakah ketukan akan maju atau mundur.
Negara Bagian
Selanjutnya, kita membuat objek kecil dengan beberapa status yang relevan dengan logika kita. Di sini
kita hanya tertarik pada cerita saat ini. Pada markup HTML, kita bisa
mengaksesnya dengan memilih teman pertama dan kisah terbaru mereka. Menambahkan kode yang ditandai
ke app/js/index.js
Anda:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
const state = {
current_story: stories.firstElementChild.lastElementChild
}
Pemroses
Kita memiliki logika yang cukup untuk mulai memproses peristiwa pengguna dan mengarahkannya.
Tikus
Mari kita mulai dengan mendengarkan peristiwa 'click'
di penampung story kita.
Tambahkan kode yang ditandai ke 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')
})
Jika klik terjadi dan bukan pada elemen <article>
, kami menjamin dan tidak melakukan apa pun.
Jika itu adalah artikel, kita ambil posisi horizontal {i>mouse<i} atau jari dengan
clientX
. Kita belum mengimplementasikan navigateStories
, tetapi argumen yang
yang dibutuhkan untuk menentukan arah
apa yang kita harus tuju. Jika posisi pengguna tersebut
lebih besar dari median, kita tahu kita harus membuka next
, jika tidak
prev
(sebelumnya).
Keyboard
Sekarang, mari kita dengarkan penekanan {i>keyboard<i}. Jika Panah Bawah ditekan, kita akan membuka
ke next
. Jika sasarannya adalah Panah Atas, kita akan membuka prev
.
Tambahkan kode yang ditandai ke 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')
})
Navigasi story
Saatnya menangani logika bisnis unik dari cerita dan UX yang telah menjadi objeknya yang terkenal. Ini terlihat tebal dan rumit, tetapi saya pikir jika Anda menjelaskannya , Anda akan merasa bahwa entri tersebut cukup mudah dicerna.
Di depan, kita menyimpan beberapa pemilih yang membantu kita memutuskan apakah akan menggulir ke teman atau tampilkan/sembunyikan sebuah cerita. Karena HTML adalah tempat kita bekerja, kita akan dengan kueri untuk mengetahui keberadaan teman (pengguna) atau cerita (cerita).
Variabel-variabel ini akan membantu kita menjawab pertanyaan seperti, "mengenai cerita x, apakah "berikutnya" pindah ke cerita lain dari teman yang sama atau kepada teman yang berbeda?" Saya melakukannya dengan menggunakan yang kita bangun, menjangkau orang tua dan anak-anak mereka.
Tambahkan kode berikut ke bagian bawah 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
}
Berikut adalah tujuan logika bisnis kami, yang semirip mungkin dengan natural language:
- Tentukan cara menangani ketukan
- Jika ada cerita berikutnya/sebelumnya: tampilkan cerita tersebut
- Jika cerita terakhir/pertama tentang teman: tunjukkan teman baru
- Jika tidak ada cerita untuk disampaikan ke arah itu: jangan lakukan apa pun
- Simpan cerita baru saat ini ke
state
Tambahkan kode yang ditandai ke fungsi navigateStories
Anda:
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
}
}
}
Cobalah
- Untuk melihat pratinjau situs, tekan Lihat Aplikasi. Lalu tekan Layar penuh .
Kesimpulan
Itu rangkuman untuk kebutuhan saya dengan komponen tersebut. Jangan ragu untuk membangun itu, jalankan dengan data, dan secara umum jadikan milik Anda!