建構分割文字動畫

基礎總覽:如何製作分割字母和字詞動畫。

在這篇文章中,我想分享如何以最簡便、無障礙的方式,在各種瀏覽器中解決網頁的文字分割動畫和互動問題。試用示範模式

示範

如果比較喜歡看影片,可以觀看這篇貼文的 YouTube 版本:

總覽

文字分割動畫效果非常出色。在這篇文章中,我們只會稍微介紹動畫的潛力,但這足以做為基礎,讓您進一步建構動畫。目標是逐步製作動畫。文字預設應可閱讀,動畫則建構在文字上方。文字分割動作效果可能會過於誇張,甚至造成干擾,因此只有在使用者接受動作效果時,我們才會操控 HTML 或套用動作樣式。

以下是工作流程和結果的概略說明:

  1. 準備 CSS 和 JS 的減少動態效果條件變數。
  2. 在 JavaScript 中準備分割文字公用程式。
  3. 在網頁載入時自動調度條件和公用程式。
  4. 撰寫字母和字詞的 CSS 轉場效果和動畫 (最酷的部分!)。

以下是我們想達成的條件式結果預覽畫面:

螢幕截圖:Chrome 開發人員工具已開啟「元素」面板,並將「減少動作」設為「減少」,且 h1 未分割
使用者偏好減少動態效果:文字可辨識 / 未分割

如果使用者偏好減少動作,我們會保留 HTML 文件,不加入任何動畫。如果動作沒問題,我們就會將其切成多個片段。以下是 JavaScript 將文字依字母分割後,HTML 的預覽畫面。

螢幕截圖:Chrome 開發人員工具已開啟「元素」面板,並將「減少動作」設為「減少」,且 h1 未分割
使用者可接受動態效果;文字分成多個 <span> 元素

準備動作條件

這個專案會使用 CSS 和 JavaScript 中提供的 @media (prefers-reduced-motion: reduce) 媒體查詢。這個媒體查詢是我們決定是否要分割文字的主要條件。CSS 媒體查詢會用於保留轉場效果和動畫,而 JavaScript 媒體查詢則會用於保留 HTML 操控。

準備 CSS 條件

我使用 PostCSS 啟用 Media Queries Level 5 的語法,可將媒體查詢布林值儲存至變數:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

準備 JS 條件

在 JavaScript 中,瀏覽器提供檢查媒體查詢的方法,我使用解構從媒體查詢檢查中擷取布林結果並重新命名:

const {matches:motionOK} = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

然後我可以測試 motionOK,且只有在使用者未要求減少動作時,才變更文件。

if (motionOK) {
  // document split manipulations
}

我可以使用 PostCSS 啟用 @nest 語法,從 Nesting Draft 1 檢查相同的值。這樣一來,我就可以將動畫的所有邏輯,以及父項和子項的樣式需求,儲存在同一個位置:

letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}

有了 PostCSS 自訂屬性和 JavaScript 布林值,我們就能有條件地升級效果。這讓我們進入下一節,我將說明如何使用 JavaScript 將字串轉換為元素。

分割文字

無法使用 CSS 或 JS 個別為文字字母、字詞、行等加入動畫。 如要達到這種效果,我們需要方塊。如要為每個字母製作動畫,每個字母都必須是元素。如要為每個字詞製作動畫,每個字詞都必須是元素。

  1. 建立 JavaScript 公用程式函式,將字串分割成元素
  2. 協調這些公用程式的使用情形

分割字母公用函式

您可以先從一個函式開始,該函式會接收字串,並傳回陣列中的每個字母。

export const byLetter = text =>
  [...text].map(span)

ES6 的 擴展 語法確實有助於快速完成這項工作。

分割字詞公用程式函式

這個函式與分割字母類似,會接收字串並傳回陣列中的每個字詞。

export const byWord = text =>
  text.split(' ').map(span)

JavaScript 字串的 split() 方法可讓我們指定要切片的字元。我傳遞了空白空間,表示字詞之間有分隔。

製作方塊公用函式

效果需要每個字母的方塊,而我們在這些函式中看到 map() 是以 span() 函式呼叫。以下是 span() 函式。

const span = (text, index) => {
  const node = document.createElement('span')

  node.textContent = text
  node.style.setProperty('--index', index)

  return node
}

請務必注意,名為 --index 的自訂屬性會使用陣列位置設定。有字母動畫的方塊很棒,但若能在 CSS 中使用索引,看似微小的增幅卻能帶來巨大影響。其中最顯著的影響是交錯。我們可以使用 --index 抵銷動畫,營造交錯效果。

公用程式結論

splitting.js 模組完成:

const span = (text, index) => {
  const node = document.createElement('span')

  node.textContent = text
  node.style.setProperty('--index', index)

  return node
}

export const byLetter = text =>
  [...text].map(span)

export const byWord = text =>
  text.split(' ').map(span)

接下來,請匯入並使用這些 byLetter()byWord() 函式。

分割自動化調度管理

分割公用程式已可使用,因此整合所有內容的步驟如下:

  1. 找出要分割的元素
  2. 分割這些字串,並以 HTML 取代文字

之後,CSS 會接手並為元素 / 方塊製作動畫。

尋找元素

我選擇使用屬性和值來儲存所需動畫的相關資訊,以及如何分割文字。我喜歡將這些宣告式選項放入 HTML。JavaScript 會使用 split-by 屬性尋找元素,並為字母或字詞建立方塊。CSS 會使用 letter-animationword-animation 屬性,指定元素子項並套用轉換和動畫。

以下是 HTML 範例,說明這兩項屬性:

