Sprite CSS: Khi nào vẫn hữu ích và cách tạo
Phần lớn hướng dẫn sprite CSS vẫn mở bằng câu năm 2009: giảm HTTP nên nhanh. Lập luận dựa giới hạn HTTP/1.1 không còn trên máy chủ mới. Đây là bản trung thực: lý do thật còn dùng, lý do không và phép tính chính xác background-position và phép tính retina để xây dựng một bản hoạt động.
CSS sprite thực sự là gì
Sprite CSS là một tệp ảnh chứa nhiều đồ họa riêng, cộng CSS chỉ hiển thị một hình mỗi lần. Không có thao tác cắt ảnh hay JavaScript. Phần tử được gán một giá trị cố định width và height, toàn bộ bảng được đặt làm background-image, và background-position dịch sheet phía sau phần tử để biểu tượng cần dùng nằm trong hộp hiển thị. Hộp là cửa sổ; sprite sheet là tờ giấy lớn được kéo phía sau.
Chi tiết làm người mới vướng là độ lệch phải âm. Bạn không di chuyển cửa sổ đến biểu tượng, mà di chuyển bảng để biểu tượng đến cửa sổ. Để hiển thị biểu tượng có góc trên trái cách cạnh trái bảng 96 pixel, bạn đẩy bảng sang trái 96 pixel, tức background-position: -96px 0.
Ví dụ cụ thể: bạn xếp năm biểu tượng 24×24 thành một hàng, với khoảng cách 8 pixel giữa chúng, bắt đầu cách trái 8 pixel và cách trên 8 pixel. Bảng có kích thước 160×32. Cạnh trái biểu tượng thứ ba ở 8 + 24 + 8 + 24 + 8 = 72 pixel, cạnh trên ở 8 pixel. Quy tắc là:
| Khai báo | Chức năng |
|---|---|
| background-image: url("/sprite.png") | Tải toàn bảng sau phần tử |
| background-repeat: no-repeat | Ngăn sheet lặp và lộ biểu tượng khác ở mép |
| background-position: -72px -8px | Dịch sheet trái 72px, lên 8px để biểu tượng ba tại gốc |
| width: 24px; height: 24px | Cắt cửa sổ đúng một biểu tượng — đây là phần che phần còn lại |
| display: inline-block | Cho phần tử nội dòng như span thực tuân thủ rộng/cao |
Chỉ thay hai con số trong background-position và bạn nhận một biểu tượng khác từ cùng tệp. Đó là toàn bộ cơ chế.

