Skip to main content

· 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

· 7 min read
Lê Sĩ Bích

Một vài lưu ý trước khi bắt đầu.

  • Code demo trong bài sẽ sử dụng rspec, capybara, factory_bot (Ruby/Rails), hướng tới đối tượng Web developer.
  • Bài viết không tránh khỏi thiếu sót, nếu có chỗ nào không đúng, mọi người cứ quăng gạch ở dưới comment :v

Ông cha ta có câu "Dục tốc bất đạt". Trước khi đi vào chi tiết, ta hãy cùng điểm qua một vài khái niệm trước.

Nếu bạn là một developer, chắc hẳn sẽ không lạ lẫm gì khái niệm "testing".

Ví dụ khi bạn lập trình chức năng đăng nhập của 1 trang web. Sau một hồi hì hục code, chắc hẳn bạn sẽ vào trang đó, tiến hành đăng nhập, rồi chờ xem code mình có chạy đúng hay không :v

Tuy nhiên giờ đây, đa số đều sử dụng các automated test script để tự động hóa quá trình test. Hơn thế, nó còn giúp những người maintainer sau này có thể hiểu cái đống hổ lốn code bạn để lại chạy như thế nào.

Test khác với Debug.

Các loại test nên chú ý:

  • Unit test: test riêng lẻ từng class/function, viết khá dễ.
  • Integration test, Feature test: ở đây bắt đầu có sự kết hợp giữa nhiều module với nhau để mô tả 1 (hay nhiều) chức năng của ứng dụng.

Màu sắc đặc trưng:

  • Đỏ: xin chia buồn test của bạn đã tạch =))
  • Xanh: chúc mừng, test đã pass, code của bạn "có thể" đã chạy đúng.

2. TDD - Test Driven Development

Dịch ra thì sẽ là Phát triển với trọng tâm là kiểm thử (test), hay nôm na là test trước code sau.

TDD

  • Trước tiên ta viết các test script và chạy chúng, tất nhiên là sẽ fail vì làm gì có code =))

    VD ta muốn viết 1 hàm tính số Fibonacci:

    Fibonacci

    # spec/fibonacci_spec.rb
    describe "#fibonacci_of" do
    context "one" do
    it "returns 1" do
    expect(fibonacci_of(1)).to eq 1
    end
    end

    context "two" do
    it "returns 1" do
    expect(fibonacci_of(2)).to eq 1
    end
    end

    context "greater than two" do
    it "returns sum of two elements before" do
    expect(fibonacci_of(4)).to eq 3
    end
    end
    end
  • Rồi ta mới bắt tay vào code, chạy test, nếu fail thì lại hì hục sửa, hỏng đâu vá đó. Lúc này bạn chưa cần bận tâm về việc code mình có dễ hiểu/đẹp không.

    Và sau 1 hồi thì cuối cùng test ta cũng pass :v

    # fibonacci.rb
    def fibonacci_of(n)
    case n
    when 1
    1
    when 2
    1
    else
    fibonacci_of(n - 1) + fibonacci_of(n - 2)
    end
    end
  • Ta sẽ nhìn lại đống hổ lốn mà ta vừa viết ra, tỉa tót lại cho đẹp mắt, tách service các thứ. Và nhớ rằng đừng làm cho test đỏ lòm.

    # fibonacci.rb
    def fibonacci_of(n)
    return 1 if [1, 2].include?(n)
    fibonacci_of(n - 1) + fibonacci_of(n - 2)
    end

Lưu ý: Không phải cứ test pass là app của ta đã chạy đúng, việc này còn bao gồm nhiều yếu tố khác như

  • Ta có hiểu đúng yêu cầu của khách hàng không
  • Test của ta đã bao gồm hết các trường hợp có thể chưa

Có thể dễ dàng nhận thấy, hàm #fibonacci_of ở trên sẽ chết ngay lập tức nếu gặp tham số <= 0.

3. BDD - Behavior Driven Development

BDD

Kế thừa người tiền nhiệm TDD với phương châm "Test trước code sau", BDD chỉ khác chút là ta sẽ tập trung vào hành vi người dùng (feature).

Theo đó, ta sẽ viết các feature test trước (mô tả một tính năng của ứng dụng, hoặc usecase/userstory). Với mỗi feature test đó, ta có thể sẽ phải triển khai thêm 1 vài unit test/integration test cho các class/function cần thiết trong feature test.

Ví dụ: Ta muốn xây dựng tính năng tạo album cho ứng dụng quản lý album nhạc.

Trước hết hãy hình dung tính năng này trong đầu:

Admin từ trang index albums (/albums), bấm nút "Add album", 1 form sẽ hiện ra để nhập thông tin album (bao gồm title và artist name).

Nếu anh admin này nhập đúng, hãy redirect sang trang index và hiển thị album mới này, còn không thì hãy hiện lỗi để anh admin còn biết đường mà lần.

Feature test của ta sẽ như sau:

# spec/features/create_album_spec.rb
RSpec.feature "album creating process", type: :feature do
context "all fields are filled correctly" do
it "create a new album" do
visit '/albums'
click_link 'Add album'

within 'form#album_form' do
fill_in 'Title', with: 'Chay ngay di'
fill_in 'Artist name', with: "Sep'ss"
end
click_button 'Create'

expect(page).to have_content('Chay ngay di')
end
end

context "title is blank" do
it "show errors" do
visit '/albums'
click_link 'Add album'

within 'form#album_form' do
fill_in 'Artist name', with: "Sep'ss"
end
click_button 'Create'

expect(page).to have_content("Title can't be blank")
end
end
end

Nếu làm theo đúng flow của Rails, ta sẽ phải viết unit test cho:

  • Controller: index, create - test xem logic bên trong đã đúng chưa, status code ra sao.
  • View: new, index - test xem trường hợp có lỗi thì form hiển thị sao, hay trường hợp không có album thì view index có thông báo cho người dùng biết hay không.
  • Model: album - test xem validate có hoạt động hay không.

Chi tiết thì mình xin lược bớt vì dài quá, bạn có thể tham khảo thêm ở đây.

Nếu bạn băn khoăn nên test cái gì trong Rails, có thể tham khảo bài này.

3. Các nguyên tắc khi viết test (TDD)

  1. Chỉ viết code khi nó cần thiết để test của bạn pass.

    VD bạn viết một hàm lấy về email của người dùng theo id.

    def get_user_mail_by_id(id)
    user = User.find(id)
    user.mail
    end

    Ở đây bắt buộc phải truy vấn CSDL để tìm ra user. Không dùng find thì lấy thông tin user thế nào :v

  2. Chỉ viết nên unit test trong phạm vi vừa đủ.

4. Ưu điểm

  • Những người não to trên thế giới đã chứng minh được TDD giúp thay đổi mindset của bạn, và bạn sẽ trở thành những lập trình viên tốt hơn.

    Nó đòi hỏi bạn phải thu tập trung hơn vào những chi tiết nhỏ để test của mình pass, hơn là suy nghĩ vẩn vơ về cả cái app to đùng.

  • Ngoài kiểm thử, test cũng là docs cho maintainer sau này, nó cung cấp chi tiết về các đặc tả kỹ thuật (specification) của app.

  • Ít bug hơn, dễ bảo trì, phát hiện lỗi sớm.

  • Cho bạn biết code mà bạn vừa viết xong nó làm hỏng cả app không =))

5. Kết luận

  • TDD/BDD thực sự rất tốt, nếu bạn muốn trở thành một lập trình viên tốt hơn, hãy cố gắng tryhard :v

  • Thường mọi người hay có xu hướng code xong mới viết test, và mình cũng là một trong số đó =)). Tuy vậy, hãy cố gắng viết test case của mình thật minh bạch, dễ hiểu, vì chính bản thân ta và các maintainer sau này.

6. Tham khảo