この Codelab では、Instagram のストーリーのようなエクスペリエンスを構築する方法について説明します。 できます。コンポーネントは、HTML、CSS、 JavaScript
ブログ投稿「ストーリーのコンポーネントを作成する」をご覧ください。 をご覧ください。
セットアップ
- [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>
- YouTube では、ストーリーのプロトタイプを作成できるように画像サービス(
picsum.com
)を使用しています。 - 各
<article>
のstyle
属性は、プレースホルダの読み込みの一部である これについては次のセクションで詳しく説明します。
CSS
コンテンツをスタイリッシュに彩りましょう。この骨を、みんなが思えるものに変えてみよう 指定します。本日はモバイル ファーストの対応を予定しています。
.stories
<div class="stories">
コンテナには、水平スクロール コンテナを作成します。
これは次の方法で実現できます。
- コンテナをグリッドにする
- 各子を行トラックを埋めるように設定します
- 各子の幅をモバイル デバイスのビューポートの幅にする
引き続き 100vw
分の新しい列が前の列の右側に配置されます
すべての HTML 要素がマークアップ内に配置されるまで
![全幅レイアウトを示すグリッド ビジュアルが表示された Chrome と DevTools](https://web.developers.google.cn/static/articles/codelab-building-a-stories-component/image/chrome-devtools-open-a-72aaa3c91f873.png?authuser=9&hl=ja)
app/css/index.css
の下部に次の CSS を追加します。
.stories {
display: grid;
grid: 1fr / auto-flow 100%;
gap: 1ch;
}
ビューポートを越えてコンテンツを拡張できたので、次は
コンテナの処理方法を指定します。ハイライト表示されたコード行を .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
オーバースクロール動作
セクションがあります。
親コンテナと子コンテナの両方がスクロールのスナップに同意する必要があるため、
処理しましょうapp/css/index.css
の末尾に次のコードを追加します。
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
}
アプリはまだご利用いただけませんが、
scroll-snap-type
が有効または無効になりました。有効にすると、横向き、縦向き、
スクロールして次のストーリーにスナップします。無効にすると、ブラウザは
デフォルトのスクロール動作を指定します。
そうすると、友だちをスクロールできるようになりますが、まだ問題があります ストーリーで解決します
.user
これらの子ストーリーをラングリングするレイアウトを .user
セクションに作成しましょう。
配置する必要がありますこの問題を解決するために、便利な積み重ねの手法を使用します。
基本的には、行と列のグリッドが同じ 1x1 のグリッドを作成します。
[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
あとは、ストーリー アイテム自体のスタイルを設定するだけです。
先ほど、各 <article>
要素の style
属性は
プレースホルダの読み込み方法:
<article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>
ここでは、CSS の background-image
プロパティを使用します。これにより、
複数の背景画像を使用できます。並べ替えて、ユーザーが
読み込みが完了すると自動的に表示されます。宛先
有効にするには、画像の URL をカスタム プロパティ(--bg
)に挿入して、それを使用します。
使用して、読み込み用プレースホルダと重ねて表示します。
まず、.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 つの背景画像の定義
を使用すると、Load tombstone という CSS ウェブの工夫を凝らすことができます。
- 背景画像 1(
var(--bg)
)は、HTML でインラインで渡した URL です。 - 背景画像 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)
)が表示されました
マテリアル デザインの Easing から
ガイド([Accerlerated イージング] セクションまでスクロール)
鋭い目を持つ人なら、pointer-events: none
に気づいたでしょう
という言葉です。これが唯一の
課題に直面しますこれが必要なのは、.seen.story
要素が
見えなくてもタップでたどります。ルールの
pointer-events
から none
へ、ガラスのストーリーが窓に変身し、
増加する傾向があります。トレードオフも悪くない、管理も難しくない
CSS に入力してみましょうz-index
はジャグにしていません。気分が良くなる
できます。
JavaScript
ストーリー コンポーネントのインタラクションは、ユーザーにとって非常にシンプルです。 右へ進むには左、戻るには左をタップ。ユーザーにとってシンプルなこと 大変な労力を費やしていますただし、多くの処理は Google が行います。
セットアップ
まず、できるだけ多くの情報を計算して保存しましょう。
app/js/index.js
に次のコードを追加します。
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
JavaScript の最初の行は、プライマリ HTML への参照を取得して保存します。 要素ルート。次の行で要素の中央位置を計算しています。 タップを進むか戻るかを決定できます。
州
次に、ロジックに関連する状態を持つ小さなオブジェクトを作成します。この
現在のストーリーにしか関心がありませんHTML マークアップでは、
1 人目の友だちとその最新のストーリーを選んでアクセスできます。ハイライト表示されたコードを追加する
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')
})
ストーリーのナビゲーション
ストーリーの独自のビジネス ロジックとストーリーが成り立つ UX に取り組むときです。 おわかりいただけると思いますわかりにくいように見えますが 見やすいと思います
最初に、スクロールするか選択するかの判断に役立つ 表示/非表示を切り替えられます。作業場所は 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
}
自然言語にできる限り近い、ビジネス ロジックの目標は次のとおりです。
- タップの処理方法を決定する
<ph type="x-smartling-placeholder">
- </ph>
- 次/前のストーリーがある場合: そのストーリーを表示する
- その友だちの最後/最初のストーリーの場合: 新しい友だちを見せる
- その方向に進むストーリーがない場合: 何もしない
- 新しい最新のストーリーを
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
}
}
}
試してみる
- サイトをプレビューするには、[アプリを表示] を押します。[
全画面表示
。
まとめ
これで、このコンポーネントに関するニーズについての学習は終了です。自由に構築できる そして、データで実際にやってみて、いろいろと自分好みにカスタマイズしましょう。