Nói thật: HTTP/2 đã đổi phép tính
Sprite CSS ra đời để khắc phục một giới hạn cụ thể. Với HTTP/1.1, trình duyệt mở ít kết nối TCP cho mỗi nguồn, thường sáu, và mỗi kết nối chỉ mang một yêu cầu mỗi lần. Bốn mươi tệp biểu tượng nghĩa bốn mươi yêu cầu xếp hàng qua sáu đường, mỗi yêu cầu chịu độ trễ thiết lập kết nối và khứ hồi. Gộp thành một tệp biến bốn mươi yêu cầu nối tiếp thành một. Trên kết nối độ trễ cao, đây không phải tối ưu nhỏ mà thường là cải thiện lớn nhất có thể trên trang.
HTTP/2 ghép kênh. Nhiều yêu cầu cùng chia một kết nối, không chặn đầu hàng ở lớp HTTP, header nén giữa yêu cầu. Vấn đề cụ thể sprite được tạo để sửa phần lớn biến mất. Nếu hướng dẫn 2026 nói sprite nhanh vì giảm số yêu cầu rồi dừng, nó được viết cho web không còn tồn tại.
Vậy tại sao có bài này? Vì “lý do ban đầu biến mất” không đồng nghĩa “không còn lý do”. Một số lợi ích thực vẫn tồn tại qua ghép kênh, đáng dùng để quyết định:
- Chi phí phụ trên mỗi yêu cầu không giảm về không. Yêu cầu ghép kênh rẻ, không miễn phí. Mỗi cái còn header, tra cache, vị trí trong lịch tài nguyên. Năm biểu tượng là nhiễu nhỏ; hai trăm nhỏ đo được, sprite thu thành một mục.
- Quản lý cache đơn giản hơn. Một tệp nghĩa là một mục bộ nhớ đệm, một Cache-Control chính sách, một tên tệp băm để vô hiệu hóa. Hai trăm tệp biểu tượng có phiên bản riêng tạo thêm phạm vi dựng và triển khai phải quản lý, nhưng thường không được quản lý.
- Quản lý phiên bản nguyên khối. Mọi biểu tượng được cung cấp cùng nhau, nên không thể có bộ biểu tượng cập nhật dở dang với ba biểu tượng lấy kiểu cũ từ cache còn phần khác mới. Với triển khai hệ thống thiết kế, đây là tính đúng đắn thực sự, không chỉ gọn gàng.
- Không nhấp nháy mất biểu tượng. Khi bảng tải xong, mọi biểu tượng trong đó có ngay lập tức. Các biểu tượng tải riêng xuất hiện lần lượt khi từng yêu cầu hoàn tất; điều này dễ thấy nhất đúng ở nơi gây khó chịu nhất: thanh công cụ hoặc lưới biểu tượng được hiển thị trong vùng nhìn thấy ngay khi mở trang.
- Thay đổi trạng thái không độ trễ. Trạng thái rê chuột hoặc kích hoạt ở nơi khác trên cùng bảng đã được tải xuống. Với tệp riêng, ảnh rê chuột thường chỉ được yêu cầu ở lần rê đầu, tạo nhấp nháy trống khi người dùng chạm nút lần đầu. Đây luôn là một trong những lập luận mạnh nhất cho sprite và HTTP/2 không làm nó yếu đi.
Khi không nên dùng CSS sprite
Khuyến nghị đáng tin phải bao gồm cả trường hợp câu trả lời là không, và với sprite CSS có một số trường hợp như vậy.
- Biểu tượng đơn sắc co giãn nên dùng SVG. Một phần tử nội dòng <svg> hoặc SVG sprite được dựng từ <symbol> và <use> phóng thu đến mọi kích thước mà không cần tính retina, và kế thừa màu qua currentColor. Với bộ biểu tượng giao diện thông thường, đây đơn giản là công cụ tốt hơn, và đó là lý do bảng sprite đã âm thầm mất phần lớn thị phần trong hệ thống biểu tượng.
- Một biểu tượng đơn lẻ không bao giờ đáng để tạo bảng sprite. Nếu chỉ một ảnh, dùng một ảnh. Sprite thêm ghi tọa độ và bước build không có lợi.
- Biểu tượng thay độc lập. Quản lý phiên bản nguyên khối có hai mặt. Nếu một biểu tượng đổi, toàn bộ cache của bảng bị vô hiệu và mọi người tải lại tất cả. Bộ biểu tượng thay đổi thường xuyên không phù hợp với sprite.
- Nội dung cần đổi màu hoặc đổi chủ đề. Sprite raster cố định màu sẵn trong ảnh. Chế độ tối, giao diện thương hiệu và màu trạng thái đều cần bảng thứ hai hoặc thủ thuật lọc dễ hỏng. Đây là lợi thế cấu trúc rõ nhất của SVG.
- Biểu tượng phải theo cỡ phông. Sprite cố định theo kích thước pixel. Nếu biểu tượng cần co giãn theo em cùng với chữ, phương pháp vector xử lý được còn sprite sẽ gây khó khăn.
Sprite, SVG sprite, SVG nội tuyến hoặc phông biểu tượng
| Cách tiếp cận | Co giãn sắc nét | Đổi màu bằng CSS | Yêu cầu | Bộ nhớ đệm | Khả năng tiếp cận |
|---|---|---|---|---|---|
| Sprite CSS (raster) | Không — pixel cố định, cần bảng 2x | Không, chỉ thủ thuật bộ lọc | Một cho mọi biểu tượng | Rất tốt, một tệp dùng lâu | Ảnh nền vô hình với công nghệ hỗ trợ — cần nhãn chữ |
| SVG sprite (symbol + use) | Có, mọi kích thước | Có, qua currentColor | Một cho mọi biểu tượng | Rất tốt, một tệp | Trong DOM, hỗ trợ title và ARIA |
| SVG nội dòng cho mỗi biểu tượng | Có, mọi kích thước | Có, kiểm soát hoàn toàn bằng CSS | Không có, nhúng trong HTML | Không có — gửi lại cùng mỗi trang | Tốt nhất, hoàn toàn trong DOM |
| Phông biểu tượng | Có, thay đổi theo font-size | Có, qua color | Một tệp phông chữ | Tốt | Kém — ký tự bị đọc thành tiếng hoặc thay bằng phông dự phòng |
Đọc bảng đó như cách quyết định, không phải bảng xếp hạng. Hình raster nhiều màu như cờ, logo, huy hiệu minh họa và ảnh biểu tượng ứng dụng không thể làm SVG sprite một cách hợp lý. Đây là nơi CSS sprite vẫn là đáp án đúng, không phải giải pháp lỗi thời.
Tạo một
Bốn bước, chỉ bước ba khó làm tay.
- Thu thập các biểu tượng. Xuất ở kích thước hiển thị cuối hoặc gấp đôi, xem phần retina. Giữ kích thước nhất quán khi có thể; lưới đều giúp tính tọa độ đơn giản và CSS đều.
- Đóng gói vào một sheet. Một hàng đơn hoặc lưới cố định dễ hiểu hơn; công cụ đóng gói lấp không gian tốt hơn khi kích thước khác nhau. Chừa khoảng cách giữa các biểu tượng.
- Đọc tọa độ. Mỗi biểu tượng cần x, y, width, height theo pixel bảng. Làm tay trong editor dễ sai vì đọc lệch một pixel tạo mảnh biểu tượng bên cạnh mà không gì giải thích.
- Viết CSS. Một quy tắc nền chung và một quy tắc nhỏ cho mỗi biểu tượng.
Quy tắc nền chung quan trọng hơn vẻ ngoài. Mọi biểu tượng cần cùng background-image, background-repeat: no-repeat, và display: inline-block. Lặp lại background-image URL trong 50 quy tắc không vấn đề performance, chỉ lấy một lần, nhưng bảo trì: đổi tên cache phải sửa 50 nơi thay một. Mẫu là lớp nền có chung, lớp icon chỉ vị trí/cỡ:
| Quy tắc | Nội dung |
|---|---|
| .sprite | background-image, background-repeat: no-repeat, display: inline-block |
| .sprite-search | background-position: -8px -8px; width: 24px; height: 24px |
| .sprite-settings | background-position: -40px -8px; width: 24px; height: 24px |
Trong markup là <span class="sprite sprite-search"></span>. Trong Sass, ý tưởng này thường được thể hiện bằng một placeholder, %sprite-base, được đưa vào từng quy tắc biểu tượng bằng @extend — tạo một bộ chọn nhóm trong đầu ra đã biên dịch thay vì lặp khai báo, đồng thời giữ mã đánh dấu ở một lớp cho mỗi biểu tượng.
Retina và HiDPI: mẹo background-size
Phần đa số hướng dẫn bỏ hoặc sai. Raster 1x mềm ở màn 2x; sửa không đổi độ lệch mà đóng gói gấp đôi, bảo CSS sheet nửa cỡ thật.
Lấy ví dụ trước, gấp đôi khi xuất: biểu tượng 48×48 pixel thực, khe 16, sheet 320×64 pixel thực. Giờ đặt background-size: 160px 32px — đúng nửa kích thước thật của sheet. Trình duyệt thu toàn sheet về không gian tọa độ 160×32 pixel CSS và ánh xạ chi tiết thêm lên pixel vật lý của màn hình HiDPI.
Lợi ích là mọi số CSS khác giữ hệ tọa độ 1x gốc. Biểu tượng ba vẫn background-position: -72px -8px với width: 24px; height: 24px, không đổi, dù các pixel thực nằm tại 144, 16 trong tệp. Bạn tính độ lệch một lần bằng pixel CSS, và một background-size khai báo trên lớp cơ sở xử lý ánh xạ mật độ cho mọi biểu tượng cùng lúc.
Hai hệ quả cần nói rõ. Trước hết, background-size thuộc quy tắc cơ sở dùng chung, không lặp lại cho từng biểu tượng. Thứ hai, đừng cung cấp hai sheet qua media query trừ khi có lý do rõ ràng: sheet 2x hiển thị ở nửa kích thước cũng đúng trên màn hình 1x (trình duyệt chỉ thu nhỏ), nên một sheet và một quy tắc bao phủ cả hai trường hợp, đổi lại tệp lớn hơn. Vì sheet biểu tượng nén tốt thường nhỏ, sự đánh đổi đó thường đáng giá — và tránh vùng trung gian khó xử của tỷ lệ pixel thiết bị phân số như 1.5 và 2.5, khi media query phải chọn một phía và một phía sẽ sai.
Biến thể rê chuột và trạng thái
Bố cục sprite cổ điển cho phần tử tương tác là lưới mà mỗi cột là một biểu tượng và mỗi hàng là một trạng thái: mặc định ở hàng một, hover ở hàng hai, active hoặc disabled ở hàng ba. Vì cột không di chuyển, đổi trạng thái chỉ dịch Y và độ lệch X giữ nguyên hoàn toàn.
Với biểu tượng 24px và khoảng cách 8px, hàng một nằm tại y = 8 và hàng hai tại y = 40. Quy tắc mặc định của biểu tượng thứ ba là background-position: -72px -8px và quy tắc hover của nó là background-position: -72px -40px. Nếu đặt bước dọc bằng một biến — ví dụ --sprite-row: 32px — bạn có thể diễn đạt trạng thái hover một lần trên lớp cơ sở bằng calc() theo biến X riêng của mỗi biểu tượng, thay vì viết quy tắc thứ hai cho từng biểu tượng.
Sprite còn thắng tệp riêng vì pixel hover đến cùng mặc định, lần đầu tức thì. Giữ trạng thái cùng sheet, tách hover làm lại chớp yêu cầu lần đầu mà bố cục tránh.
Khoảng đệm và lý do lem màu ở đây nhẹ hơn trong game
Đặt ảnh sát nhau có thể khiến bộ lấy mẫu vượt ranh giới biểu tượng và kéo màu của ảnh bên cạnh vào. Trong game engine, đây là rủi ro thường xuyên, quen thuộc vì texture bị thu nhỏ, tạo mipmap, lọc và vẽ với phép biến đổi tùy ý ở mức dưới pixel.
Sprite CSS hoạt động trong môi trường nhẹ nhàng hơn. Không có mipmap, nền thường ghép ở vị trí nguyên và tại đúng 1:1 không biến đổi thì không lấy mẫu vượt ranh giới. Nhưng “nhẹ hơn” không nghĩa “không bao giờ”; lỗi xuất hiện ở nơi dự đoán được: tỷ lệ pixel thiết bị phân số (1,5, 2,25, 3), zoom trang ở phần trăm lẻ, bất kỳ transform: scale() trên phần tử tổ tiên, và background-size thu nhỏ từ phần retina — tất cả đều đặt mép biểu tượng lên ranh giới pixel thiết bị không nguyên, nơi bộ tổng hợp phải nội suy.
Cách sửa giống nhau và rẻ: chừa khe trong suốt 2 đến 4 pixel ở 1x, tức 4 đến 8 trên sheet 2x, giữa mọi biểu tượng và quanh mép ngoài. Tốn dung lượng không đáng kể, loại cả loại báo lỗi “có đường nhạt bên trái biểu tượng, chỉ trên laptop tôi”. Phần sâu hơn như extrusion, lem mipmap, cỡ lũy thừa hai được giải thích cho engine tại Đóng gói sprite atlas: đệm, lũy thừa hai và lem màu.
Các kiểu lỗi thường gặp
- Biểu tượng bên cạnh lộ ở cạnh. Hộp phần tử lớn hơn biểu tượng nên cửa sổ lộ nội dung phía ngoài. Rộng/cao sai hoặc padding làm hộp lớn; kiểm tra xem box-sizing: border-box đang được áp dụng, vì nó thay đổi những gì chiều rộng khai báo của bạn bao gồm.
- Quên background-repeat: no-repeat. Mặc định là repeat, khiến bảng lặp lại và các mảnh biểu tượng khác lấp đầy khung. Lỗi này dễ bị bỏ qua khi biểu tượng tình cờ nằm gần gốc của bảng và trông gần đúng.
- Span không có kích thước. Phần tử nội dòng bỏ qua width và height hoàn toàn, nên một <span> có lớp sprite sẽ co về kích thước không và không hiển thị gì. Đặt display: inline-block (hoặc block, hoặc biến nó thành phần tử flex) trên lớp cơ sở.
- Sheet cũ sau khi dựng lại. Bạn đóng gói lại sprite, tọa độ thay đổi, nhưng khách quay lại vẫn có sheet cũ trong bộ nhớ đệm cùng CSS mới — nên mọi biểu tượng đều hơi sai với họ và hoàn hảo với bạn. Phá bộ nhớ đệm bằng cách đổi tên tệp mỗi lần dựng lại (mã băm nội dung, sprite.a1b2c3.png) thay vì dựa vào chuỗi truy vấn hoặc việc người dùng tải lại cưỡng bức.
- Độ lệch nửa pixel. Kích thước biểu tượng lẻ, khoảng rãnh lẻ hoặc lề đầu lẻ trong bảng 2x tạo ra độ lệch phân số sau khi chia đôi, và chính độ lệch phân số gây viền nhiễu ở cạnh. Hãy giữ mọi kích thước trong bảng 2x là số chẵn.
Câu hỏi thường gặp
Sprite CSS đã lỗi thời vào năm 2026 chưa?
Lý do ban đầu lỗi thời, kỹ thuật không. HTTP/2 và HTTP/3 khiến giảm yêu cầu riêng nó không thuyết phục. Còn bộ nhiều hình raster nhỏ: một cache, phiên bản nguyên tử, không bật hiện từng biểu tượng khi tải, hover tức thời vẫn hữu ích. Với UI đơn sắc, SVG thật sự thay thế raster; dùng raster ở đó năm 2026 là chọn công cụ kém hơn.
Sprite CSS hay sprite SVG — nên dùng loại nào?
Quyết định theo đồ họa, không theo cách phân phối. Nếu biểu tượng phẳng, hình học và một hoặc hai màu, dùng sprite SVG: đổi tỷ lệ không cần bảng 2x, đổi màu qua currentColor, và nằm trong DOM để công nghệ hỗ trợ có thể tiếp cận. Nếu ảnh của bạn là ảnh chụp, có nhiều chuyển màu hoặc thực sự là đồ họa raster nhiều màu — cờ, ảnh thu nhỏ sản phẩm, logo nền tảng, đồ họa pixel — SVG không mang lại lợi ích và sprite CSS raster là lựa chọn đúng.
Bảng có thể lớn đến đâu trước khi gây hại?
Hai giới hạn. Thực tế sheet chặn vẽ mọi icon, lớn trì hoãn lần vẽ đầu thì hết lợi; giữ vài trăm KB thấp, tách icon ít dùng như admin/settings thành sheet chỉ tải nơi cần. Kỹ thuật: browser/mobile giới hạn ảnh giải mã/nhớ canvas; lớn có thể âm thầm thu hoặc không giải mã trên máy ít nhớ. Quá khoảng 2000 pixel một cạnh nên chia, nhớ sheet 2x đã gấp đôi thiết kế.
Tôi có thể đổi màu biểu tượng sprite bằng CSS không?
Không hẳn, và đây là câu trả lời trung thực chứ không phải câu trả lời thuận tiện. Màu đã được ghi cố định vào tệp raster. Bạn có thể mô phỏng gần đúng bằng filter — nối chuỗi invert, sepia, saturate, và hue-rotate để đưa biểu tượng đen đến sắc màu mục tiêu — nhưng đúng nghĩa đây là mẹo vá: giá trị tìm bằng thử nghiệm hoặc bộ giải, không đạt chính xác màu thương hiệu, hỏng với hình nhiều màu và người tiếp theo trong mã nguồn khó hiểu được. Một sheet thứ hai có màu khác rõ ràng hơn chuỗi bộ lọc. Nếu đổi màu là yêu cầu thực sự, yêu cầu đó đang nói bạn nên dùng SVG.
Đóng gói biểu tượng vào sprite sheet
Sprite Gen lấy ảnh thả vào, đóng gói thành một sheet với đệm chỉ định và ghi tọa độ nên không phải đọc độ lệch thủ công từ canvas. Chọn CSS sẽ có quy tắc nền chung chứa background-image, background-repeat: no-repeat và display: inline-block, cộng một quy tắc cho mỗi sprite với giá trị âm của background-position và chiều rộng, chiều cao pixel — đúng cấu trúc mô tả phía trên. Chọn SCSS và cùng đầu ra đó xuất hiện dưới dạng %sprite-base chỗ giữ chỗ mà mỗi quy tắc biểu tượng đưa vào qua @extend. Chọn JSON để nhận tọa độ khung hình thô, đưa vào công cụ riêng của bạn. Tất cả chạy trong trình duyệt, không tải lên đâu cả.
Mở Tạo bảng sprite