Tạo ảnh động chia tách văn bản

Thông tin tổng quan cơ bản về cách tạo ảnh động chữ cái và từ được tách.

Trong bài đăng này, tôi muốn chia sẻ suy nghĩ về các cách giải quyết ảnh động và tương tác văn bản phân tách cho web ở mức tối thiểu, dễ tiếp cận và hoạt động trên các trình duyệt. Hãy dùng thử bản minh hoạ.

Bản minh hoạ

Nếu bạn thích xem video, hãy xem phiên bản video của bài đăng này trên YouTube:

Tổng quan

Ảnh động văn bản phân tách có thể rất ấn tượng. Chúng ta sẽ chỉ tìm hiểu sơ qua về tiềm năng của ảnh động trong bài đăng này, nhưng bài đăng này sẽ cung cấp nền tảng để bạn có thể xây dựng. Mục tiêu là tạo ảnh động một cách liên tục. Theo mặc định, văn bản phải đọc được, với ảnh động được tạo ở trên cùng. Hiệu ứng chuyển động chia tách văn bản có thể quá xao nhãng và có thể gây gián đoạn, vì vậy chúng ta sẽ chỉ thao tác với HTML hoặc áp dụng kiểu chuyển động nếu người dùng vẫn chấp nhận chuyển động.

Dưới đây là tổng quan chung về quy trình và kết quả:

  1. Chuẩn bị các biến có điều kiện giảm chuyển động cho CSS và JS.
  2. Chuẩn bị các tiện ích văn bản phân tách trong JavaScript.
  3. Điều phối các điều kiện và tiện ích khi tải trang.
  4. Viết hiệu ứng chuyển đổi và ảnh động CSS cho chữ cái và từ (phần thú vị!).

Dưới đây là bản xem trước của các kết quả có điều kiện mà chúng ta sẽ đạt được:

ảnh chụp màn hình công cụ dành cho nhà phát triển Chrome với bảng điều khiển Elements (Thành phần) đang mở và chế độ giảm chuyển động được đặt thành "reduce" (giảm) và h1 hiển thị không bị tách
Người dùng muốn giảm chuyển động: văn bản dễ đọc/không bị tách rời

Nếu người dùng muốn giảm chuyển động, chúng ta sẽ để nguyên tài liệu HTML và không tạo ảnh động. Nếu chuyển động được, chúng ta sẽ tiếp tục và cắt thành từng mảnh. Dưới đây là bản xem trước HTML sau khi JavaScript phân tách văn bản theo chữ cái.

ảnh chụp màn hình công cụ của Chrome cho nhà phát triển với bảng điều khiển Phần tử mở và chuyển động được giảm được đặt thành "giảm" và h1 được hiển thị không phân tách
Người dùng vẫn ổn với chuyển động; văn bản được chia thành nhiều <span> phần tử

Đang chuẩn bị điều kiện chuyển động

Truy vấn nội dung đa phương tiện @media (prefers-reduced-motion: reduce) có sẵn và thuận tiện sẽ được sử dụng từ CSS và JavaScript trong dự án này. Truy vấn nội dung nghe nhìn này là điều kiện chính để quyết định có phân tách văn bản hay không. Truy vấn nội dung đa phương tiện CSS sẽ được dùng để giữ lại các hiệu ứng chuyển đổi và ảnh động, còn truy vấn nội dung đa phương tiện JavaScript sẽ được dùng để giữ lại thao tác thao tác HTML.

Chuẩn bị điều kiện CSS

Tôi đã sử dụng PostCSS để bật cú pháp của Truy vấn nội dung nghe nhìn cấp 5, trong đó tôi có thể lưu trữ một truy vấn nội dung nghe nhìn boolean vào một biến:

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

Chuẩn bị câu lệnh có điều kiện JS

Trong JavaScript, trình duyệt cung cấp một cách để kiểm tra truy vấn nội dung đa phương tiện. Tôi đã sử dụng tính năng phân ly để trích xuất và đổi tên kết quả boolean từ quy trình kiểm tra truy vấn nội dung đa phương tiện:

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

Sau đó, tôi có thể kiểm thử motionOK và chỉ thay đổi tài liệu nếu người dùng không yêu cầu giảm chuyển động.

if (motionOK) {
  // document split manipulations
}

Tôi có thể kiểm tra cùng một giá trị bằng cách sử dụng PostCSS để bật cú pháp @nest từ Bản nháp lồng 1. Điều này cho phép tôi lưu trữ tất cả logic về ảnh động, cũng như các yêu cầu về kiểu cho thành phần mẹ và con ở cùng một nơi:

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