<h1 split-by="letter" letter-animation="breath">animated letters</h1>
<h1 split-by="word" word-animation="trampoline">hover the words</h1>

透過 JavaScript 尋找元素

我使用屬性存在狀態的 CSS 選取器語法,收集要分割文字的元素清單:

const splitTargets = document.querySelectorAll('[split-by]')

從 CSS 尋找元素

我也在 CSS 中使用屬性存在選取器,為所有字母動畫提供相同的基本樣式。稍後,我們將使用屬性值新增更具體的樣式,以達到效果。

letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}

在原地分割文字

針對在 JavaScript 中找到的每個分割目標,我們會根據屬性的值分割文字,並將每個字串對應至 <span>。接著,我們可以用建立的方塊取代元素文字:

splitTargets.forEach(node => {
  const type = node.getAttribute('split-by')
  let nodes = null

  if (type === 'letter') {
    nodes = byLetter(node.innerText)
  }
  else if (type === 'word') {
    nodes = byWord(node.innerText)
  }

  if (nodes) {
    node.firstChild.replaceWith(...nodes)
  }
})

自動化調度管理結論

index.js 完成:

import {byLetter, byWord} from './splitting.js'

const {matches:motionOK} = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

if (motionOK) {
  const splitTargets = document.querySelectorAll('[split-by]')

  splitTargets.forEach(node => {
    const type = node.getAttribute('split-by')
    let nodes = null

    if (type === 'letter')
      nodes = byLetter(node.innerText)
    else if (type === 'word')
      nodes = byWord(node.innerText)

    if (nodes)
      node.firstChild.replaceWith(...nodes)
  })
}

這段 JavaScript 可以解讀為:

  1. 匯入一些輔助公用程式函式。
  2. 檢查這名使用者是否可以移動,如果不行,請勿採取任何行動。
  3. 針對要分割的每個元素。
    1. 根據他們希望的分組方式分組。
    2. 以元素取代文字。

分割動畫和轉場效果

上述文件分割操作剛解鎖了許多潛在的動畫和效果,可透過 CSS 或 JavaScript 實現。本文底部的幾個連結可協助您發揮分潤潛力。

現在就來展現這項技能的用途吧!我會分享 4 個由 CSS 驅動的動畫和轉場效果。🤓

分割字母

我發現下列 CSS 有助於建立分割字母效果。我將所有轉場效果和動畫都放在動態媒體查詢後面,然後為每個新的子字母 span 提供顯示屬性,以及處理空白字元的樣式:

[letter-animation] > span {
  display: inline-block;
  white-space: break-spaces;
}

空白字元樣式很重要,這樣版面配置引擎就不會摺疊僅為空白字元的範圍。現在來看看有狀態的有趣內容。

轉場分割字母範例

這個範例使用 CSS 轉場效果來製作文字分割效果。有了轉場效果,引擎就能在狀態之間製作動畫,我選擇了三種狀態:沒有懸停、懸停在句子中,以及懸停在字母上。

當使用者將滑鼠游標懸停在句子 (也就是容器) 上時,我會縮放所有子項,就像使用者將子項推得更遠一樣。接著,當使用者將游標懸停在某個字母上時,我會將該字母帶到前景。

@media (--motionOK) {
  [letter-animation="hover"] {
    &:hover > span {
      transform: scale(.75);
    }

    & > span {
      transition: transform .3s ease;
      cursor: pointer;

      &:hover {
        transform: scale(1.25);
      }
    }
  }
}

以動畫呈現分割字母範例

這個範例使用預先定義的 @keyframe 動畫,無限次數地為每個字母製作動畫,並運用內嵌自訂屬性索引建立交錯效果。

@media (--motionOK) {
  [letter-animation="breath"] > span {
    animation:
      breath 1200ms ease
      calc(var(--index) * 100 * 1ms)
      infinite alternate;
  }
}

@keyframes breath {
  from {
    animation-timing-function: ease-out;
  }
  to {
    transform: translateY(-5px) scale(1.25);
    text-shadow: 0 0 25px var(--glow-color);
    animation-timing-function: ease-in-out;
  }
}

分割字詞

在這些範例中,Flexbox 可做為容器類型,並善用 ch 單位做為適當的間距長度。

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
Flexbox 開發人員工具,顯示字詞之間的間距

轉場效果分割字詞範例

在這個轉場效果範例中,我再次使用懸停。由於效果最初會隱藏內容,直到游標懸停為止,因此我確保只有在裝置具備懸停功能時,才會套用互動和樣式。

@media (hover) {
  [word-animation="hover"] {
    overflow: hidden;
    overflow: clip;

    & > span {
      transition: transform .3s ease;
      cursor: pointer;

      &:not(:hover) {
        transform: translateY(50%);
      }
    }
  }
}

Animate split words example

在這個動畫範例中,我再次使用 CSS @keyframes,在一般段落文字上建立交錯的無限動畫。

[word-animation="trampoline"] > span {
  display: inline-block;
  transform: translateY(100%);
  animation:
    trampoline 3s ease
    calc(var(--index) * 150 * 1ms)
    infinite alternate;
}

@keyframes trampoline {
  0% {
    transform: translateY(100%);
    animation-timing-function: ease-out;
  }
  50% {
    transform: translateY(0);
    animation-timing-function: ease-in;
  }
}

結論

現在您已瞭解我的做法,您會怎麼做呢?🙂

讓我們多元化方法,學習在網路上建構內容的所有方式。 建立 Codepen 或代管自己的試用版,然後在推文中提及我,我會將其新增至下方的「社群混音」部分。

來源

更多示範和靈感

社群重混作品