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 tách chữ và từ.

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ột cách tối thiểu, dễ truy cập và hoạt động trên nhiều trình duyệt. Dùng thử bản minh hoạ.

Bản minh hoạ

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

Tổng quan

Ảnh động văn bản tách rời có thể rất ấn tượng. Trong bài đăng này, chúng ta sẽ chỉ tìm hiểu sơ qua về tiềm năng của ảnh động, nhưng bài đăng này sẽ cung cấp nền tảng để bạn có thể phát triển thêm. Mục tiêu là tạo hiệu ứng chuyển động tăng dần. 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 văn bản tách có thể trở nên quá mức và có khả năng 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 đồng ý với chuyển động.

Sau đây là thông tin tổng quan 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 các chữ cái và từ (phần thú vị!).

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

ảnh chụp màn hình công cụ cho nhà phát triển của Chrome với bảng điều khiển Elements (Phần tử) đang mở và chế độ chuyển động giảm được đặt thành "reduce" (giảm) và h1 xuất hiện ở trạng thái chưa phân chia
Người dùng thích chế độ chuyển động giảm: văn bản dễ đọc / không bị tách

Nếu người dùng muốn giảm chuyển động, chúng ta sẽ giữ nguyên tài liệu HTML và không tạo hiệu ứng động. Nếu chuyển động ổn, chúng ta sẽ chia thành nhiều phần. Sau đây là bản xem trước của HTML sau khi JavaScript chia văn bản theo chữ cái.

ảnh chụp màn hình công cụ cho nhà phát triển của Chrome với bảng điều khiển Elements (Phần tử) đang mở và chế độ chuyển động giảm được đặt thành "reduce" (giảm) và h1 xuất hiện ở trạng thái chưa phân chia
Người dùng không gặp vấn đề với chuyển động; văn bản được chia thành nhiều phần tử <span>

Chuẩn bị câu lệnh có điều kiện về chuyển động

Truy vấn nội dung nghe nhìn có sẵn @media (prefers-reduced-motion: reduce) một cách thuận tiện sẽ được 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 để chúng tôi quyết định có tách văn bản hay không. Truy vấn phương tiện CSS sẽ được dùng để giữ lại các hiệu ứng chuyển đổi và ảnh động, trong khi truy vấn phương tiện JavaScript sẽ được dùng để giữ lại 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 Media Queries Level 5, trong đó tôi có thể lưu trữ một boolean truy vấn nội dung nghe nhìn vào một biến:

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

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

Trong JavaScript, trình duyệt cung cấp một cách để kiểm tra các truy vấn nội dung nghe nhìn, tôi đã sử dụng destructuring để trích xuất và đổi tên kết quả boolean từ quá trình kiểm tra truy vấn nội dung nghe nhìn:

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

Sau đó, tôi có thể kiểm tra motionOK và chỉ thay đổi tài liệu nếu người dùng chưa 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ừ Nesting Draft 1. Điều này cho phép tôi lưu trữ tất cả logic về hiệu ứng chuyển động và các yêu cầu về kiểu của hiệu ứng đó cho thành phần mẹ và thành phần con ở 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 có điều kiện. Điều đó đưa chúng ta đến phần tiếp theo, nơi tôi sẽ phân tích JavaScript để chuyển đổi các chuỗi thành phần tử.

Chia văn bản

Bạn không thể tạo hiệu ứng động cho từng chữ cái, từ, dòng, v.v. bằng CSS hoặc JS. Để đạt được hiệu ứng này, chúng ta cần các hộp. Nếu muốn tạo hiệu ứng chuyển động cho từng chữ cái, thì mỗi chữ cái phải là một phần tử. Nếu muốn tạo hiệu ứ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 phân tách chữ cái

Một nơi thú vị để bắt đầu là với một hàm lấy 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 spread (trải rộng) của ES6 thực sự giúp tôi hoàn thành việc này một cách 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 lấy 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 các chuỗi JavaScript cho phép chúng ta chỉ định những 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ừ.

