捨棄 create-react-app 之餘還架了個 astro blog 昭告天下:處理首頁樣式

給還不熟 css @container 讀者的懶人包:在過去,前端時常透過 @media screen and (min-width: 900px) 來根據螢幕寬度改變畫面樣式(比如 flex-direction 在裝置螢幕變寬時,從 column 變成 row)。而 @container 的出現讓比較基準從此可以脫離「螢幕寬」,現在開發者可以將「容器(親元件)的寬度」作為條件來改變子元件的樣式設定。

個人目前的部落格首頁右半部就是使用 @container 來處理最新五篇文章(圖佐說明)的排版。今天會來簡單解說製作方式。

完整程式碼位置如下:

處理首頁排版

首先設定 .featured-posts-container 來處理文章流向。在螢幕寬低於 1200px 時,透過 display: flex 搭配 flex-direction: column 保持垂直排版:

.featured-posts-container {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-bottom: 16px;
}

而當螢幕寬於 1200px 後,改用 display: grid 搭配 grid-template-areas 來安排文章元件佈局。以下設定代表:整片 grid 區塊呈現 3 x 2 的格位,除 post-0 佔據第一行的兩格位置外,其餘的文章都各自佔據一個格子。

.featured-posts-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, 1fr);
  grid-template-areas:
    'post-0 post-0 post-1'
    'post-2 post-3 post-4';
  gap: 16px;
}

而在透過 postShouldWithSummary 渲染文章元件時,將 index 資訊(此文排序第X篇)透過 class:list 傳入文章元件 FeaturedPostLayout 中:

postShouldWithSummary.map((post, index) => (
  <div class:list={['post', `post-${index}`]}>
    <FeaturedPostLayout
      title={post.frontmatter.title}
      date={post.frontmatter.date}
      tag={post.frontmatter.tag}
      banner={post.frontmatter.banner}
      summary={post.frontmatter.summary || ''}
      url={post.url}
    />
  </div>
));

接著就可以在 <style></style> 裡透過 .post-{index} 來指定每一個文章元件會與哪一部分的 grid-area 掛鉤:

.post-0 {
  grid-area: post-0;
}

.post-1 {
  grid-area: post-1;
}

.post-2 {
  grid-area: post-2;
}

.post-3 {
  grid-area: post-3;
}

.post-4 {
  grid-area: post-4;
}

首頁完工,接下來會來介紹 FeaturedPostLayout 中的設定。

處理 FeaturedPostLayout 樣式

首先在元件的 Component Script 部位取出 Astro.props 中的背景圖位置,設定為變數 bannerUrl

const { title, date, tag, banner, summary, url } = Astro.props;
const bannerUrl = banner ? `url(${banner})` : 'none';

接著將背景圖變數透過 define:vars 傳入 astro 元件的樣式標籤中:

<style define:vars={{ bannerUrl }}>
  /* styles ... */
</style>

這樣就能在樣式區塊取用 Astro.props 提供的文章標題配圖了 (́◉◞౪◟◉‵)


接著來處理元件本身的排版。首先設定最外層的 div 元件「寬(inline-size)」為 @container 參考值:

.featured-post-wrapper {
  container-type: inline-size;
}

搭配 @container (min-width: 520px) 即代表 .featured-post-wrapper 的寬度至少有 520px 時,圖片與文字欄位要改為 flex-direction: row 流向:

@container (min-width: 520px) {
  .featured-post-layout {
    flex-direction: row;
  }

  .banner-wrapper,
  .text-wrapper {
    flex: 1 1 50%;
  }
}

對比 .featured-post-wrapper 的寬度未滿 520px 時,圖文要採取 column 排版:

.featured-post-layout {
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

這樣就完成 css container query 的設定了。現在 .banner-wrapper.text-wrapper 會根據 .featured-post-wrapper 的寬度來改變排版。


最後因為想要做出「滑鼠 hover 後、圖片放大(要能搭配 transition)」的效果,決定使用 background-image 搭配偽元素 ::before 來實現。首先將 .banner-wrapper 設定為 positive: relative 來作為 偽元素的定位點:

.banner-wrapper {
  position: relative;
  min-height: 200px;
  overflow: hidden;
}

接著將背景塞到 ::before 中,並設定 transition 效果:

.banner-wrapper::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
  background-image: var(--bannerUrl);
  transform: scale(1);
  transition: transform 0.3s ease;
}

最後設定 :hover 時,要微微放大 ::before 內容:

.banner-wrapper:hover::before {
  transform: scale(1.1);
}

大功告成。

總結

目前四大主流瀏覽器都已經支援 container query 了,讓樣式不再根據裝置、而是親容器寬度來安排,可以調整出視覺效果更好的排版。

十分推薦在比較不需要擔心相容性的 side project 練手唷 (・`ω´・)