Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Container Queries - responsywność oparta na kontenerze

Container Queries to rewolucyjna funkcjonalność CSS, która umożliwia tworzenie komponentów responsywnych w oparciu o rozmiar ich kontenera, a nie okna przeglądarki. To przełom w projektowaniu komponentów, który pozwala na prawdziwie modularne i elastyczne układy.

Podstawy Container Queries

Czym są Container Queries?

Container Queries pozwalają na stosowanie stylów w zależności od rozmiaru kontenera rodzica, a nie viewport. To oznacza, że komponenty mogą reagować na swój lokalny kontekst, co czyni je bardziej przewidywalnymi i wielokrotniego użytku.

1/* Tradycyjne Media Queries - bazują na viewport */
2@media (max-width: 768px) {
3  .card {
4    flex-direction: column;
5  }
6}
7
8/* Container Queries - bazują na rozmiarze kontenera */
9@container (max-width: 400px) {
10  .card {
11    flex-direction: column;
12  }
13}

Ustanawianie kontekstu kontenera

Aby używać Container Queries, najpierw musimy zdefiniować element jako kontener:

1.container {
2  container-type: inline-size; /* Monitoruje szerokość */
3}
4
5.container {
6  container-type: size; /* Monitoruje szerokość i wysokość */
7}
8
9.container {
10  container-type: normal; /* Wartość domyślna - brak monitorowania */
11}
12
13/* Skrócona forma z nazwą kontenera */
14.sidebar {
15  container: sidebar / inline-size;
16}

Praktyczne zastosowania Container Queries

1. Responsywna karta produktu

1.product-grid {
2  display: grid;
3  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
4  gap: 1rem;
5  container-type: inline-size;
6}
7
8.product-card {
9  background: white;
10  border-radius: 8px;
11  padding: 1rem;
12  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
13}
14
15/* Gdy kontener jest wąski - układ pionowy */
16@container (max-width: 300px) {
17  .product-card {
18    text-align: center;
19  }
20  
21  .product-card .image {
22    width: 100%;
23    margin-bottom: 1rem;
24  }
25  
26  .product-card .title {
27    font-size: 1.1rem;
28    margin-bottom: 0.5rem;
29  }
30  
31  .product-card .price {
32    font-size: 1.2rem;
33    color: #e74c3c;
34  }
35}
36
37/* Gdy kontener jest szerszy - układ poziomy */
38@container (min-width: 400px) {
39  .product-card {
40    display: flex;
41    align-items: center;
42    gap: 1rem;
43  }
44  
45  .product-card .image {
46    width: 120px;
47    height: 120px;
48    flex-shrink: 0;
49  }
50  
51  .product-card .content {
52    flex: 1;
53  }
54  
55  .product-card .title {
56    font-size: 1.3rem;
57    margin-bottom: 0.5rem;
58  }
59  
60  .product-card .description {
61    color: #666;
62    margin-bottom: 1rem;
63  }
64  
65  .product-card .price {
66    font-size: 1.4rem;
67    font-weight: bold;
68    color: #27ae60;
69  }
70}

2. Adaptacyjna nawigacja

1.navigation {
2  container-name: nav;
3  container-type: inline-size;
4}
5
6.nav-list {
7  display: flex;
8  list-style: none;
9  margin: 0;
10  padding: 0;
11  gap: 1rem;
12}
13
14/* Kompaktowa nawigacja dla małych kontenerów */
15@container nav (max-width: 600px) {
16  .nav-list {
17    flex-direction: column;
18    gap: 0;
19  }
20  
21  .nav-item {
22    border-bottom: 1px solid #eee;
23  }
24  
25  .nav-link {
26    display: block;
27    padding: 1rem;
28    text-decoration: none;
29    transition: background-color 0.2s;
30  }
31  
32  .nav-link:hover {
33    background-color: #f8f9fa;
34  }
35  
36  /* Ukryj teksty i pokaż tylko ikony */
37  .nav-text {
38    display: none;
39  }
40  
41  .nav-icon {
42    display: block;
43    font-size: 1.5rem;
44  }
45}
46
47/* Pełna nawigacja dla większych kontenerów */
48@container nav (min-width: 601px) {
49  .nav-list {
50    justify-content: space-between;
51    align-items: center;
52  }
53  
54  .nav-link {
55    display: flex;
56    align-items: center;
57    gap: 0.5rem;
58    padding: 0.5rem 1rem;
59    text-decoration: none;
60    border-radius: 4px;
61    transition: all 0.2s;
62  }
63  
64  .nav-link:hover {
65    background-color: #007bff;
66    color: white;
67  }
68  
69  .nav-text {
70    display: inline;
71  }
72  
73  .nav-icon {
74    font-size: 1.2rem;
75  }
76}

3. Dashboard z elastycznymi widgetami

1.dashboard {
2  display: grid;
3  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
4  gap: 1rem;
5  padding: 1rem;
6}
7
8.widget {
9  background: white;
10  border-radius: 8px;
11  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
12  container-type: inline-size;
13  overflow: hidden;
14}
15
16.widget-header {
17  padding: 1rem;
18  background: #f8f9fa;
19  border-bottom: 1px solid #dee2e6;
20}
21
22.widget-content {
23  padding: 1rem;
24}
25
26/* Mały widget - podstawowe informacje */
27@container (max-width: 350px) {
28  .widget-header h3 {
29    font-size: 1rem;
30    margin: 0;
31  }
32  
33  .widget-stats {
34    display: block;
35  }
36  
37  .stat-item {
38    margin-bottom: 0.5rem;
39    text-align: center;
40  }
41  
42  .stat-value {
43    font-size: 1.5rem;
44    font-weight: bold;
45    display: block;
46  }
47  
48  .stat-label {
49    font-size: 0.8rem;
50    color: #666;
51  }
52  
53  .chart {
54    height: 150px;
55  }
56}
57
58/* Średni widget - więcej szczegółów */
59@container (min-width: 351px) and (max-width: 500px) {
60  .widget-header h3 {
61    font-size: 1.2rem;
62    margin: 0;
63  }
64  
65  .widget-stats {
66    display: flex;
67    justify-content: space-around;
68    margin-bottom: 1rem;
69  }
70  
71  .stat-item {
72    text-align: center;
73  }
74  
75  .stat-value {
76    font-size: 1.8rem;
77    font-weight: bold;
78    display: block;
79    color: #007bff;
80  }
81  
82  .stat-label {
83    font-size: 0.9rem;
84    color: #666;
85  }
86  
87  .chart {
88    height: 200px;
89  }
90}
91
92/* Duży widget - pełne detale */
93@container (min-width: 501px) {
94  .widget-header {
95    display: flex;
96    justify-content: space-between;
97    align-items: center;
98  }
99  
100  .widget-header h3 {
101    font-size: 1.4rem;
102    margin: 0;
103  }
104  
105  .widget-actions {
106    display: flex;
107    gap: 0.5rem;
108  }
109  
110  .widget-stats {
111    display: grid;
112    grid-template-columns: repeat(3, 1fr);
113    gap: 1rem;
114    margin-bottom: 1.5rem;
115  }
116  
117  .stat-item {
118    text-align: center;
119    padding: 1rem;
120    background: #f8f9fa;
121    border-radius: 4px;
122  }
123  
124  .stat-value {
125    font-size: 2rem;
126    font-weight: bold;
127    display: block;
128    color: #28a745;
129  }
130  
131  .stat-label {
132    font-size: 1rem;
133    color: #666;
134    margin-top: 0.5rem;
135  }
136  
137  .chart {
138    height: 250px;
139  }
140  
141  .widget-details {
142    display: block;
143    margin-top: 1rem;
144    padding-top: 1rem;
145    border-top: 1px solid #dee2e6;
146  }
147}

Zaawansowane techniki Container Queries

1. Logiczne operatory

1.component {
2  container-type: inline-size;
3}
4
5/* Połączenie warunków */
6@container (min-width: 300px) and (max-width: 600px) {
7  .component {
8    background: lightblue;
9  }
10}
11
12/* Alternatywa */
13@container (max-width: 300px), (min-width: 800px) {
14  .component {
15    border: 2px solid red;
16  }
17}
18
19/* Negacja */
20@container not (min-width: 400px) {
21  .component {
22    font-size: 0.9rem;
23  }
24}

2. Zagnieżdżone kontenery

1.main-container {
2  container-name: main;
3  container-type: inline-size;
4}
5
6.sidebar {
7  container-name: sidebar;
8  container-type: inline-size;
9}
10
11.article {
12  container-name: article;
13  container-type: inline-size;
14}
15
16/* Różne style w zależności od kontekstu */
17@container main (min-width: 1200px) {
18  .layout {
19    display: grid;
20    grid-template-columns: 300px 1fr;
21    gap: 2rem;
22  }
23}
24
25@container sidebar (max-width: 250px) {
26  .sidebar-item {
27    padding: 0.5rem;
28  }
29  
30  .sidebar-text {
31    display: none;
32  }
33}
34
35@container article (min-width: 600px) {
36  .article-content {
37    columns: 2;
38    column-gap: 2rem;
39  }
40}

3. Container Queries z CSS Grid

1.gallery {
2  container-type: inline-size;
3}
4
5/* Responsywna galeria bazująca na rozmiarze kontenera */
6@container (max-width: 400px) {
7  .gallery {
8    display: grid;
9    grid-template-columns: 1fr;
10    gap: 1rem;
11  }
12}
13
14@container (min-width: 401px) and (max-width: 800px) {
15  .gallery {
16    display: grid;
17    grid-template-columns: repeat(2, 1fr);
18    gap: 1rem;
19  }
20}
21
22@container (min-width: 801px) and (max-width: 1200px) {
23  .gallery {
24    display: grid;
25    grid-template-columns: repeat(3, 1fr);
26    gap: 1.5rem;
27  }
28}
29
30@container (min-width: 1201px) {
31  .gallery {
32    display: grid;
33    grid-template-columns: repeat(4, 1fr);
34    gap: 2rem;
35  }
36}
37
38/* Adaptacyjne zdjęcia */
39.gallery-item {
40  position: relative;
41  overflow: hidden;
42  border-radius: 8px;
43}
44
45@container (max-width: 400px) {
46  .gallery-item {
47    aspect-ratio: 16/9;
48  }
49  
50  .gallery-item img {
51    width: 100%;
52    height: 100%;
53    object-fit: cover;
54  }
55  
56  .gallery-caption {
57    position: absolute;
58    bottom: 0;
59    left: 0;
60    right: 0;
61    background: linear-gradient(transparent, rgba(0,0,0,0.8));
62    color: white;
63    padding: 1rem;
64    font-size: 0.9rem;
65  }
66}
67
68@container (min-width: 801px) {
69  .gallery-item {
70    aspect-ratio: 1;
71    transition: transform 0.3s ease;
72  }
73  
74  .gallery-item:hover {
75    transform: scale(1.05);
76  }
77  
78  .gallery-caption {
79    position: absolute;
80    bottom: 0;
81    left: 0;
82    right: 0;
83    background: rgba(0,0,0,0.9);
84    color: white;
85    padding: 1rem;
86    transform: translateY(100%);
87    transition: transform 0.3s ease;
88  }
89  
90  .gallery-item:hover .gallery-caption {
91    transform: translateY(0);
92  }
93}

Container Queries vs Media Queries

Kiedy używać Container Queries?

1/* ✅ Dobre zastosowanie Container Queries */
2.component {
3  container-type: inline-size;
4}
5
6/* Komponenty, które mogą być używane w różnych kontekstach */
7@container (max-width: 300px) {
8  .card {
9    /* Style dla wąskiego kontenera */
10    flex-direction: column;
11  }
12}
13
14/* ✅ Media Queries dla globalnych zmian */
15@media (max-width: 768px) {
16  body {
17    font-size: 14px;
18  }
19  
20  .global-header {
21    padding: 1rem;
22  }
23}
24
25/* ❌ Nie używaj Container Queries dla globalnych zmian */
26@container (max-width: 768px) {
27  body { /* To nie zadziała jak oczekujesz */
28    font-size: 14px;
29  }
30}

Praktyczny przykład - modularna karta

1<!-- HTML -->
2<div class="page-layout">
3  <main class="main-content">
4    <div class="card-container">
5      <div class="profile-card">
6        <img src="avatar.jpg" alt="Profil" class="avatar">
7        <div class="info">
8          <h3 class="name">Jan Kowalski</h3>
9          <p class="role">Frontend Developer</p>
10          <p class="description">Passionate about creating amazing web experiences</p>
11          <div class="actions">
12            <button class="btn primary">Follow</button>
13            <button class="btn secondary">Message</button>
14          </div>
15        </div>
16      </div>
17    </div>
18  </main>
19  
20  <aside class="sidebar">
21    <div class="card-container">
22      <div class="profile-card">
23        <!-- Ta sama karta, ale w węższym kontenerze -->
24        <img src="avatar.jpg" alt="Profil" class="avatar">
25        <div class="info">
26          <h3 class="name">Jan Kowalski</h3>
27          <p class="role">Frontend Developer</p>
28          <p class="description">Passionate about creating amazing web experiences</p>
29          <div class="actions">
30            <button class="btn primary">Follow</button>
31            <button class="btn secondary">Message</button>
32          </div>
33        </div>
34      </div>
35    </div>
36  </aside>
37</div>
1/* CSS z Container Queries */
2.card-container {
3  container-type: inline-size;
4}
5
6.profile-card {
7  background: white;
8  border-radius: 12px;
9  padding: 1.5rem;
10  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
11  transition: all 0.3s ease;
12}
13
14/* Wąski kontener - układ pionowy */
15@container (max-width: 350px) {
16  .profile-card {
17    text-align: center;
18  }
19  
20  .avatar {
21    width: 80px;
22    height: 80px;
23    border-radius: 50%;
24    margin: 0 auto 1rem;
25    display: block;
26  }
27  
28  .name {
29    font-size: 1.2rem;
30    margin-bottom: 0.5rem;
31  }
32  
33  .role {
34    color: #666;
35    font-size: 0.9rem;
36    margin-bottom: 1rem;
37  }
38  
39  .description {
40    display: none; /* Ukryj w wąskim kontenerze */
41  }
42  
43  .actions {
44    display: flex;
45    flex-direction: column;
46    gap: 0.5rem;
47  }
48  
49  .btn {
50    padding: 0.75rem;
51    border: none;
52    border-radius: 6px;
53    font-size: 0.9rem;
54    cursor: pointer;
55    transition: all 0.2s;
56  }
57  
58  .btn.primary {
59    background: #007bff;
60    color: white;
61  }
62  
63  .btn.secondary {
64    background: #f8f9fa;
65    color: #666;
66  }
67}
68
69/* Średni kontener - układ mieszany */
70@container (min-width: 351px) and (max-width: 500px) {
71  .profile-card {
72    display: flex;
73    align-items: center;
74    gap: 1rem;
75    text-align: left;
76  }
77  
78  .avatar {
79    width: 100px;
80    height: 100px;
81    border-radius: 50%;
82    flex-shrink: 0;
83  }
84  
85  .info {
86    flex: 1;
87  }
88  
89  .name {
90    font-size: 1.3rem;
91    margin-bottom: 0.25rem;
92  }
93  
94  .role {
95    color: #666;
96    font-size: 1rem;
97    margin-bottom: 0.5rem;
98  }
99  
100  .description {
101    font-size: 0.9rem;
102    color: #888;
103    margin-bottom: 1rem;
104    display: -webkit-box;
105    -webkit-line-clamp: 2;
106    -webkit-box-orient: vertical;
107    overflow: hidden;
108  }
109  
110  .actions {
111    display: flex;
112    gap: 0.5rem;
113  }
114  
115  .btn {
116    padding: 0.5rem 1rem;
117    border: none;
118    border-radius: 6px;
119    font-size: 0.9rem;
120    cursor: pointer;
121    transition: all 0.2s;
122  }
123  
124  .btn.primary {
125    background: #007bff;
126    color: white;
127  }
128  
129  .btn.secondary {
130    background: #f8f9fa;
131    color: #666;
132    border: 1px solid #dee2e6;
133  }
134}
135
136/* Szeroki kontener - pełny układ */
137@container (min-width: 501px) {
138  .profile-card {
139    display: flex;
140    align-items: flex-start;
141    gap: 1.5rem;
142  }
143  
144  .avatar {
145    width: 120px;
146    height: 120px;
147    border-radius: 50%;
148    flex-shrink: 0;
149  }
150  
151  .info {
152    flex: 1;
153  }
154  
155  .name {
156    font-size: 1.5rem;
157    margin-bottom: 0.5rem;
158    font-weight: 600;
159  }
160  
161  .role {
162    color: #007bff;
163    font-size: 1.1rem;
164    margin-bottom: 1rem;
165    font-weight: 500;
166  }
167  
168  .description {
169    font-size: 1rem;
170    color: #555;
171    line-height: 1.6;
172    margin-bottom: 1.5rem;
173  }
174  
175  .actions {
176    display: flex;
177    gap: 1rem;
178  }
179  
180  .btn {
181    padding: 0.75rem 1.5rem;
182    border: none;
183    border-radius: 8px;
184    font-size: 1rem;
185    font-weight: 500;
186    cursor: pointer;
187    transition: all 0.2s;
188  }
189  
190  .btn.primary {
191    background: #007bff;
192    color: white;
193  }
194  
195  .btn.primary:hover {
196    background: #0056b3;
197  }
198  
199  .btn.secondary {
200    background: white;
201    color: #666;
202    border: 2px solid #dee2e6;
203  }
204  
205  .btn.secondary:hover {
206    border-color: #007bff;
207    color: #007bff;
208  }
209}