Hàm tiện ích 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 là --index đang được đặt ở vị trí mảng. Việc có các ô cho hoạt ảnh chữ là rất tố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 lại có tác động lớn. Điều đáng chú ý nhất trong tác động lớn này là sự choáng váng. Chúng ta có thể sử dụng --index để bù đắp ảnh động nhằm tạo hiệu ứng xếp chồng.

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 chia

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

  1. Tìm những phần tử cần tách
  2. Chia 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 và cách phân tách văn bản. Tôi thích đặt các lựa chọn khai báo này vào HTML. Thuộc tính split-by được 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 dùng từ CSS để nhắm đến các phần tử con và áp dụng các phép biến đổi cũng như hiệu ứng động.

Dưới đây là một mẫu HTML minh hoạ 2 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 tách văn bản:

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

Tìm các phần tử trong CSS

Tôi cũng sử dụng bộ chọn sự hiện diện của thuộc tính trong CSS để cung cấp cho tất cả các hoạt ảnh chữ cái cùng một kiểu cơ bản. Sau đó, chúng ta 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 */
  }
}

Chia văn bản tại chỗ

Đối với mỗi mục tiêu được phân chia mà chúng ta tìm thấy trong JavaScript, chúng ta sẽ phân chia 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 mà chúng ta đã 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 sắp xếp

index.js đã hoàn thà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 JavaScript bằng tiếng Anh như sau:

  1. Nhập một số hàm trợ giúp tiện ích.
  2. Kiểm tra xem chuyển động có phù hợp với người dùng này 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 chia.
    1. Chia các nhóm này dựa trên cách họ muốn được chia.
    2. Thay thế văn bản bằng các phần tử.

Chia tách ảnh động và hiệu ứng chuyển tiếp

Thao tác chia 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 khám phá tiềm năng phân tách.

Đã đến lúc thể hiện những gì bạn có thể làm với công cụ này! Tôi sẽ chia sẻ 4 ảnh động và hiệu ứng chuyển đổi dựa trên CSS. 🤓

Chia chữ cái

Để tạo hiệu ứng chữ tách rời, tôi thấy CSS sau đây rất hữu ích. Tôi đặt tất cả hiệu ứng chuyển đổi và ảnh động đằng sau truy vấn nội dung nghe nhì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 một kiểu để xử lý 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 khoảng chỉ là một khoảng trống không bị công cụ bố cục thu gọn. Bây giờ là phần thú vị về trạng thái.

Ví dụ về chữ cái chuyển tiếp

Ví dụ này sử dụng hiệu ứng chuyển đổi CSS cho hiệu ứng văn bản tách. Với 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 3 trạng thái: không di chuột, di chuột trong câu và di chuột lên một chữ cái.

Khi người dùng di chuột lên câu (tức 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 chúng ra xa hơn. Sau đó, khi người dùng di chuột lên một chữ cái, tôi sẽ đưa chữ cái đó lên phía 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 cho các chữ cái tách rờ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 so le.

@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 đối với tôi trong những ví dụ này, tận dụng hiệu quả đơn vị ch làm độ dài khoảng trống hợp lý.

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
Công cụ dành cho nhà phát triển hộp linh hoạt cho thấy khoảng cách giữa các từ

Ví dụ về từ chuyển cảnh

Trong ví dụ về hiệu ứng chuyển đổi này, tôi sử dụng lại hiệu ứng di chuột. Vì hiệu ứng này ban đầu sẽ ẩn nội dung cho đến khi người dùng 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ề ảnh động cho các từ được tách

Trong ví dụ về ảnh động này, tôi dùng lại @keyframes CSS để tạo một ảnh động vô hạn có độ trễ 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 làm, vậy bạn sẽ làm như 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 bản minh hoạ trên Codepen hoặc tự lưu trữ bản minh hoạ của riêng bạn, sau đó gửi cho tôi qua Twitter. 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

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

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