Menu

Media query trong CSS: breakpoint, min-width và max-width

Media query chỉ áp dụng CSS khi một điều kiện đúng, chẳng hạn màn hình rộng tối thiểu bao nhiêu, giao diện tối hay con trỏ chuột. Tìm hiểu cú pháp @media, breakpoint theo hướng mobile first, cú pháp khoảng, prefers-color-scheme, matchMedia trong JavaScript và container query.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Media query qua một ví dụ

Media query bọc CSS trong một điều kiện. Các quy tắc bên trong @media (min-width: 600px) chỉ được áp dụng khi viewport rộng ít nhất 600px:

Bản xem trước là một khung, và media query bên trong nó đo khung đó chứ không đo màn hình của bạn. Trên máy tính, khung rộng gần bằng cột bài viết này, thường hơn 600px, nên hộp có màu xanh. Thu hẹp cửa sổ trình duyệt đến khi khung xuống dưới 600px thì hộp chuyển sang màu hổ phách; dòng dưới hộp báo chiều rộng của khung theo thời gian thực. Trên điện thoại, khung vốn đã hẹp nên bạn thấy phiên bản màu hổ phách.

Cú pháp @media

@media (min-width: 600px) { ... }                        /* one condition */
@media (min-width: 600px) and (max-width: 899px) { ... } /* both must be true */
@media (max-width: 599px), print { ... }                 /* either one (comma = or) */
@media not print { ... }                                 /* negation */
@media screen and (orientation: landscape) { ... }       /* media type plus a feature */
@media (width >= 600px) { ... }                          /* range syntax */
@media (600px <= width < 900px) { ... }                  /* a range with both ends */

and cần dấu cách ở cả hai bên: (min-width: 600px)and(max-width: 900px) không hợp lệ và cả truy vấn bị bỏ qua. Cú pháp khoảng (width >= 600px) được hỗ trợ trong Chrome, Firefox và Safari hiện nay và dễ đọc hơn min-width, nhất là với trường hợp "đến nhưng không bao gồm".

Những đặc điểm bạn sẽ dùng nhiều nhất:

Đặc điểmKhớp khiVí dụ
min-width / width >=viewport rộng ít nhất bằng giá trị này(min-width: 768px)
max-width / width <=viewport rộng nhiều nhất bằng giá trị này(max-width: 767px)
orientationportrait (cao hơn rộng) hoặc landscape(orientation: landscape)
hovercon trỏ chính có thể rê (hover) hoặc không (none, phần lớn màn hình cảm ứng)(hover: hover)
pointercon trỏ chính là fine (chuột) hoặc coarse (ngón tay)(pointer: coarse)
prefers-color-schemengười dùng chọn giao diện dark hoặc light(prefers-color-scheme: dark)
prefers-reduced-motionngười dùng yêu cầu hệ thống giảm chuyển động(prefers-reduced-motion: reduce)
print (một loại media)trang được in hoặc lưu thành PDF@media print

Mobile first: breakpoint min-width

Viết bố cục cho điện thoại làm CSS cơ bản, rồi thêm các truy vấn min-width để đổi bố cục khi có thêm chỗ. Mỗi truy vấn chỉ cần mô tả những gì thay đổi:

Trong cửa sổ máy tính rộng, bản xem trước rộng hơn 720px nên bạn thấy ba cột; dòng dưới lưới báo chiều rộng và số cột. Thu hẹp cửa sổ thì lưới còn hai cột khi dưới 720px và một cột khi dưới 480px. Thứ tự của hai truy vấn rất quan trọng: trên màn hình rộng cả hai đều đúng, và truy vấn viết sau sẽ thắng, nên các truy vấn min-width đi từ nhỏ đến lớn. (Với truy vấn max-width thì ngược lại: lớn nhất trước.)

Chọn breakpoint

Không có danh sách chính thức. Đây là các giá trị phổ biến và những gì chúng thường phân tách:

BreakpointThường dành cho
min-width: 640pxđiện thoại lớn khi xoay ngang, máy tính bảng nhỏ
min-width: 768pxmáy tính bảng khi dựng đứng
min-width: 1024pxmáy tính bảng khi xoay ngang, laptop nhỏ
min-width: 1280pxmáy tính để bàn

Một cách tốt hơn: bắt đầu từ bố cục hẹp, kéo rộng cửa sổ đến khi có gì đó trông không ổn (dòng quá dài, thẻ quá rộng), rồi đặt breakpoint ở đó. Hai hoặc ba breakpoint là đủ cho phần lớn trang. Nhiều khi bạn chẳng cần cái nào: repeat(auto-fit, minmax(200px, 1fr)) trong grid, flex-wrap và clamp() tự thích ứng liên tục.

Trang nào cũng cần thẻ meta viewport

Thiếu thẻ này trong <head>, trình duyệt điện thoại sẽ dàn trang như thể màn hình rộng khoảng 980px rồi thu nhỏ lại, nên truy vấn min-width: 480px luôn đúng và bố cục cho điện thoại không bao giờ hiện ra:

<meta name="viewport" content="width=device-width, initial-scale=1">

Trang meta viewport giải thích từng giá trị trong thẻ này.

Dark mode với prefers-color-scheme

prefers-color-scheme đi theo cài đặt sáng hoặc tối của hệ điều hành. Cách gọn nhất là giữ màu trong biến CSS và chỉ đổi các biến bên trong truy vấn:

