Chọn kỹ thuật biểu tượng
Lưu trữ và kết xuất biểu tượng là hai lựa chọn riêng biệt. Ví dụ: một sprite SVG có thể cung cấp biểu tượng cho hệ thống biểu tượng CSS hoặc được tham chiếu bằng <use>.
- Hệ thống biểu tượng CSS: Sử dụng khi CSS nên chọn thời điểm và biểu tượng cần hiển thị. Sử dụng mặt nạ cho biểu tượng đơn sắc hoặc hình nền cho biểu tượng có màu.
- SVG có
<use>: Dùng để sử dụng lại một biểu tượng khi các tuỳ chọn tạo kiểu tích hợp sẵn của biểu tượng đó là đủ. CSS không thể tạo kiểu hoặc tạo ảnh động tuỳ ý cho nội dung của SVG được tham chiếu, nhưng các thuộc tính kế thừa và thuộc tính tuỳ chỉnh có thể được truyền vào khi biểu tượng được thiết kế để sử dụng các thuộc tính đó. Ví dụ: một đường dẫn có thể sử dụngfill="var(--icon-accent, currentColor)"để cho phép CSS tô màu phần đó của biểu tượng. - SVG cùng dòng: Sử dụng khi bạn cần tạo kiểu hoặc ảnh động tuỳ ý cho nội dung của SVG.
<img>có bộ lọc CSS: Tránh dùng cách này để đổi màu biểu tượng. Bộ lọc không thể áp dụng một cách đáng tin cậy giá trị màu CSS tuỳ ý cho hình ảnh, vì vậy, việc đổi màu sẽ rườm rà và không linh hoạt.
Để quyết định, hãy đặt hai câu hỏi. CSS có cần chọn hoặc hoán đổi biểu tượng (mặc định của thành phần, thay đổi trạng thái, giao diện) không? Nếu có, hãy sử dụng hệ thống biểu tượng CSS. Bạn có cần truy cập vào bên trong biểu tượng ngoài màu sắc không? Nếu có và bạn kiểm soát nguồn biểu tượng, hãy thiết kế biểu tượng bằng các thuộc tính tuỳ chỉnh và sử dụng <use>; nếu bạn không kiểm soát nguồn, hãy chèn SVG cùng dòng. Các bộ biểu tượng của bên thứ ba hiếm khi hiển thị các thuộc tính tuỳ chỉnh, vì vậy, việc sử dụng <use> cho các bộ biểu tượng này chỉ giúp bạn có được màu sắc và một số ít thuộc tính khác.
Biểu tượng được nhúng hoặc biểu tượng từ xa
Cả hệ thống biểu tượng CSS và <use> đều có thể lấy biểu tượng từ một URI dữ liệu, một tệp riêng lẻ hoặc một sprite. Việc nhúng (URI dữ liệu hoặc SVG cùng dòng) giúp tránh yêu cầu và hiển thị trong lần hiển thị đầu tiên, nhưng làm tăng kích thước CSS hoặc HTML và không thể lưu vào bộ nhớ đệm riêng biệt, vì vậy, việc này phù hợp với một nhóm nhỏ biểu tượng xuất hiện trên mọi trang. Các tệp riêng lẻ được lưu vào bộ nhớ đệm một cách độc lập và được tải theo yêu cầu, với chi phí là một yêu cầu cho mỗi tệp. Sprite là một yêu cầu có thể lưu vào bộ nhớ đệm cho toàn bộ nhóm, nhưng tải mọi biểu tượng xuống cho dù có được sử dụng hay không và <use> chỉ có thể tham chiếu đến một sprite từ cùng một nguồn gốc. Nên nhúng một số biểu tượng cốt lõi và một tệp sprite hoặc các tệp riêng lẻ cho các nhóm lớn hơn.
Nếu một trang hiển thị hàng nghìn biểu tượng cùng một lúc, hãy đo điểm chuẩn cho trang và thành phần thực tế. Trong một bài kiểm thử áp lực, SVG cùng dòng có lợi thế nhỏ về hiệu suất ở quy mô đó, nhưng kết quả phụ thuộc vào trình duyệt và cách triển khai. Kiểm thử áp lực biểu tượng SVG của Cloud Four
Phương pháp chính: Công cụ biểu tượng dựa trên CSS
Biểu tượng là mã thông báo thiết kế: mỗi biểu tượng là một thuộc tính tuỳ chỉnh chứa hình ảnh và mọi phần tử đều có thể hiển thị một biểu tượng bằng cách đặt --icon-start hoặc --icon-end. Công cụ này kết hợp 3 tính năng CSS:
- Các thuộc tính tuỳ chỉnh đã đăng ký (
@property) ngăn--icon-startvà--icon-endkế thừa, vì vậy, biểu tượng được đặt trên một thành phần sẽ không được áp dụng cho các thành phần con của thành phần đó. - Truy vấn kiểu vùng chứa (
@container style(...)) phát hiện thời điểm một phần tử được chỉ định biểu tượng và hiển thị biểu tượng đó mà không có thay đổi về lớp hoặc mã đánh dấu. - Mặt nạ CSS và
currentColorhiển thị biểu tượng trong một phần tử giả theo màu văn bản của phần tử. Kích thước, màu sắc, khoảng cách và độ lệch dọc được đọc từ các thuộc tính--icon-style-*có thể kế thừa với các phương án dự phòng hợp lý, vì vậy, toàn bộ cây con có thể được tạo kiểu lại từ một đối tượng cấp trên. Tiền tố--icon-style-giúp các chế độ cài đặt này không xung đột với chính mã thông báo biểu tượng, cũng nằm trong--icon-*.
Xác định mã thông báo thiết kế biểu tượng
Xác định biểu tượng SVG và mọi giá trị ghi đè --icon-style-* dưới dạng thuộc tính tuỳ chỉnh có thể sử dụng lại bằng cách sử dụng URI dữ liệu SVG cùng dòng hoặc URL tương đối gốc (ví dụ: url("/icons/trash.svg")). Khi một biểu tượng được tải từ tệp SVG bên ngoài, hãy sử dụng URL tương đối gốc hoặc URI dữ liệu được nhúng. Một URL tương đối được sử dụng thông qua một thuộc tính tuỳ chỉnh có thể phân giải tương ứng với biểu định kiểu nơi biểu tượng được sử dụng, thay vì biểu định kiểu nơi thuộc tính tuỳ chỉnh được xác định. Nếu các biểu định kiểu đó nằm trong các thư mục khác nhau, thì URL có thể phân giải thành tệp không chính xác.
Để giữ cho URL tương ứng với biểu định kiểu xác định mã thông báo biểu tượng, một thuộc tính đã đăng ký sẽ giúp: giá trị được tính toán của <url> hoặc <image> đã đăng ký được phân giải dựa trên biểu định kiểu đã khai báo. KHÔNG dựa vào việc đăng ký mã thông báo dưới dạng <image> bằng URL trong initial-value. Chrome và Safari phân giải URL initial-value dựa trên tài liệu, vì vậy, URL này chỉ hoạt động trong Firefox. Chrome cũng phân giải các giá trị được khai báo trên :root dựa trên tài liệu, trừ phi cú pháp bắt đầu bằng <url>.
Khi không thể tránh khỏi URL tương đối, chẳng hạn như biểu định kiểu trong các thư mục khác nhau, hãy đăng ký mã thông báo bằng cú pháp bắt đầu bằng <url> và khai báo URL thực tế trên :root. Điều này phân giải dựa trên biểu định kiểu khai báo trong mọi trình duyệt:
@property --icon-caret {
/* The syntax must start with <url>. A syntax that starts with <image> resolves against the document in Chrome. */
syntax: "<url> | <image> | none";
inherits: true;
initial-value: none;
}
:root {
/* Declared here, the URL resolves against this stylesheet */
--icon-caret: url("icons/caret.svg");
}
Mỗi biểu tượng hiện nằm ở hai nơi và có nhiều mã nguyên mẫu hơn, vì vậy, cách này chỉ đáng giá khi một dự án có biểu định kiểu trong các thư mục khác nhau và số lượng biểu tượng có thể quản lý được. Sau khi có khoảng mười biểu tượng, hãy đăng ký mã thông báo --icon-* (ngoại trừ --icon-style-*) từ một tập lệnh có CSS.registerProperty, sử dụng cùng một cú pháp, thay vì viết từng @property theo cách thủ công. Các thuộc tính đã đăng ký là thuộc tính chung và không hoạt động trong gốc bóng, vì vậy, thuộc tính này có thể không phù hợp với mọi dự án. Nếu có thể, hãy dùng URL tương đối gốc hoặc URI dữ liệu vì chúng đơn giản hơn và hoạt động ở mọi nơi.
:root {
/* Optional: override the built-in fallbacks (1em, currentColor, 0.3em, -0.02em) */
--icon-style-size: 1em;
--icon-style-spacing: 0.4em;
--icon-trash: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Cpath d='M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0-1 14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2L4 6h16Z'/%3E%3C/svg%3E");
--icon-favorite: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z'/%3E%3C/svg%3E");
}
Hiển thị biểu tượng từ mã thông báo
Một phần tử sẽ hiển thị biểu tượng bằng cách đặt --icon-start (đầu) hoặc --icon-end (cuối) thành một trong các mã thông báo ở trên. Hai đoạn CSS biến nhiệm vụ đó thành một biểu tượng được kết xuất.
Trước tiên, hãy đăng ký --icon-start và --icon-end bằng @property. Cú pháp "*" chấp nhận mọi giá trị và inherits: false là phần quan trọng: nếu không có cú pháp này, một biểu tượng được đặt trên nút sẽ kế thừa vào mọi thành phần con và mỗi thành phần con cũng sẽ hiển thị biểu tượng đó.
@property --icon-start {
syntax: "*";
inherits: false;
}
@property --icon-end {
syntax: "*";
inherits: false;
}
Thứ hai, truy vấn kiểu vùng chứa sẽ phát hiện thời điểm thuộc tính được đặt trên một phần tử và hiển thị biểu tượng trong một phần tử giả. Vì phần tử giả không kế thừa --icon-start (được đăng ký là không kế thừa), nên nội dung khai báo đầu tiên sẽ chuyển tiếp giá trị một cách rõ ràng bằng inherit. Các nội dung khai báo còn lại xử lý việc định cỡ, tạo mặt nạ, căn chỉnh và giãn cách. Vì màu nền mặc định là currentColor, nên biểu tượng sẽ sử dụng màu văn bản của phần tử và tuân theo các hiệu ứng chuyển đổi của phần tử đó.
/* Style query for elements configuring a start icon */
@container style(--icon-start) {
::before {
--icon-start: inherit; /* Forward the value inside the query block */
content: "" / var(--icon-style-alt, "");
display: inline-block;
width: var(--icon-style-size, 1em);
height: var(--icon-style-size, 1em);
background-color: var(--icon-style-color, currentColor);
mask: var(--icon-start) no-repeat center / contain;
/* Center on the cap height, nudged slightly up for optical balance */
vertical-align: calc(0.5cap - var(--icon-style-size, 1em) / 2 + var(--icon-style-voffset, -0.02em));
flex: none;
pointer-events: none;
forced-color-adjust: none;
}
/* Automatically add spacing ONLY if the element has other text or sibling content */
:where(:not(.icon, :empty))::before {
margin-inline-end: var(--icon-style-spacing, 0.3em);
}
}
Mỗi nội dung khai báo đều có vị trí riêng:
vertical-align:middlecăn chỉnh theo một nửa chiều cao x, tức là thấp hơn một cách rõ rệt so với hầu hết các bộ biểu tượng. Việc căn giữa trên0.5capgiúp tâm của biểu tượng luôn cố định khi--icon-style-sizetăng lên, còn--icon-style-voffsetâm nhỏ sẽ bù cho việc mắt người dùng chú ý đến không gian phía trên biểu tượng nhiều hơn không gian phía dưới.flex: none: Một phần tử giả trống có kích thước tối thiểu bằng 0, vì vậy, bên trong nút linh hoạt, đây là phần tử đầu tiên thu nhỏ khi nhãn dài, có thể là không có gì. Kích thước biểu tượng là một quyết định thiết kế, chứ không phải là điều mà áp lực bố cục nên thương lượng.pointer-events: none: Ngăn biểu tượng chặn lượt nhấp hoặc trở thànhevent.target.forced-color-adjust: none: Ở chế độ buộc màu, trình duyệt sẽ thay thếbackground-color, khiến biểu tượng có mặt nạ biến mất. Màu nền của mặt nạ được giữ nguyên vàcurrentColorvẫn tuân theo màu văn bản bắt buộc.content: "" / var(--icon-style-alt, ""): Văn bản thay thế cho nội dung được tạo cho phép một thành phần điều khiển chỉ có biểu tượng lấy tên thành phần hỗ trợ tiếp cận từ biểu tượng thông qua--icon-style-alt(xem phần Thành phần điều khiển chỉ có biểu tượng bên dưới). Thuộc tính alt trống mặc định giúp ẩn các biểu tượng trang trí khỏi công nghệ hỗ trợ.
Đối với biểu tượng ở cuối, hãy làm theo cùng một mẫu với --icon-end và ::after thay vì --icon-start và ::before. Sử dụng mask: var(--icon-end) và áp dụng khoảng cách bằng margin-inline-start thay vì margin-inline-end.
Thành phần độc lập và chỉ có biểu tượng
Đối với các biểu tượng độc lập hoặc nút chỉ có biểu tượng, hãy áp dụng .icon trực tiếp cho phần tử trống (chính nút đó cho thành phần điều khiển chỉ có biểu tượng) và đặt biểu tượng của phần tử đó bằng --icon. Ở đây, --icon là thuộc tính cấp cao hơn; quy tắc .icon:empty liên kết thuộc tính này với --icon-start cho biểu tượng ở đầu:
.icon:empty {
--icon-start: var(--icon);
}
Ví dụ về cách dùng
Giá trị mặc định của thành phần trong biểu định kiểu
Lợi ích chính của biểu tượng dưới dạng thuộc tính tuỳ chỉnh là chúng tham gia vào tầng. Đặt biểu tượng trên bộ chọn và mọi phần tử phù hợp đều nhận được biểu tượng đó mà không cần thay đổi mã đánh dấu:
.button-delete {
--icon-start: var(--icon-trash);
}
a[href^="http"]:not([href*="example.com"]) {
--icon-end: var(--icon-external);
}
<button class="button-delete">Delete item</button>
Thay đổi biểu tượng theo trạng thái
Vì biểu tượng chỉ là một thuộc tính, nên việc thay đổi trạng thái là một cơ chế ghi đè CSS thông thường. Không có phần tử thứ hai để hiện hoặc ẩn và không có lớp nào để bật/tắt:
.button-expand {
--icon-start: var(--icon-caret-right);
}
.button-expand[aria-expanded="true"] {
--icon-start: var(--icon-caret-down);
}
Điều tương tự cũng áp dụng cho bản trình bày: đặt --icon-style-size, --icon-style-color hoặc --icon-style-spacing trên một vùng chứa, chẳng hạn như thanh công cụ và mọi biểu tượng bên trong thanh công cụ đó đều cập nhật.
Biểu tượng một lần có kiểu cùng dòng
Một phần tử không thuộc phạm vi của quy tắc thành phần nào có thể đặt cùng một thuộc tính cùng dòng, ví dụ: <button style="--icon-start: var(--icon-trash)">. Hãy coi đây là lối thoát thay vì mặc định: khi cùng một biểu tượng xuất hiện trên cùng một loại phần tử nhiều lần, hãy chuyển biểu tượng đó vào quy tắc biểu định kiểu.
Chỉ có biểu tượng điều khiển
Các nút chỉ có biểu tượng sử dụng lớp .icon và --icon được mô tả ở trên. Đặt tên thành phần hỗ trợ tiếp cận cho nút bằng aria-label hoặc để biểu tượng cung cấp tên thông qua --icon-style-alt; nếu không có cả hai, biểu tượng do CSS tạo sẽ chỉ mang tính trang trí. Đối với các biểu tượng thông tin độc lập, hãy hiển thị phần tử .icon bằng role="img" và aria-label:
.button-close {
--icon: var(--icon-close);
--icon-style-alt: "Close";
}
<!-- Icon-only interactive control, icon and name from the stylesheet -->
<button class="icon button-close"></button>
<!-- Icon-only interactive control, named via aria-label -->
<button class="icon" style="--icon: var(--icon-trash);" aria-label="Delete item"></button>
<!-- Standalone informative icon -->
<span class="icon" style="--icon: var(--icon-favorite);" role="img" aria-label="Favorite"></span>
Làm việc trực tiếp với SVG
Khi hệ thống biểu tượng CSS không phù hợp, hãy sử dụng SVG cùng dòng và <use> cùng nhau thay vì dùng làm giải pháp thay thế. Chèn SVG vào cùng dòng khi biểu tượng xuất hiện lần đầu tiên trên một trang và tham chiếu biểu tượng đó bằng <use href="#id"> cho mỗi lần lặp lại; tham chiếu cục bộ không tốn yêu cầu và không có quy định hạn chế về nhiều nguồn gốc. Dành riêng một sprite bên ngoài (<use href="/icons.svg#id">) cho các biểu tượng được dùng chung trên nhiều trang.
Với <use>, nội dung được tham chiếu sẽ được sao chép vào cây bóng, vì vậy, CSS của trang không thể chọn các phần tử của nội dung đó. Những thuộc tính được kế thừa là: color, currentColor và mọi thuộc tính tuỳ chỉnh. Ngoài các thuộc tính trình bày SVG, CSS hiện đại cũng có thể thay đổi nhiều thuộc tính hình học, vì vậy, biểu tượng mà bạn kiểm soát có thể hiển thị chính xác các điểm nối mà bạn cần, ví dụ: stroke-width="var(--icon-stroke, 2)" hoặc thậm chí là d: var(--icon-path, path(…)) trên một đường dẫn cụ thể.
Safari không hỗ trợ CSS d, vì vậy, chỉ sử dụng chế độ tuỳ chỉnh này khi bạn chấp nhận được việc không hỗ trợ này. Nếu bạn không kiểm soát biểu tượng, hãy đặt biểu tượng cùng dòng.
<use> điều cần lưu ý:
viewBoxphải nằm trên<symbol>hoặc<svg>được tham chiếu;<svg>bên ngoài gói<use>không lấy từ tham chiếu, vì vậy, hãy đặtwidthvàheighttrên trình bao bọc và giữviewBoxbằng biểu tượng.- Kiểu không vượt qua ranh giới theo cả hai hướng:
<style>bên trong một sprite bên ngoài không áp dụng cho trang và biểu định kiểu trang không áp dụng bên trong cây được tham chiếu. Chỉ các thuộc tính kế thừa và thuộc tính tuỳ chỉnh mới được chuyển qua. - Tham chiếu bên ngoài chỉ có cùng nguồn gốc; các sprite khác nguồn gốc sẽ không hoạt động mà không có thông báo.
- Các sprite được chèn cùng dòng vào tài liệu sẽ dùng chung không gian tên
idcủa trang, vì vậy, hãy đặt mã nhận dạng biểu tượng không gian tên để tránh xung đột.
Đánh dấu SVG trang trí bằng aria-hidden="true" và đặt tên thành phần hỗ trợ tiếp cận cho thành phần điều khiển chứa SVG, giống như với hệ thống biểu tượng CSS.
Chiến lược dự phòng
Thuộc tính tuỳ chỉnh đã đăng ký
Nếu không có @property, --icon-start và --icon-end sẽ hoạt động như các thuộc tính tuỳ chỉnh kế thừa thông thường, vì vậy, một biểu tượng được đặt trên một phần tử cũng sẽ hiển thị trên các phần tử con của phần tử đó. Bạn có thể ngăn chặn điều này bằng cách đặt lại các thuộc tính trên phần tử con bên trong truy vấn kiểu (@container style(--icon-start) { * { --icon-start: initial; } }), đây cũng là cách giải quyết của Safari dưới đây.
Truy vấn kiểu vùng chứa
Để hỗ trợ các trình duyệt không có Truy vấn kiểu vùng chứa, hãy sử dụng các lớp hoặc bộ chọn thành phần làm hook dự phòng. Giữ lại lựa chọn biểu tượng trong các thuộc tính tuỳ chỉnh; chỉ riêng bộ chọn kiểu cùng dòng sẽ bỏ lỡ các biểu tượng được chỉ định thông qua quy tắc CSS.
:where(.icon-start, .icon:empty)::before {
content: image-set(linear-gradient(transparent 0 100%), var(--icon-start)) / var(--icon-style-alt, "");
/* icon styles */
}
:where(.icon-start)::before {
--icon-start: inherit;
}
:where(.icon:empty)::before {
--icon-start: var(--icon);
}
:where(.icon-start:not(.icon, :empty))::before {
margin-inline-end: var(--icon-style-spacing, 0.3em);
}
Đối với biểu tượng ở cuối, hãy sử dụng cùng một phương án dự phòng cho lớp móc nối với .icon-end, --icon-end và ::after thay vì .icon-start, --icon-start và ::before. Áp dụng khoảng cách cho nội dung không trống bằng margin-inline-start thay vì margin-inline-end.
Mẫu image-set() này khiến content không hợp lệ khi thuộc tính tuỳ chỉnh không được đặt, vì vậy, thuộc tính này sẽ quay lại none và phần tử giả sẽ biến mất mà không cần thêm hoặc xoá lớp. Lớp này không xoá các kiểu khác của phần tử giả hoặc ngăn xung đột với các quy tắc ::before hoặc ::after khác, vì vậy, chỉ sử dụng các hook này khi các phần tử giả đó có sẵn cho biểu tượng.
Cách giải quyết cho trình duyệt Safari
Safari có hai lỗi kết xuất ảnh hưởng đến biểu tượng: WebKit #301609 có thể ngăn biểu tượng xuất hiện cho đến khi người dùng tương tác và #320220 cũng ảnh hưởng đến quá trình kết xuất biểu tượng. Giải pháp dưới đây giải quyết cả hai vấn đề:
@keyframes webkit-301609 {}
@supports ((-webkit-nbsp-mode: normal) or (-webkit-touch-callout: none) or (font: -apple-system-body)) and (content-visibility: auto) {
@layer webkit-301609-fix {
::before, ::after {
animation: webkit-301609 0s;
}
}
}
Truy vấn @supports chỉ nhắm đến Safari và @layer giảm xung đột ảnh động.
Lỗi WebKit #325900 có thể ngăn việc ghi đè trực tiếp --icon-start có hiệu lực trong một số trường hợp, chẳng hạn như thay đổi biểu tượng khi di chuột. Điều này ảnh hưởng đến giải pháp cho các thuộc tính đã đăng ký không kế thừa. Đối với Safari, hãy đăng ký --icon-start dưới dạng kế thừa và đặt lại thuộc tính này trên các phần tử con bên trong truy vấn kiểu:
@supports ((-webkit-nbsp-mode: normal) or
(-webkit-touch-callout: none) or
(font: -apple-system-body)) and (content-visibility: auto) {
/* Override previous declaration */
@property --icon-start {
syntax: "*";
inherits: true;
}
@layer webkit-325900-fix {
@container style(--icon-start) {
* {
--icon-start: initial;
}
}
}
}
Thao tác đặt lại sẽ ngăn giá trị biểu tượng truyền đến các thành phần con. Việc giữ thuộc tính này trong một lớp sẽ giúp các nội dung khai báo biểu tượng không phân lớp được ưu tiên hơn so với việc đặt lại.