Với thuộc tính tuỳ chỉnh PostCSS và một boolean JavaScript, chúng ta đã sẵn sàng để nâng cấp hiệu ứng theo điều kiện. Điều đó đưa chúng ta đến phần tiếp theo, trong đó tôi phân tích JavaScript để chuyển đổi chuỗi thành các phần tử.

Phân tách văn bản

Bạn không thể tạo ảnh động riêng cho các chữ cái, từ, dòng văn bản, v.v. bằng CSS hoặc JS. Để đạt được hiệu ứng, chúng ta cần những chiếc hộp. Nếu chúng ta muốn tạo ảnh động cho từng chữ cái, thì mỗi chữ cái phải là một phần tử. Nếu chúng ta muốn tạo ảnh động cho từng từ, thì mỗi từ cần phải là một phần tử.

  1. Tạo các hàm tiện ích JavaScript để chia chuỗi thành các phần tử
  2. Điều phối việc sử dụng các tiện ích này

Hàm tiện ích tách chữ cái

Bạn có thể bắt đầu bằng một hàm nhận một chuỗi và trả về từng chữ cái trong một mảng.

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

Cú pháp truyền dữ liệu từ ES6 thực sự đã giúp việc này trở nên nhanh chóng.

Hàm tiện ích tách từ

Tương tự như việc tách các chữ cái, hàm này nhận một chuỗi và trả về từng từ trong một mảng.

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

Phương thức split() trên chuỗi JavaScript cho phép chúng ta chỉ định ký tự cần cắt. Tôi đã truyền một khoảng trống, cho biết có sự phân tách giữa các từ.

Chức năng hữu dụng tạo hộp

Hiệu ứng này yêu cầu các hộp cho mỗi chữ cái và chúng ta thấy trong các hàm đó, map() đang được gọi bằng hàm span(). Sau đây là hàm span().

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

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

  return node
}

Điều quan trọng cần lưu ý là một thuộc tính tuỳ chỉnh có tên --index đang được đặt bằng vị trí mảng. Việc có các hộp cho ảnh động chữ cái là rất tuyệt, nhưng việc có một chỉ mục để sử dụng trong CSS là một điểm bổ sung có vẻ nhỏ nhưng có tác động lớn. Điều đáng chú ý nhất trong tác động lớn này là staggering. Chúng ta có thể sử dụng --index để bù ảnh động cho giao diện tạo hiệu ứng xếp kề.

Kết luận về tiện ích

Mô-đun splitting.js đã hoàn tất:

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)

Tiếp theo là nhập và sử dụng các hàm byLetter()byWord() này.

Điều phối phân tách

Khi các tiện ích phân tách đã sẵn sàng để sử dụng, việc kết hợp tất cả các tiện ích này có nghĩa là:

  1. Tìm phần tử cần tách
  2. Phân tách các phần tử này và thay thế văn bản bằng HTML

Sau đó, CSS sẽ tiếp quản và tạo ảnh động cho các phần tử/hộp.

Tìm phần tử

Tôi đã chọn sử dụng các thuộc tính và giá trị để lưu trữ thông tin về ảnh động mong muốn cũng như cách phân tách văn bản. Tôi thích đưa các tuỳ chọn khai báo này vào HTML. Thuộc tính split-by được sử dụng từ JavaScript để tìm các phần tử và tạo hộp cho chữ cái hoặc từ. Thuộc tính letter-animation hoặc word-animation được sử dụng từ CSS để nhắm mục tiêu phần tử con và áp dụng các phép biến đổi và ảnh động.

Dưới đây là một mẫu HTML minh hoạ hai thuộc tính này:

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

Tìm các phần tử từ JavaScript

Tôi đã sử dụng cú pháp bộ chọn CSS cho sự hiện diện của thuộc tính để thu thập danh sách các phần tử muốn văn bản được phân tách:

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

Tìm phần tử từ CSS

Tôi cũng sử dụng bộ chọn hiện diện thuộc tính trong CSS để cung cấp cho tất cả ảnh động chữ cái cùng một kiểu cơ sở. Sau đó, chúng ta sẽ sử dụng giá trị thuộc tính để thêm các kiểu cụ thể hơn nhằm đạt được hiệu ứng.

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

Phân tách văn bản tại chỗ

Đối với mỗi mục tiêu phân tách mà chúng ta tìm thấy trong JavaScript, chúng ta sẽ phân tách văn bản của mục tiêu đó dựa trên giá trị của thuộc tính và liên kết từng chuỗi với một <span>. Sau đó, chúng ta có thể thay thế văn bản của phần tử bằng các hộp đã tạo:

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)
  }
})

