Phân tích sâu về DPI và PPI: Từ pixel vật lý đến pixel CSS

Khi mới bắt đầu làm frontend, tôi từng nghĩ rằng `px` trong CSS đơn giản là các chấm nhỏ trên màn hình. Tuy nhiên, một lần nhà thiết kế hỏi tôi: "Tại sao cùng một nút bấm `200px`, hiển thị trên iPhone và điện thoại Android lại có kích thước khác nhau?". Lúc đó, tôi mới nhận ra vấn đề phức tạp hơn nhiều.

Pixel vật lý và Pixel CSS: Mối quan hệ phức tạp

Pixel vật lý giống như những viên gạch Lego nhỏ nhất. Màn hình điện thoại của bạn thực chất được tạo thành từ hàng triệu điểm ảnh này, mỗi điểm ảnh chỉ có thể hiển thị một màu duy nhất. Ví dụ, màn hình iPhone 13 có 2532×1170 pixel vật lý, là những điểm thực tế tồn tại ở cấp độ phần cứng.

Ngược lại, pixel CSS lại là một khái niệm "ảo". Khi bạn viết `width: 200px` trong mã, `px` này không trực tiếp tương ứng với một pixel vật lý. Nó giống như một thước đo ảo mà trình duyệt tự động quy đổi dựa trên đặc tính của thiết bị. Điều này giải thích tại sao cùng một nút bấm `200px` lại trông sắc nét hơn trên màn hình có độ phân giải cao, bởi vì trình duyệt đã "vẽ" 1 pixel CSS lên nhiều pixel vật lý hơn.


/* Viền 1px mà bạn nghĩ */
.border {
  border: 1px solid red;
}
/* Trên màn hình độ phân giải cao, đường viền này có thể được hiển thị bằng 4 pixel vật lý */
  

DPR: Tỷ lệ quy đổi trong thế giới pixel

Một lần, khi thử nghiệm trang hoạt động H5 cho công ty, tôi gặp hiện tượng lạ: một đường kẻ mảnh 2px theo bản thiết kế hiển thị bình thường trên điện thoại Xiaomi nhưng lại trông rất dày trên iPhone. Đây chính là do Tỷ lệ điểm ảnh thiết bị (DPR) gây ra.

DPR giống như tỷ giá hối đoái tiền tệ:

  • Màn hình độ phân giải thường (DPR=1): 1 USD = 1 VNĐ
  • Màn hình độ phân giải cao (DPR=2): 1 USD = 2 VNĐ
  • Màn hình độ phân giải siêu cao (DPR=3): 1 USD = 3 VNĐ

Bạn có thể kiểm tra DPR của thiết bị hiện tại bằng JavaScript:


console.log('Tỷ lệ điểm ảnh thiết bị hiện tại:', window.devicePixelRatio);
  

Dưới đây là bảng tổng hợp DPR phổ biến của các thiết bị:

Loại thiết bị Giá trị DPR điển hình Thiết bị ví dụ
Màn hình PC thông thường 1 Màn hình 1080p
Màn hình Retina 2 iPhone 8/13
Màn hình độ phân giải siêu cao 3 iPhone 12/13 Pro Max
TV 4K 1-2 Dòng Sony X9500H

PPI: Phép màu của mật độ pixel

Có lần đi cửa hàng điện thoại, nhân viên giới thiệu: "Điện thoại này PPI siêu cao, xem video cực nét!". Với tư cách là một kỹ sư, tôi cần hiểu rõ bản chất của điều này.

PPI (Pixels Per Inch - Số pixel trên mỗi inch) giống như mật độ dệt của vải. Với hai tấm vải cùng kích thước, tấm nào có mật độ đường chỉ dày hơn chắc chắn sẽ mịn hơn. Công thức tính khá đơn giản:


PPI = √(Số pixel ngang² + Số pixel dọc²) / Kích thước đường chéo màn hình (inch)
  

Ví dụ: Màn hình iPhone 13 Pro có độ phân giải 2532×1170, kích thước 6.1 inch


PPI = √(2532² + 1170²) / 6.1 ≈ 460
  