Najlepsze praktyki Container Queries

1. Wydajność i optymalizacja

1/* ✅ Użyj tylko potrzebnego typu kontenera */
2.width-only {
3  container-type: inline-size; /* Lepsze dla wydajności */
4}
5
6.both-dimensions {
7  container-type: size; /* Tylko gdy potrzebujesz wysokości */
8}
9
10/* ✅ Grupuj podobne breakpointy */
11@container (max-width: 400px) {
12  .component-a { /* style */ }
13  .component-b { /* style */ }
14  .component-c { /* style */ }
15}
16
17/* ❌ Unikaj nadmiernego zagnieżdżania */
18@container (min-width: 300px) {
19  @container (max-width: 600px) { /* Trudne do zarządzania */
20    .component { /* style */ }
21  }
22}

2. Fallback dla starszych przeglądarek

1/* Fallback bez Container Queries */
2.responsive-component {
3  padding: 1rem;
4  background: #f8f9fa;
5}
6
7.responsive-component .title {
8  font-size: 1.2rem;
9  margin-bottom: 1rem;
10}
11
12.responsive-component .content {
13  display: block;
14}
15
16/* Container Queries jako enhancement */
17@supports (container-type: inline-size) {
18  .responsive-container {
19    container-type: inline-size;
20  }
21  
22  @container (min-width: 400px) {
23    .responsive-component {
24      padding: 2rem;
25    }
26    
27    .responsive-component .title {
28      font-size: 1.5rem;
29    }
30    
31    .responsive-component .content {
32      display: flex;
33      gap: 1rem;
34    }
35  }
36}

Jednostki Container Query

Container Queries wprowadzają specjalne jednostki, które odnoszą się do rozmiaru kontenera:

| Jednostka | Opis | |-----------|------| |

cqw
| 1% szerokości kontenera (container query width) | |
cqh
| 1% wysokości kontenera (container query height) | |
cqi
| 1% rozmiaru inline kontenera (container query inline) | |
cqb
| 1% rozmiaru block kontenera (container query block) |

1@container (min-width: 400px) {
2  .card-title {
3    font-size: 5cqw; /* 5% szerokości kontenera */
4  }
5}

Jednostki te pozwalają na płynne skalowanie elementów w zależności od rozmiaru kontenera, podobnie jak

vw
/
vh
działają względem viewport.

Container Queries reprezentują przyszłość responsywnego web designu, umożliwiając tworzenie prawdziwie modularnych komponentów, które adaptują się do swojego kontekstu niezależnie od rozmiaru viewport. To narzędzie, które fundamentalnie zmienia sposób myślenia o responsywności w CSS.

Vai a CodeWorlds