Kết luận về việc dàn xếp

index.js hoàn chỉnh:

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)
  })
}

Bạn có thể đọc đoạn mã JavaScript bằng tiếng Anh như sau:

  1. Nhập một số hàm tiện ích trợ giúp.
  2. Kiểm tra xem người dùng này có thể sử dụng tính năng chuyển động hay không, nếu không thì không làm gì cả.
  3. Đối với mỗi phần tử muốn được tách.
    1. Phân chia các nhóm này dựa trên cách họ muốn phân chia.
    2. Thay thế văn bản bằng các phần tử.

Tách hoạt ảnh và hiệu ứng chuyển tiếp

Thao tác phân tách tài liệu ở trên vừa mở ra vô số ảnh động và hiệu ứng tiềm năng bằng CSS hoặc JavaScript. Có một số đường liên kết ở cuối bài viết này để giúp bạn khai thác tiềm năng phân tách.

Đã đến lúc cho bạn thấy những gì bạn có thể làm với tính năng này! Tôi sẽ chia sẻ 4 ảnh động và hiệu ứng chuyển đổi do CSS điều khiển. 🤓

Chữ cái tách

Để tạo nền tảng cho các hiệu ứng phân tách chữ cái, tôi thấy CSS sau đây rất hữu ích. Tôi đặt tất cả các hiệu ứng chuyển đổi và ảnh động sau truy vấn nội dung đa phương tiện chuyển động, sau đó cung cấp cho mỗi chữ cái con mới span một thuộc tính hiển thị cùng với kiểu để thực hiện thao tác với khoảng trắng:

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

Kiểu khoảng trắng rất quan trọng để các span chỉ là một khoảng trắng không bị trình bố cục thu gọn. Bây giờ, hãy chuyển sang phần thú vị về trạng thái.

Ví dụ về chữ cái phân tách chuyển đổi

Ví dụ này sử dụng hiệu ứng chuyển đổi CSS cho hiệu ứng văn bản phân tách. Với các hiệu ứng chuyển đổi, chúng ta cần các trạng thái để công cụ tạo ảnh động giữa các trạng thái đó. Tôi đã chọn ba trạng thái: không di chuột, di chuột trong câu, di chuột trên một chữ cái.

Khi người dùng di chuột qua câu (còn gọi là vùng chứa), tôi sẽ thu nhỏ tất cả các thành phần con như thể người dùng đẩy các thành phần đó ra xa hơn. Sau đó, khi người dùng di chuột qua một chữ cái, tôi sẽ đưa chữ cái đó lên trước.

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

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

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

Ví dụ về ảnh động tách chữ cái

Ví dụ này sử dụng ảnh động @keyframe được xác định trước để tạo ảnh động vô hạn cho từng chữ cái và tận dụng chỉ mục thuộc tính tuỳ chỉnh nội tuyến để tạo hiệu ứng lồng ghép.

@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;
  }
}

Tách từ

Flexbox hoạt động như một loại vùng chứa cho tôi trong các ví dụ này, tận dụng hiệu quả đơn vị ch làm khoảng cách hợp lý.

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
Flexbox devtools cho thấy khoảng cách giữa các từ

Ví dụ về hiệu ứng chuyển đổi đối với từ được tách

Trong ví dụ về hiệu ứng chuyển đổi này, tôi sử dụng thao tác di chuột một lần nữa. Vì hiệu ứng ban đầu ẩn nội dung cho đến khi di chuột, nên tôi đảm bảo rằng hoạt động tương tác và kiểu chỉ được áp dụng nếu thiết bị có khả năng di chuột.

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

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

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

Ví dụ về Tạo ảnh động cho từ được tách

Trong ví dụ về ảnh động này, tôi sử dụng lại CSS @keyframes để tạo ảnh động vô hạn theo kiểu so le trên một đoạn văn bản thông thường.

[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;
  }
}

Kết luận

Giờ bạn đã biết cách tôi thực hiện điều đó, bạn sẽ làm thế nào?! 🙂

Hãy đa dạng hoá các phương pháp và tìm hiểu tất cả các cách xây dựng trên web. Tạo một Codepen hoặc lưu trữ bản minh hoạ của riêng bạn, tweet cho tôi về bản minh hoạ đó và tôi sẽ thêm bản minh hoạ đó vào phần Bản phối lại của cộng đồng ở bên dưới.

Nguồn

Bản minh hoạ và nguồn cảm hứng khác

Bản phối lại của cộng đồng