0
0
Lập trình
Flame Kris
Flame Krisbacodekiller

Tạo Accordion bằng HTML, CSS và JavaScript: Hướng dẫn Chi tiết

Đăng vào 3 tuần trước

• 6 phút đọc

Chủ đề:

HTMLNextjspure css

Chào các bạn, mình là Bình, một kỹ sư phần mềm với kinh nghiệm làm việc trong lĩnh vực Frontend. Trong thời gian qua, mình thường chỉ làm việc với các dashboard mà ít có cơ hội phát triển các trang web chính như Home hay Landing Page. Gần đây, mình lại có cơ hội làm việc với nhiều dự án yêu cầu thiết kế trang chính cần phải hoạt động hiệu quả mà không cần JavaScript, điều này tạo ra thách thức lớn khi các thành phần như dropdown menu, accordion và carousel đều yêu cầu JavaScript để hoạt động. Tuy nhiên, mình muốn tối ưu hóa việc sử dụng công nghệ, vì vậy trong bài viết này, mình sẽ hướng dẫn các bạn cách tạo một Accordion chỉ với HTML và CSS, tận dụng thêm một chút tính năng của React trong Next.js.

Tại sao Accordion thường cần JavaScript?

Accordion về cơ bản là một thành phần có trạng thái (state), nghĩa là nó yêu cầu khả năng lưu trạng thái mở và đóng. Mặc dù HTML và CSS là rất hữu ích, nhưng chúng không phải là ngôn ngữ lập trình, do đó chúng không thể lưu trạng thái. Thông thường, đây là lý do mà chúng ta phải dùng JavaScript để xử lý các thành phần này.

Giải pháp thay thế cho trạng thái mà không cần JavaScript

Trước khi JavaScript trở nên phổ biến, chúng ta vẫn có thể nhập dữ liệu lên trang web thông qua các thẻ input, mà thực tế chính là cách lưu trữ trạng thái. Ví dụ:

  • <input type="checkbox">: Có thể lưu trữ trạng thái boolean (true hoặc false).
  • <input type="radio">: Cho phép lựa chọn trong một nhóm (enum).
  • Các thẻ input khác như number, text cũng giúp lưu trữ trạng thái số và chữ.

Như vậy, chúng ta hoàn toàn có thể tạo ra một accordion chỉ bằng một thẻ input checkbox.

Cấu tạo của Accordion

Accordion bao gồm hai phần: phần tiêu đề (heading) và phần nội dung (body). Tiêu đề sẽ chứa thông tin tóm tắt và nội dung là phần chi tiết mà người dùng có thể mở hoặc đóng.

Cách cài đặt Accordion

Để tạo Accordion, chúng ta sẽ sử dụng một thẻ label, trong đó trỏ tới input checkbox, ẩn input đi để người dùng không thấy. Dưới đây là cách làm:

html Copy
<label htmlFor={inputId} className="w-full block">
    <span>{heading}</span>
    <input type="checkbox" id={inputId} name="switch" hidden />
</label>

Phần nội dung của accordion sẽ được đặt trong một thẻ div liền kề với thẻ label, với chiều cao mặc định là 0 và overflow ẩn để ẩn đi:

html Copy
<div className="w-full h-0 overflow-hidden">
    {body}
</div>

Toàn bộ mã HTML sẽ được bọc trong một thẻ div để dễ dàng tạo thành một component tái sử dụng:

html Copy
<div>
    <label htmlFor={inputId} className="w-full block">
        <span>{heading}</span>
        <input type="checkbox" id={inputId} name="switch" hidden />
    </label>

    <div className="w-full h-0 overflow-hidden">
        {body}
    </div>
</div>

Để bắt trạng thái của input, chúng ta sẽ sử dụng pseudo class :has:checked của CSS:

html Copy
<div>
    <label htmlFor={inputId} className="w-full block">
        <span>{heading}</span>
        <input type="checkbox" id={inputId} name="switch" hidden />
    </label>

    <div className="w-full h-0 overflow-hidden [*:has([name=switch]:checked)>&]:h-auto">
        {body}
    </div>
</div>

Diễn giải: Nếu phần tử cha của nội dung chứa một element có nameswitch và có trạng thái checked, chúng ta sẽ áp dụng class h-auto để hiển thị.

Tuy nhiên, khi sử dụng height, mà không có chiều cao sẽ không thể animate từ trạng thái không có chiều cao (height: 0) sang chiều cao tự động (height: auto). Do đó, thay vì sử dụng height, chúng ta sẽ dùng max-height:

html Copy
<div>
    <label htmlFor={inputId} className="w-full block">
        <span>{heading}</span>
        <input type="checkbox" id={inputId} name="switch" hidden />
    </label>

    <div className="w-full max-h-0 overflow-hidden [*:has([name=switch]:checked)>&]:max-h-max">
        {body}
    </div>
</div>

Tuy nhiên, để có thể thực hiện animation, max-height cần được chỉ định một giá trị cụ thể. Chúng ta có thể tùy chỉnh các class của body và heading để linh hoạt hơn:

javascript Copy
import clsx from "clsx";
import { ReactNode, useId } from "react";

interface IProps {
  rootCls?: string;
  heading: string | ReactNode;
  headingCls?: string;
  body: string | ReactNode | ReactNode[];
  bodyCls?: string;
}

export function Accordion({
  rootCls,
  heading,
  headingCls,
  body,
  bodyCls,
}: IProps) {
  const id = useId();
  const inputId = `accordions.${id}`;

  return (
    <div className={rootCls}>
      <label htmlFor={inputId} className={clsx("w-full block", headingCls)}>
        <span>{heading}</span>
        <input type="checkbox" id={inputId} name="switch" hidden />
      </label>

      <div
        className={clsx(
          "w-full max-h-0 overflow-hidden [*:has([name=switch]:checked)>&]:max-h-max",
          bodyCls,
        )}
      >
        {body}
      </div>
    </div>
  );
}

Tuy nhiên, nếu điều kiện như hiện tại, khi component khác gọi đến Accordion sẽ cần phải xác định cả max-height kèm theo selector dài. Vì vậy, ta sẽ đảo điều kiện bằng pseudo class :not:

html Copy
<div
    className={clsx(
        "w-full max-h-max overflow-hidden [*:not(:has([name=switch]:checked))>&]:max-h-0",
        bodyCls,
    )}
  >
    {body}
</div>

Cuối cùng, Accordion sẽ được sử dụng trong một component khác như sau:

javascript Copy
import { Accordion } from "@/components/Accordion";

export default function Home() {
    return (
        <main>
            <Accordion
                rootCls="max-w-screen-sm"
                heading="Nhấn để mở rộng"
                body="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
                bodyCls="transition-all duration-200 max-h-56"
            />
        </main>
    );
}

Cuối cùng, với việc thực hiện như trên, Accordion của chúng ta đã hoàn tất và có thể hoạt động mà không cần JavaScript, đồng thời rất tối ưu cho SEO.

Kết luận

Hy vọng bài viết này đã giúp các bạn hiểu rõ hơn về cách tạo Accordion chỉ với HTML và CSS. Nếu bài viết nhận được sự quan tâm, mình sẽ tiếp tục chia sẻ thêm những dự án như dropdown menu và carousel.
source: viblo

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào