程式碼研究室:建構故事元件

本程式碼研究室將說明如何打造 Instagram 限時動態等內容 在網路上。建構元件時,我們會從 HTML、CSS 開始 那麼 JavaScript

查看我的網誌文章:打造短片故事元件 ,瞭解建構此元件時的漸進式強化功能。

設定

  1. 按一下「Remix to Edit」即可編輯專案。
  2. 開啟 app/index.html

HTML

我一直努力使用語意 HTML。 由於每個好友的故事數量不多,因此我覺得使用 每位好友的 <section> 元素和每則故事的 <article> 元素。 讓我們從頭開始吧。首先,我們需要建立容器 故事元件

<body> 中新增 <div> 元素:

<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) 來為故事設計原型。
  • 每個 <article> 上的 style 屬性都是載入預留位置的一部分 您將在下一節進一步瞭解這個主題

CSS

我們的內容隨時都展現風格。讓我們將這些骨頭轉換為客戶 或想與其互動從今天開始,我們決定以行動裝置為優先。

.stories

對於 <div class="stories"> 容器,我們需要水平捲動容器。 做法如下:

  • 將容器設為格線
  • 設定每個子項來填入列軌
  • 讓每個子項的寬度調整為行動裝置可視區域的寬度

格線將繼續將新的 100vw 寬欄放置在上一個儲存格的右側 一,直到標記所有的 HTML 元素都放下為止

Chrome 和開發人員工具開啟,並以格狀檢視畫面顯示完整寬度版面配置
Chrome 開發人員工具顯示格線欄溢位,產生水平捲軸。

將下列 CSS 新增至 app/css/index.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 捲動 Snap 點中的 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 屬性,以便為 一或多張背景圖片。我們可以依序排列這些元件 圖示,會在圖片載入完畢後自動顯示。目的地: 啟用後,我們會將圖片網址放入自訂屬性 (--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 張背景圖片 我們可以提取名為 loading tombstone 的可靠 CSS 網路技巧:

  • 背景圖片 1 (var(--bg)) 是我們內嵌在 HTML 中的網址
  • 背景圖片 2 (linear-gradient(to top, lch(98 0 0), lch(90 0 0)) 為漸層色 載入網址時顯示

圖片下載完畢後,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 會指示瀏覽器在這些互動 應視為觸控事件,這樣瀏覽器就會 決定是否要點擊網址

最後,加入一個 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)) Material Design 的加/減速設定 指南圖示 (捲動至「加速加/減速」部分)。

無論你喜歡什麼,也許會注意到pointer-events: none 而且你現在也抓到自己頭部我認為這是 解決方案的缺點我們需要這樣做,因為 .seen.story 元素 即使不可見,也能夠輕觸。透過設定 pointer-events飛往none,我們將玻璃故事打造成一個窗戶,然後偷偷告訴您 增加的使用者互動次數權衡利弊得失,而且不易管理 CSS 供應商我們不會在z-index的東西上表演。我感覺好心情 保持。

JavaScript

短片故事元件的互動方式非常簡單:輕觸 按左方可返回,向左滑動可返回上一個畫面。使用者通常覺得很簡單 成為開發人員但我們會處理好多事。

設定

首先,請盡可能運算及儲存各種資訊。 在 app/js/index.js 加入以下程式碼:

const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)

第一行 JavaScript 會擷取並儲存我們主要 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 是我們處理的位置 查詢該圖片,尋找好友 (使用者) 或故事 (故事)。

這些變數有助於我們回答以下這類問題: 是想從這位朋友分享另一個故事,還是要跟其他朋友分享?我是透過 藉此觸及家長和孩子的孩子

將以下程式碼新增至 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
    }
  }
}

立即試用

  • 如要預覽網站,請按下「查看應用程式」。然後按下 全螢幕 全螢幕

結論

以上就是使用元件的需求。歡迎建立 並使用資料推動資料,而且通常成為您自己專屬的功能!