Gần đây khi đọc về CSS, tôi tìm thấy một thứ khiến thiết kế đáp ứng trở nên giống như phép thuật thực sự.
Phép thuật thực sự của kỹ thuật này xuất hiện khi bạn xây dựng một bố cục và mọi thứ khớp với nhau một cách hoàn hảo. Tuần trước, tôi tình cờ biết đến kỹ thuật CSS này, nó đã thay đổi hoàn toàn cách chúng ta nghĩ về các thành phần đáp ứng.
Chúng ta hãy xem kỹ thuật đơn giản này biến đổi việc xây dựng giao diện UI phản hồi trong CSS như thế nào để phù hợp hoàn hảo với vùng chứa thay vì khung nhìn.
Mẫu hình thay đổi mọi thứ
Tôi đã thử nghiệm CSS trong nhiều tháng nay và có một mẫu liên tục xuất hiện ở mọi nơi tôi nhìn.
Mẫu đơn giản này khắc phục được vấn đề: “Tôi muốn bố cục khác nhau cho tính năng X tùy thuộc vào dung lượng của nó. Mẫu này cần đáp ứng kích thước của vùng chứa , chứ không phải kích thước của khung nhìn.”
Tại sao các truy vấn truyền thông không đạt yêu cầu ở đây
Các truy vấn phương tiện truyền thống sẽ trông giống như cơn ác mộng này:
.metadata-column {
/* Các kiểu được cô đọng ở đây */
@media ( min-width : 35rem ) và ( max-width : 42rem ),
( min-width : 60rem ) {
/* Các kiểu thưa thớt ở đây */
}
}
Những con số đó: 35rem, 42rem, 60remchúng không phải là điểm dừng thực sự. Chúng là những con số ma thuật mà tôi đo được bằng cách thay đổi kích thước cửa sổ trình duyệt cho đến khi mọi thứ trông ổn.
Cách tiếp cận này khá mong manh. Thay đổi khoảng cách giữa các cột? Bố cục của bạn bị hỏng. Điều chỉnh khoảng đệm? Mọi thứ sẽ tràn ra ngoài ở độ rộng khung nhìn ngẫu nhiên.
Hầu hết các nhà phát triển thậm chí sẽ không nhận thấy vì sự cố này chỉ xảy ra trong phạm vi khung nhìn hẹp.
Truy vấn container: Giải pháp sạch
Hãy xem nó sạch hơn bao nhiêu nhé:
.container {
container-type: inline-size;
}
.child {
/* Các kiểu cô đọng ở đây */
@container ( min-width : 19rem ) {
/* Các kiểu thưa thớt ở đây */
}
}
Thay vì ba số ngẫu nhiên, tôi có một điểm dừng cố ý.
Thay vì định dạng dựa trên khung nhìn, nó cho phép các thành phần tự định dạng dựa trên kích thước vùng chứa của chúng. Nói cách khác: các thành phần sẽ phản hồi với không gian thực tế mà chúng có.
Điều này tạo ra các thành phần thực sự có tính mô-đun, có thể tái sử dụng và thích ứng linh hoạt ở bất cứ nơi nào bạn đặt chúng.
< div class = “post” >
< div class = “card” >
< h2 > Tiêu đề thẻ </ h2 >
< p > Nội dung thẻ </ p >
</ div >
</ div >
.post {
container-type: inline-size;
}
/* Kiểu tiêu đề mặc định cho tiêu đề thẻ */
.card h2 {
font-size : 1em ;
}
/* Nếu container lớn hơn 700px */
@container ( width > 700px ) {
.card h2 {
font-size : 2em ;
}
}
Container được đặt tên
Đây là một tính năng mà hầu hết các nhà phát triển không biết, bạn có thể chọn vùng chứa nào để nhắm mục tiêu.
main {
tên-thùng-chứa: bên ngoài;
kiểu-thùng-chứa: kích thước-nội-dòng;
}
phần {
tên-thùng-chứa: bên trong;
kiểu-thùng-chứa: kích thước-nội-dòng;
}
.locked-to-main {
@container bên ngoài ( chiều rộng tối thiểu : 12rem ) {
màu : đỏ;
độ đậm phông chữ : đậm;
}
}
.locked-to-section {
@container bên trong ( chiều rộng tối thiểu : 12rem ) {
màu : đỏ;
độ đậm phông chữ : đậm;
}
}
Theo mặc định, các truy vấn chứa nhắm mục tiêu đến tổ tiên gần nhất vớicontainer-type.Nhưng với các thùng chứa được đặt tên, bạn có thể nhắm mục tiêu đến bất kỳ tổ tiên nào bạn muốn.
Bạn có thể đơn giản hóa điều này bằng thuộc tính viết tắt:
main {
container: outer / inline-size;
/* Tương đương với: */
container-name: outer;
container-type: inline-size;
}
Ký tự gạch chéo phân tách tên khỏi kiểu: đây không phải là phép chia, mà chỉ là cú pháp CSS hiện đại.
Đơn vị truy vấn container
Truy vấn container cũng cung cấp cho bạn các đơn vị tương đối dựa trên kích thước container:
.card-container {
kiểu chứa: kích thước nội tuyến;
}
.card-title {
cỡ chữ : 4 cqw; /* 4% chiều rộng của vùng chứa */
đệm : 2 cqh; /* 2% chiều cao của vùng chứa */
lề dưới : 1 cqi; /* 1% kích thước nội tuyến */
}
.responsive-text {
cỡ chữ : kẹp ( 1rem , 3 cqw, 2rem );
}
@container ( chiều rộng > 700px ) {
.card h2 {
cỡ chữ : tối đa ( 1,5em , 1,23em + 2 cqi);
}
}
Các đơn vị có sẵn:
cqw- 1% chiều rộng container
cqh- 1% chiều cao của container
cqi- 1% kích thước nội tuyến của container
cqb- 1% kích thước khối chứa
cqmin- nhỏ hơn cqihoặccqb
cqmax- lớn hơn cqihoặccqb
Những sai lầm phổ biến mà các nhà phát triển mắc phải
Sau đây là một số lỗi phổ biến mà các nhà phát triển mắc phải khi sử dụng truy vấn container:
Quên loại container
Sử dụng truy vấn container như truy vấn phương tiện. Không sử dụng nó cho bố cục cấp trang
Sự nhầm lẫn về container lồng nhau. Truy vấn container nhắm mục tiêu đến container cha gần nhất vớicontainer-type
Thử nghiệm
Tích hợp tính năng cải tiến dần dần
Ưu điểm của cách tiếp cận này là gì? Nó không hiệu quả trên các trình duyệt cũ.
Các quy tắc CSS bên trong @containersẽ không bao giờ được áp dụng nếu trình duyệt không hỗ trợ truy vấn vùng chứa. Các thành phần của bạn sẽ luôn ở trạng thái “cô đọng” bất kể kích thước vùng chứa.
Hỗ trợ trình duyệt: Sẵn sàng cho sản xuất
Truy vấn container hoạt động trên tất cả các trình duyệt hiện đại kể từ năm 2024:
Chrome/Edge: 105+ (tháng 9 năm 2022)
Firefox: 110+ (tháng 2 năm 2023)
Safari: 16+ (tháng 9 năm 2022)
Tức là có hơn 93% trình duyệt được hỗ trợ trên toàn cầu.
Đối với các trình duyệt cũ hơn, các thành phần sẽ tự động chuyển về kiểu mặc định.
Bài học cuối cùng
Truy vấn container mang đến một sự thay đổi căn bản trong cách chúng ta suy nghĩ về thiết kế đáp ứng. Chúng ta đang chuyển từ tư duy dựa trên khung nhìn sang tư duy dựa trên thành phần. Từ điểm dừng toàn cục sang nhận thức ngữ cảnh cục bộ.
Tôi muốn giới thiệu một bộ ảnh mà không cần bất kỳ đoạn mã nặng nề nào trong ứng dụng của mình. Hóa ra, CSS thuần túy có thể xử lý nó một cách hoàn hảo. Bạn có thể phóng to những bức ảnh đó mà không cần JavaScript.
Bí quyết là gì? Sử dụng tabindex thuộc tính cùng :focusvới :focus-within bộ chọn giả CSS để có khả năng tương tác mượt mà và nhẹ nhàng.
Thiết lập cấu trúc
Nếu bạn muốn hiển thị số lượng hình ảnh linh hoạt, bạn sẽ cần tạo đánh dấu thích ứng trông như thế này:
< div class = “image-gallery image-gallery–4” >
< div class = “image-gallery__item” >
< div class = “image-gallery__backdrop” tabindex = “-1” >
< img
class = “image-gallery__image”
src = “//picsum.photos/seed/7cb42b45/1500/750”
tabindex = “0”
/>
</ div >
</ div >
< div class = “image-gallery__item” >
< div class = “image-gallery__backdrop” tabindex = “-1” >
< img
class = “image-gallery__image”
src = “//picsum.photos/seed/4dd8300c/1500/750”
tabindex = “0”
/>
</ div >
</ div >
< div class = “image-gallery__item” >
< div class = “image-gallery__backdrop” tabindex = “-1” >
< img
class = “image-gallery__image”
src = “//picsum.photos/seed/b66a4549/1500/750”
tabindex = “0”
/>
</ div >
</ div >
< div class = “image-gallery__item” >
< div class = “image-gallery__backdrop” tabindex = “-1” >
< img
class = “image-gallery__image”
src = “//picsum.photos/seed/5783fa29/1500/750”
tabindex = “0”
/>
</ div >
</ div >
</ div >
Phần tử này .image-gallerybao gồm một lớp phản ánh số lượng hình ảnh mà nó chứa, chẳng hạn như .image-gallery–4. Chi tiết này giúp việc định dạng các bố cục khác nhau dễ dàng hơn nhiều, như bạn sẽ thấy trong phần CSS bên dưới.
Mỗi mục .image-gallery__itemđều có phần tử nền với tabindex=”-1″, do đó có thể tập trung khi nhấp vào nhưng sẽ bị bỏ qua khi chuyển qua trang.
Cuối cùng, <img>bên trong phông nền có nút tabindex=”0″cho phép người dùng tập trung vào bằng cách nhấp hoặc nhấn phím tab.
Chúng ta hãy thêm một số kiểu:
.image-gallery {
chiều rộng : 100% ;
hiển thị : lưới;
cột mẫu lưới : lặp lại ( 2 , 1 fr);
khoảng cách : 1px ;
lề : 0 tự động;
}
@media ( chiều rộng tối thiểu : 1001px ) {
.image-gallery {
cột mẫu lưới : lặp lại ( 4 , 1 fr);
}
}
.image-gallery__item {
chiều rộng : 100% ;
chiều cao : 100% ;
tỷ lệ khung hình: 1400 / 650 ;
}
.image-gallery–1 .image-gallery__item {
cột lưới : 1 / – 1 ;
}
@media ( chiều rộng tối thiểu : 1001px ) {
.image-gallery :is ( .image-gallery–1 ) {
hàng mẫu lưới : 1 fr;
}
.image-gallery :not ( .image-gallery–1 , .image-gallery–3 ) {
grid-template-rows : 250px 250px ;
}
}
.image-gallery__item {
nền : gradient tuyến tính (
xuống dưới bên phải,
#f5f5f5 0% ,
#c5c5c5 50% ,
#fff 100%
);
}
@media ( min-width : 1001px ) {
.image-gallery :is ( .image-gallery–2 , .image-gallery–3 )
.image-gallery__item :not ( :nth-child ( 1 )),
.image-gallery :not ( .image-gallery–1 , .image-gallery–2 , .image-gallery–3 )
.image-gallery__item :nth-child ( 4 ),
.image-gallery :not (
.image-gallery–1 ,
.image-gallery–2 ,
.image-gallery–3 ,
.image-gallery–4
)
.image-gallery__item :nth-child (n + 5 ) {
cột lưới : khoảng 2 ;
}
.image-gallery :không ( .image-gallery–1 ) .image-gallery__item :nth-child ( 1 ),
.image-gallery–2 .image-gallery__item :nth-child ( 2 ) {
cột lưới : khoảng 2 ;
hàng lưới : khoảng 2 ;
}
}
.image-gallery__backdrop {
chiều rộng : 100% ;
chiều cao : 100% ;
chuyển đổi : màu nền 160ms ;
chỉ số z : 1 ;
}
.image-gallery__image {
chiều rộng : 100% ;
chiều cao : 100% ;
đối tượng-phù hợp : bìa;
bán kính đường viền : kế thừa;
con trỏ : phóng to;
người dùng chọn: không có;
phác thảo : không có;
}
Thêm tính tương tác
Hãy làm cho nó thú vị hơn bằng cách thêm hiệu ứng phóng to ở đây. Chúng ta sẽ sử dụng position: fixedhiệu ứng này để hiển thị phông nền của ảnh khi ảnh con đang ở trạng thái lấy nét:
.image-gallery__backdrop :not ( :focus ) :focus -within {
vị trí : cố định;
trên cùng : 0 ;
bên trái : 0 ;
hiển thị : flex;
căn chỉnh các mục : ở giữa;
căn chỉnh nội dung : ở giữa;
màu nền : rgba ( 0 , 0 , 0 , 0 , 0,1 );
con trỏ : thu nhỏ;
chỉ số z : 2 ;
người dùng chọn: không có;
}
Bây giờ chúng ta sẽ làm cho hình ảnh hiển thị ở phía trên phông nền khi được lấy nét:
.image-gallery__image :focus {
chiều rộng : tự động;
chiều rộng tối đa : 90% ;
chiều cao : tự động;
chiều cao tối đa : 90% ;
object-fit : chứa;
bán kính đường viền : 8px ;
sự kiện con trỏ : không có;
chỉ số z : 3 ;
hoạt ảnh : phóng to image-gallery 160ms ;
}
@keyframes image-gallery-zoom-in {
0% {
độ mờ : 0 ;
biến đổi : tỷ lệ ( 0,98 );
}
}
Chúng tôi đã thêm pointer-events: nonethuộc tính để sự kiện nhấp chuột sẽ chuyển đến chính phông nền, giúp đóng hình ảnh đang hoạt động.
Khi tabindex=”0″làm cho hình ảnh có thể lấy nét, bạn có thể di chuyển về phía trước qua hình ảnh bằng phím tab và lùi lại bằng phím shift+tab.
Chúng ta hãy thêm các chỉ số để người dùng hiểu cách thức hoạt động:
< div class = “image-gallery__keyboard-indicator” >
Sử dụng < code > Tab </ code > hoặc < code > Shift </ code > + < code > Tab </ code > để di chuyển giữa các hình ảnh
</ div >
.image-gallery__keyboard-indicator {
hiển thị : không có;
vị trí : cố định;
dưới cùng : 26px ;
trái : 50% ;
đệm : 8px 12px ;
cỡ chữ : 14px ;
màu nền : #f5f5f5 ;
bán kính đường viền : 4px ;
biến đổi : translateX (- 50% );
chỉ số z : 4 ;
}
@media ( con trỏ : tốt) {
.image-gallery : có ( .image-gallery__image :tiêu điểm )
.image-gallery__keyboard-indicator {
hiển thị : khối;
}
}
.image-gallery__keyboard-indicator > mã {
đệm : 2px 4px ;
cỡ chữ : tính toán ( 1rem – 2px );
màu nền : #fff ;
đường viền : 1px rắn #c2c2c2 ;
bán kính đường viền : 4px ;
}
Bản demo đầy đủ
Chúng ta hãy xem bản triển khai và bản demo đầy đủ tại đây:
<!DOCTYPE html >
< html lang = “en” >
< head >
< meta charset = “UTF-8” />
< meta name = “viewport” content = “width=device-width, initial-scale=1.0″ />
< title > Lưới ba cột có lưới con </ title >
< style >
.image-gallery {
width: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1px;
margin: 0 auto;
}
@media (min-width: 1001px) {
.image-gallery {
grid-template-columns: repeat(4, 1fr);
}
}
.image-gallery__item {
width: 100%;
height: 100%;
aspect-ratio: 1400 / 650;
}
.image-gallery–1 .image-gallery__item {
grid-column: 1 / -1;
}
@media (min-width: 1001px) {
.image-gallery:is(.image-gallery–1) {
grid-template-rows: 1fr;
}
.image-gallery:not(.image-gallery–1, .image-gallery–3) {
grid-template-rows: 250px 250px;
}
}
.image-gallery__item {
nền: gradient tuyến tính(
xuống dưới bên phải,
#f5f5f5 0%,
#c5c5c5 50%,
#fff 100%
);
}
@media (min-width: 1001px) {
.image-gallery:is(.image-gallery–2, .image-gallery–3)
.image-gallery__item:not(:nth-child(1)),
.image-gallery:not(
.image-gallery–1,
.image-gallery–2,
.image-gallery–3
)
.image-gallery__item:nth-child(4),
.image-gallery:not(
.image-gallery–1,
.image-gallery–2,
.image-gallery–3,
.image-gallery–4
)
.image-gallery__item:nth-child(n + 5) {
cột lưới: khoảng 2;
}
.image-gallery:không(.image-gallery–1) .image-gallery__item:con thứ n(1),
.image-gallery–2 .image-gallery__item:con thứ n(2) {
grid-column: span 2;
grid-row: span 2;
}
}
.image-gallery__backdrop {
width: 100%;
height: 100%;
transition: background-color 160ms;
z-index: 1;
}
.image-gallery__backdrop:not(:focus):focus-within {
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.1);
cursor: zoom-out;
z-index: 2;
user-select: none;
}
.image-gallery__image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
cursor: zoom-in;
user-select: none;
outline: none;
}
.image-gallery__image:focus {
width: auto;
chiều rộng tối đa: 90%;
chiều cao: tự động;
chiều cao tối đa: 90%;
đối tượng vừa vặn: chứa;
bán kính đường viền: 8px;
sự kiện con trỏ: không có;
chỉ mục z: 3;
hoạt ảnh: phóng to thư viện ảnh 160ms;
}
@keyframes phóng to thư viện ảnh {
0% {
độ mờ: 0;
biến đổi: tỷ lệ (0,98);
}
}
.image-gallery__keyboard-indicator {
hiển thị: không có;
vị trí: cố định;
dưới cùng: 26px;
bên trái: 50%;
đệm: 8px 12px;
cỡ chữ: 14px;
màu nền: #f5f5f5;
bán kính đường viền: 4px;
biến đổi: translateX(-50%);
chỉ mục z: 4;
}
@media (con trỏ: tốt) {
.image-gallery:has(.image-gallery__image:focus)
.image-gallery__keyboard-indicator {
hiển thị: khối;
}
}
.image-gallery__keyboard-indicator > mã {
đệm: 2px 4px;
cỡ chữ: calc(1rem – 2px);
màu nền: #fff;
đường viền: 1px solid #c2c2c2;
bán kính đường viền: 4px;
}
</ style >
</ head >
< body >
< div class =”image-gallery image-gallery–4” >
< div class = “image-gallery__item” >
< div class = “image-gallery__backdrop” tabindex = “-1” >
< img
class = “image-gallery__image”
src = “//picsum.photos/seed/7cb42b45/1500/750”
tabindex = “0”
/>
</ div >
</ div >
< div class = “image-gallery__item” >
< div class = “image-gallery__backdrop” tabindex = “-1” >
< img
class = “image-gallery__image”
src = “//picsum.photos/seed/4dd8300c/1500/750”
tabindex = “0”
/>
</ div >
</ div >
< div class = “image-gallery__item” >
< div class = “image-gallery__backdrop” tabindex = “-1” >
< img
class = “image-gallery__image”
src = “//picsum.photos/seed/b66a4549/1500/750”
tabindex = “0”
/>
</ div >
</ div >
< div class = “image-gallery__item” >
< div class = “image-gallery__backdrop” tabindex = “-1” >
< img
class = “image-gallery__image”
src = “//picsum.photos/seed/5783fa29/1500/750”
tabindex = “0”
/>
</ div >
</ div >
< div class = “image-gallery__keyboard-indicator” >
Sử dụng < code > Tab </ code > hoặc < code > Shift </ code > + < code> Tab </ code > để di chuyển
giữa các hình ảnh
</ div >
</ div >
</ body >
</ html >
Bài học cuối cùng
Hôm nay chúng ta sẽ khám phá cách xây dựng thư viện ảnh mà không cần sử dụng JavaScript phức tạp và chỉ cần CSS thuần túy.
Bài viết liên quan: