Skip to main content

2 posts tagged with "frontend"

View All Tags

· 9 min read
Lê Sĩ Bích

Ở kì trước, ta đã cùng nhau tìm hiểu về Pixel của CSS, đó là 1 đơn vị độc lập, không bị phụ thuộc vào độ phân giải hay các thông số khác của màn hình. Đối với một loại màn hình (xét theo khoảng cách từ mắt tới màn hình), nó luôn là 1 hằng số không đổi. Điều đó đã giúp cho lập trình viên Web dễ thở hơn rất nhiều.

Ở Android cũng có một đơn vị giống như vậy, Google đặt tên cho nó là DP (hay DIP - Density Independent Pixel). Ngoài ra đơn vị này còn dùng trong React Native [link]

2. Một số khái niệm cần nhắc lại

Pixel thiết bị:

Là 1 điểm ảnh, nó phụ thuộc vào kích thước màn hình, và độ phân giải màn hình. Thông thường, 1px thiết bị trên các màn hình đều có giá trị khác nhau.

Pixel này là pixel vật lý của thiết bị khác hoàn toàn so với đơn vị px mà ta hay sử dụng trong CSS.

Độ phân giải thiết bị - Resolution:

Các bạn thường thấy màn hình có độ phân giải 4k, HD, FullHD, kèm theo nó là 2 con số có dạng P1xP2 phải không nào?

Những con số này thể hiện số pixel có trên màn hình, P1 là số pixel phân bố theo chiều dài, còn P2 là theo chiều rộng.

(ảnh chỉ mang tính chất minh họa, mình k phải seeder đâu nhá =)))

TV trên có chiều dài 52.4 inch, có 3840px theo chiều ngang. 1px sẽ có giá trị

1px = 52.4 / 3840 = 0.01365 (inch)

DPI, PPI:

DPI - Dots Per Inch. PPI - Points Per Inch

Chúng đều là số pixel/điểm ảnh có trên 1 inch độ dài vật lý của thiết bị, biểu thị mật độ pixel trên màn hình.

DPI thấp, 1px có kích thước lớn, hình ảnh có thể sẽ bị răng cưa, không sắc nét bằng DPI cao

Lấy lại VD màn hình TV trên:

DPI = 3840 / 52.4 = 73.28 (pixels/inch)

3. Density Independent Pixels

3.1 Đặt vấn đề

Các điện thoại Android có nhiều loại kích thước (phone, tablet, TV, ...), hơn nữa chúng lại còn có độ phân giải khác nhau, dẫn đến kích thước pixel luôn thay đổi, gây khó khăn cho nhà phát triển khi dựng giao diện.

Lấy ví dụ đơn giản nhất về 2 điện thoại có cùng kích thước đường chéo 5inch, nhưng 1 chiếc FullHD 1080x1920, 1 chiếc 4K 2160 x 3840.

Nếu bạn là dev, bạn sẽ muốn 1 button trong app của mình hiển thị trên 2 điện thoại này giống nhau phải không nào?

Thế nhưng nếu làm việc với px thiết bị, ta sẽ gặp 1 chút rắc rối vì với cùng 1 kích thước, điện thoại 4K luôn cần gấp đôi số px so với FullHD.

2 cái đã mệt, trên thực tế, ta có ty tỷ loại màn hình, vậy thì style sao cho xuể, chưa kể còn đến tối ưu các file ảnh cho app.

Chính vì vậy, dp ra đời.

3.2. DP

Google định nghĩa nó như sau:

One dp is a virtual pixel unit that's roughly equal to one pixel on a medium-density screen (160dpi; the "baseline" density). Android translates this value to the appropriate number of real pixels for each other density.

Nếu bạn đã đọc bài trước của mình về CSS pixel, thì có thể thấy nó khá quen thuộc phải không nào, đây cũng là 1 virtual unit.

1dp tương đương 1 px trên màn hình 160dpi. Có nghĩa là 1dp = 1 / 160 = 0.00625 (inch) = 1px (160dpi)

Để quy đổi dp sang px trên các loại màn hình khác, ta nhân dp với 1 số, gọi là scale factor (tỉ lệ DPI của màn hình so với chuẩn 160dpi).

Ví dụ máy 320dpi. 1dp = 1 * 320 / 160 = 2px

Android sẽ tự động convert sang pixel thiết bị cho ta, nên ta chỉ cần dùng dp là trên mọi thiết bị, phần tử của ta sẽ đều có kích thước bằng nhau (ngoại lệ sẽ đề cập sau).

Khi ta dựng UI, nên tận dụng tối đa dp, tránh sử dụng px.

Bên cạnh đó, Google cũng khuyên ta không nên sử dụng dp cho text, mà hãy dùng sp. Đây cũng là một loại density-independent, nhưng nó có thể bị ảnh hưởng bởi tùy chọn của người dùng. Người dùng chọn cỡ chữ to, thì 1sp cũng lớn lên theo. Vì vậy, cũng tránh dùng sp khi thiết kế layout.

3.3 Density Bucket

dp ra đời, nhưng cũng chưa giải quyết được hết các vấn đề. Đó chính là về resource hình ảnh. Style cho ảnh thì có thể sử dụng dp được (ảnh sẽ bị kéo to, hoặc co lại cho đủ size cần thiết), nhưng resource thì đâu có thể to nhỏ tùy ý được, thông số của nó luôn là cố định.

Lấy ví dụ về 1 hình ảnh 500x500px (resolution), với 2 màn hình có kích thước bằng nhau, nhưng khác độ phân giải (khác DPI), 1 chiếc là 360x640, 1 chiếc là 720x1280.

Hình ảnh trên nếu hiển thị trên màn hình 360x640 thì chẳng phải sẽ dư thừa sao? Vì chiều rộng màn hình chỉ có 360px. Còn trên màn hình 720x1280, thì nó chả đáng là bao. Vì vậy ta cần sử dụng hình ảnh có độ phân giải thấp hơn cho màn hình 360x640.

Nhưng không lẽ cứ mỗi độ phân giải lại tạo 1 hình ảnh tương ứng? Vậy thì app size của ta sẽ bùng nổ luôn.

Vì lẽ đó mà ra đời density bucket.

Để cho đơn giản, các loại màn hình có DPI gần bằng nhau sẽ được làm tròn rồi gom vào 1 nhóm. Ví dụ như máy 293dpi, Android sẽ coi như máy đó là 320dpi, khi đó 1dp = 2px. Còn nhiều loại khác nữa, nhưng mình chỉ nêu ra 4 bucket đáng chú ý, best practice là support cả 4 bucket này.

Bucket nameDensity bucketDPIScale Factor
Mediummdpi1601.00
Highhdpi2401.50
Extra Highxhdpi3202.00
Extra Extra Highxxhdpi4803.00

Những gì bạn cần làm là ném hết hình ảnh của các bucket vào thư mục tương ứng. Android sẽ tự detect và chọn hình ảnh tối ưu nhất cho bạn (lấy từ DPI cao nhất về).

Cũng vì việc làm tròn DPI này, mà 1dp của ta trên các thiết bị có thể không bằng nhau, nhưng luôn xấp xỉ 1/160 = 0.00625 inch

DensityBucketScale factorPhysical Size (inch)
293dpixhdpi - 320dpi21 * 2 / 293 = 0.00686
320dpixhdpi21 * 2 / 320 = 0.00625
330 dpixhdpi21 * 2 / 330 = 0.00606

Điều này cũng đem đến cho ta 1 lợi ích, đó là với các số máy density khác nhau, nhưng cùng độ phân giải, kích thước bề ngang của ta tính bằng dp đều bằng nhau. Lấy ví dụ 3 máy 720x1280, 1 máy 293 dpi, 1 máy 320 dpi, 1 máy 330 dpi.

DensityWidth (without Density Bucket)Width (with density bucket)
293dpi720 / (293 / 160) = 393dp360dp
320dpi720 / (320 / 160) = 360dp360dp
330dpi720 / (330 / 160) = 349dp360dp

4. Ví dụ

Sau khi nhồi 1 đống lý thuyết vào đầu, ta hãy thử làm 1 bài tập nho nhỏ, để áp dụng chúng. Hãy thử tự tính toán thông số cho điện thoại của mình xem.

Điện thoại mình 5inch, độ phân giải 720x1280.

Ta dễ dàng tính toán được chiều dài và chiều rộng của máy, dựa vào định lý Pytago:

5**2 = (1280 * x)**2 + (720 * x)**2
width = 720 * x = 2.4513 (inch)
height = 1280 * x = 4.3579 (inch)

Từ đó ta có thể tính tất cả các thông số khác:

Thông sốGiá trị
Device density720 / 2.4513 = 294 dpi
Density bucketxhdpi - 320dpi
Scale factor320 / 160 = 2.00
Width720 / 2 = 360dp
Height1280 / 2 = 640dp
DIP1dp = 2px = 0.0068 (inch)

5. Kết luận

Sự ra đời của dpdensity bucket quả thực đã giúp ta tránh được không ít rắc rối trong khi phát triển ứng dụng Android, tối ưu hình ảnh cho app. Bài viết không tránh khỏi sai sót, nếu có sạn, hãy cứ góp ý, mình sẽ tiếp thu và chỉnh sửa cho phù hợp.

Đây có lẽ là bài cuối của mình trong series về những thứ bé nhỏ mà ít ai để ý trong khi thiết kế giao diện Web, mobile. Cám ơn mọi người đã đọc bài! - nếu các bạn đọc đến tận dòng này =))

6. Link tham khảo

  1. https://developer.android.com/training/multiscreen/screendensities
  2. https://www.highgroundgaming.com/tas/gaming-mouse-guide/low-vs-high-dpi-example/
  3. https://facebook.github.io/react-native/docs/height-and-width.html#fixed-dimensions
  4. https://www.avconcepts.com/2015/09/24/4k-ultra-hd-vs-hd-projection/
  5. https://www.captechconsulting.com/blogs/understanding-density-independence-in-android
  6. https://medium.com/@sashaserg/a-mysterious-density-independent-pixel-a-quick-introduction-to-android-design-111d68be7cf5

· 7 min read
Lê Sĩ Bích

Nếu là một nhà phát triển ứng dụng Web hay Android, thì chắc hẳn bạn đã dùng các đơn vị độ dài như px, dp để tạo style cho các phần tử trên giao diện.

Có lẽ nhiều người sẽ nghĩ rằng: Màn hình mình FullHD, độ phân giải 1920x1080, thì cứ 1920px mà vã vào css thôi, nghĩ ngợi làm gì nhiều. Nhưng liệu có thật là như vậy không? Chúng ta hãy cùng tìm hiểu

TL;DR

1px = 1/96 inch

1dp = 1/160 inch

2. Pixel - nguyên tử của thế giới Web

Lưu ý: Trước tiên, ta cần phải làm rõ 1 điều: 1px của CSS khác với 1px của thiết bị (độ phân giải). 1px CSS có thể tương ứng với 1 hoặc nhiều px của thiết bị.

Có rất nhiều loại màn hình, độ phân giải, kích thước khác nhau. Nó làm khó lập trình viên trong việc dựng giao diện, được màn hình này thì lại hỏng màn hình kia. Vì vậy w3 đã quy ước 1 đơn vị chuẩn, dùng cho mọi thiết bị: px.

Định nghĩa trên w3

1px = 1/96th of 1in

Tuy nhiên, khi nhìn xuống dưới một chút, ta có thể thấy thêm

For a CSS device, these dimensions are anchored either:

​ i. by relating the physical units to their physical measurements, or

​ ii. by relating the pixel unit to the reference pixel.

Nhìn sơ qua thì khá là khó hiểu, nhưng ta có thể hình dung được 1 điều, pixel có thể được tính toán theo 2 cách.

2.1 By relating the physical units to their physical measurements

Ý đầu là dành cho các thiết bị đòi hỏi độ chính xác cao về độ dài, ví dụ như máy in. Ta phải sử dụng các đơn vị inch, cm, ... đúng như độ dài thực tế của chúng. Khi đó, 1px sẽ tương đương 0.267mm (25.6/96). Ta sẽ không đề cập đến những loại thiết bị này trong bài.

2.2 By relating the pixel unit to the reference pixel

Đối với các thiết bị trình chiếu (bất kể độ phân giải, màn hình Retina hay thứ gì thần thánh khác), các thiết bị mà có khoảng cách nhìn bất thường, px sẽ là đơn vị để tính ra inch, cm, ...

Và w3 khuyên rằng, nên sử dụng reference pixel để tính ra tỉ lệ quy đổi 1px CSS này với số pixel thiết bị (màn hình 4096 × 2160 có thể sẽ không còn là 4096px trong CSS nữa). Hay nói cách khác là tính xem 1px CSS bằng bao nhiêu.

Lại một đơn vị hại não nữa, và w3 định nghĩa nó như sau:

The reference pixel is the visual angle of one pixel on a device with a pixel density of 96dpi and a distance from the reader of an arm’s length. For a nominal arm’s length of 28 inches, the visual angle is therefore about 0.0213 degrees. For reading at arm’s length, 1px thus corresponds to about 0.26 mm (1/96 inch).

Như vậy, reference pixel là một visual angle của 1px trên màn hình 96dpi. Khoảng cách nhìn là 1 sải tay (khoảng 28in đối với người bình thường) - đây là khoảng cách ngồi PC thông thường.

Từ khái niệm này lại đẻ ra khái niệm khác :v Ta hãy bắt đầu với DPI

DPI (Dots Per Inch): tương đương với PPI (Pixels Per Inch), nó cho ta biết 1inch màn hình thiết bị có bao nhiêu pixel thiết bị. 96dpi nghĩa là 1inch có 96 pixel. Như vậy 1px của màn hình 96dpi sẽ là 2.56 / 96 ~ 0.26mm

DPI càng lớn, màn hình hiển thị càng chi tiết. DPI được tính dựa trên kích thước thiết bị và độ phân giải của thiết bị.

VD màn hình công ty mình dùng là Dell P2319H, 23in (58,42cm), độ phân giải 1920x1080

Áp dụng Pytago: ta có

(1920x)^2 + (1080x)^2 = 58.42^2 (cm^2)

Tính nhanh, ta được x = 0.0265cm. Như vậy, máy mình có chiều rộng là 1920x = 19.89in, và DPI = 1920 / 19.89 = 96.53

Visual angle là góc v được biểu thị dưới hình sau:

Áp dụng vào, bấm máy tính:

v = 2 x arctan((2.56/96) / (2x28x2.56)) => 0.0213 (độ)

Với reference pixel là 0.0213 độ, khoảng cách nhìn 28in, 1px CSS luôn có giá trị là 0.26mm. Vì vậy giữa 2 màn hình PC cùng kích thước, nhưng có độ phân giải khác nhau (ví dụ 1 cái FullHD, 1 cái 4K), 1 element có width 10px sẽ luôn có kích thước vật lý bằng nhau (2.6mm).

Với màn hình FullHD, 1px CSS sẽ ứng với 1px của thiết bị, còn với màn Retina căng đét, 1px CSS ứng với 2px thiết bị, nhưng kích thước thật của chúng đều là 0.26mm.

Khoảng cách tới màn hình càng cao, độ lớn của 1px càng tăng lên. Ở khoảng cách 3.5m, 1px có giá trị lên tới 1.3mm, và 1in lúc này khoảng 124.8mm (gấp gần 5 lần giá trị thực).

Loanh quanh một hồi, ta vẫn chưa giải thích được vấn đề ban đầu: Ta có thể coi 1px trên màn hình PC là 1px CSS hay không?

Câu trả lời là có. Bởi vì màn hình PC, laptop hiện nay đa số đều có độ phân giải khoảng 96dpi, nên 1px thiết bị vừa đúng với 1px CSS.

3. Pixel trên trình duyệt Mobile

Liệu các bạn có bao giờ thắc mắc: Màn hình điện thoại toàn HD 720x1280, FullHD 1080x1920, ... mà sao khi responsive lại phải sử dụng media query < 576px?

Chắc hẳn các bạn đã tự có câu trả lời cho mình. Vì khi sử dụng reference pixel, 1px trên điện thoại sẽ không còn là 1px của CSS nữa.

Do DPI điện thoại cao chăng? Ví dụ điện thoại mình có DPI là 294, độ phân giải 720x1280, vậy thì 1px CSS (0.26mm) = 3px thiết bị (0.087mm). Vậy thì là 240x427?

Không, một phần đúng là do DPI, và 1 lý do nữa là vì ta cầm điện thoại gần hơn nhiều so với khoảng cách ngồi trước màn hình PC, Laptop (khoảng 18in)

Áp dụng định lý Talet trong tam giác, ta tính được 1px CSS giờ chỉ còn:

18 * 0.26 / 28 = 0.167mm

Lại lấy điện thoại 5in, độ phân giải 720x1280 của mình, 1px = 0.087mm

Dễ thấy 1px CSS = 2px thiết bị. Vì thế màn hình rộng 720px của ta giờ chỉ còn 360px trong mắt CSS. Tỉ lệ 2:1 này, gọi là devicePixelRatio

Con số này sẽ được nhà sản xuất tính toán và cung cấp khi sản xuất 1 thiết bị nào đó. Với Javascript, ta có thể dùng hàm sau để lấy được devicePixelRatio:

window.devicePixelRatio;

4. Kết luận

Thế giới Frontend khá hại não với các loại màn hình khác nhau, nên người ta đã phải quy ước 1 đơn vị chuẩn dùng cho mọi thiết bị trong cùng 1 khoảng cách nhìn. Điều này đã giúp anh em dev chúng ta dễ thở hơn nhiều.

Hy vọng bài viết này giúp mọi người hiểu thêm về đơn vị px mà mình vẫn sử dụng thường ngày.

Định viết thêm cả về dp trong Android mà cảm thấy bài dài quá rồi, có lẽ mình sẽ để kỳ tới, mong các bạn ủng hộ.

5. Tham khảo