Giá trị này đã vượt xa tiêu chuẩn "màn hình Retina" (khoảng 300 PPI), có nghĩa là ở khoảng cách xem thông thường, mắt người đã không thể phân biệt được từng pixel riêng lẻ.

Kỹ thuật thích ứng đa thiết bị thực tế

Trong đợt khuyến mãi lớn Shuang Shi Yi năm ngoái, trang H5 của chúng tôi phải đảm bảo tính nhất quán UI trên hơn 80 mẫu thiết bị. Sau khi gặp vô số khó khăn, tôi đã đúc kết được những kinh nghiệm sau:

Cấu hình viewport chuẩn hóa:


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

Sử dụng Media Query nâng cao:


/* Thích ứng hình ảnh theo DPR */
.hero-image {
  background-image: url('image@1x.jpg');
}

@media (-webkit-min-device-pixel-ratio: 2),
       (min-resolution: 192dpi) {
  .hero-image {
    background-image: url('image@2x.jpg');
  }
}
  

Giải pháp hiện đại cho bố cục REM:


// Đặt kích thước font gốc dựa trên chiều rộng bản thiết kế 375px
document.documentElement.style.fontSize = 
  (window.innerWidth / 375) * 16 + 'px';
  

Tôi nhớ có lần phần tử định vị `fixed` bị rung lắc trên iOS, cuối cùng phát hiện ra vấn đề tương thích của `viewport-fit=cover` bị bỏ qua. Phát triển mobile là vậy, mọi chi tiết nhỏ đều có thể ẩn chứa "ma quỷ".

Nhầm lẫn phổ biến và hướng dẫn khắc phục

Ba lỗi phổ biến mà người mới thường mắc phải:

  1. Vấn đề viền 1px: Viết trực tiếp `1px` sẽ hiển thị thành 2 pixel vật lý trên màn hình độ phân giải cao.
    
    /* Giải pháp */
    .border {
      position: relative;
    }
    .border::after {
      content: "";
      position: absolute;
      left: 0;
      bottom: 0;
      width: 100%;
      height: 1px;
      background: #000;
      transform: scaleY(0.5);
    }
          
  2. Vấn đề hình ảnh mờ: Quên chuẩn bị bộ ảnh `@2x`/`@3x`.
    
    <!-- Giải pháp đề xuất -->
    <img srcset="image@1x.jpg 1x, 
                 image@2x.jpg 2x,
                 image@3x.jpg 3x"
         src="image@1x.jpg">
          
  3. Vấn đề kích thước font bất thường: Bố cục REM không có tác dụng trên các phiên bản Android cũ.
    
    /* Giải pháp tương thích */
    body {
      font-size: 16px;
      font-size: 1rem;
    }
          

Tuần trước, tôi gặp một trường hợp kỳ lạ: DPR của một chiếc Huawei lại là giá trị không nguyên như 1.75. Cuối cùng, tôi chỉ có thể xử lý đặc biệt bằng truy vấn media `min-resolution` của CSS.

Xu hướng tương lai: Khi pixel không còn quan trọng

Với sự phổ biến của màn hình gập, thiết bị VR, những thách thức mới lại xuất hiện. DPI của dòng Samsung Z Fold thay đổi động khi mở ra, còn PPI của Meta Quest 3 đã vượt quá 700. Lúc này, bố cục `px` truyền thống trở nên không đủ khả năng đáp ứng.

Gần đây, tôi bắt đầu thử nghiệm các đơn vị `dvw`/`dvh` của CSS trong dự án:


.container {
  width: 100dvw;
  height: 100dvh;
  padding: max(5dvw, 20px);
}
  

Và công nghệ "hắc ám" Container Queries:


@container (min-width: 380px) {
  .card {
    flex-direction: row;
  }
}
  

Khi làm prototype cho Apple Vision Pro, tôi nhận thấy mật độ pixel cao đến mức cần phải suy nghĩ lại hoàn toàn về khoảng cách tương tác và kích thước các thành phần. Điều này làm tôi nhớ lại cảm giác choáng ngợp khi lần đầu tiếp xúc với màn hình Retina mười năm trước – công nghệ luôn làm lung lay nhận thức của chúng ta.

Thẻ: css DPR PPI viewport Media Queries

Đăng vào ngày 16 tháng 6 lúc 10:22