Trong bản xem trước này, truy vấn có thể in ra false dù hệ thống của bạn đang để chế độ tối: Chrome không truyền cài đặt hệ thống vào một khung sandbox như thế này. Nút bấm đặt chính các biến đó bằng tay, giống cách nút đổi giao diện của một trang web hoạt động. :root[data-theme] có độ ưu tiên cao hơn :root thường bên trong truy vấn, nên lựa chọn của người đọc thắng cài đặt hệ thống. Hãy lưu ví dụ thành tệp rồi mở ra để thấy truy vấn đi theo hệ thống của bạn.

Đoạn script cho thấy nửa còn lại của media query: matchMedia() chạy cùng truy vấn đó từ JavaScript, trả về việc hiện tại nó có khớp hay không, và phát sự kiện change khi kết quả thay đổi. Hãy dùng nó khi hành vi (chứ không chỉ giao diện) phụ thuộc vào màn hình, chẳng hạn mở menu theo cách khác trên thiết bị cảm ứng.

Chuyển động cũng làm tương tự. Hãy bọc các animation lớn hoặc lặp lại trong một điều kiện kiểm tra prefers-reduced-motion, như trong trang animation.

Container query: khi component tự quyết định

Media query chỉ biết viewport. Một thẻ nằm trong cột chính rộng và cả trong thanh bên hẹp trên cùng một trang thì cần biết khoảng trống của chính nó. Đó là container query: đánh dấu phần tử cha bằng container-type: inline-size, rồi dùng @container với những điều kiện tương tự.

Kéo góc của hộp xám để đổi kích thước. Thẻ đổi bố cục khi chiều rộng hộp là 360px, bất kể cửa sổ rộng bao nhiêu:

Ở chiều rộng ban đầu 480px, hình nằm cạnh chữ. Kéo hộp hẹp hơn 376px (360px nội dung cộng 8px padding mỗi bên) thì hình chuyển lên trên chữ. Container query hoạt động trên mọi trình duyệt lớn hiện nay.

Style khi in

@media print chỉ áp dụng khi trang được in hoặc lưu thành PDF. Hãy ẩn thanh điều hướng và các nút, và dùng chữ tối trên nền trắng:

@media print {
  nav, .ad, button { display: none; }
  body { color: black; background: white; }
  a::after { content: " (" attr(href) ")"; } /* print the URL after each link */
}

Lỗi thường gặp

  • Thiếu thẻ meta viewport. Khi đó điện thoại báo một layout viewport rộng và các truy vấn cho màn hình nhỏ không bao giờ khớp.
  • Truy vấn sai thứ tự. Hai truy vấn cùng đúng với cùng một selector: truy vấn viết sau thắng. Hãy xếp các truy vấn min-width từ nhỏ đến lớn.
  • Ranh giới chồng chéo hoặc bị hở. max-width: 768px và min-width: 768px cùng khớp ở đúng 768px. Hãy dùng 767px cho một trong hai, hoặc cú pháp khoảng (width < 768px) và (width >= 768px).
  • Thiếu dấu cách sau and. and( không hợp lệ và truy vấn bị bỏ đi mà không báo gì.
  • Chỉ thử bằng cách thu nhỏ cửa sổ máy tính. Hãy kiểm tra thêm trên điện thoại thật hoặc chế độ thiết bị của trình duyệt, vốn áp dụng viewport, tỷ lệ điểm ảnh và cảm ứng của điện thoại.
  • Quá nhiều breakpoint. Nếu component nào cũng cần breakpoint riêng, hãy thử grid auto-fit, flex-wrap hoặc container query.

Câu hỏi thường gặp

Media query trong CSS là gì?

Là một quy tắc chỉ áp dụng phần CSS bên trong khi một điều kiện về thiết bị hoặc cửa sổ là đúng. @media (min-width: 600px) { ... } chỉ áp dụng style của nó khi viewport rộng ít nhất 600px, và đó là cách một trang thích ứng từ điện thoại đến máy tính.

Nên dùng media query min-width hay max-width?

Phần lớn trang web viết theo hướng mobile first: CSS cơ bản dành cho màn hình nhỏ và các truy vấn min-width bổ sung bố cục khi màn hình lớn dần. Truy vấn max-width làm theo chiều ngược lại, bắt đầu từ bố cục máy tính. Cách nào cũng được; trộn cả hai trên cùng một phần tử sẽ gây chồng chéo và khoảng hở ở các ranh giới.

Các breakpoint media query chuẩn là bao nhiêu?

Không có chuẩn chính thức. Các giá trị hay dùng là khoảng 640px, 768px, 1024px và 1280px, nhưng quy tắc tốt hơn là thêm breakpoint ở chỗ bố cục của chính bạn bắt đầu trông không ổn, và giữ số breakpoint ít.

Vì sao media query không hoạt động trên điện thoại?

Nguyên nhân phổ biến nhất là thiếu thẻ meta viewport. Không có <meta name="viewport" content="width=device-width, initial-scale=1">, trình duyệt di động coi như trang rộng khoảng 980px, nên các truy vấn cho chiều rộng nhỏ không bao giờ khớp. Hãy kiểm tra thêm rằng truy vấn nằm sau các quy tắc mà nó ghi đè.

Viết media query cho dark mode thế nào?

Dùng @media (prefers-color-scheme: dark) { ... }. Truy vấn này khớp khi hệ điều hành hoặc trình duyệt của người dùng đặt giao diện tối, và bạn có thể ghi đè màu sắc hay biến CSS bên trong nó.

Media query và container query khác nhau thế nào?

Media query kiểm tra viewport (toàn bộ cửa sổ). Container query, viết bằng @container, kiểm tra kích thước của một phần tử cha mà bạn đánh dấu bằng container-type: inline-size. Hãy dùng container query cho component xuất hiện ở cả chỗ rộng lẫn chỗ hẹp trên cùng một trang.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu