Chuyển đến nội dung
MINH VO Ghi chép từ công việc
của một kỹ sư tại Việt Nam
Cơ bản9 phút đọc

Thuộc tính logic cho bố cục đổi hướng chữ

Theo hồ sơ qua LTR và RTL, giải xung đột padding vật lý và logic, rồi sửa inset vẫn dùng phép dịch theo chiều vật lý.

Mô hình giấy màu ô liu và be thể hiện trang trình duyệt trên máy tính và trang hẹp trên điện thoại

Khoảng trống giữa hình đại diện và tên thuộc về quan hệ giữa hai mục ấy. Nó không mặc nhiên thuộc bên trái. Thuộc tính CSS logic cho component mô tả quan hệ với dòng chữ, còn thuộc tính vật lý giữ ý nghĩa trên, phải, dưới và trái. Muốn chọn đúng, hãy bắt đầu từ quan hệ mà thiết kế cần.

Ví dụ dành cho lập trình viên frontend đã biết Flexbox và khoảng cách cơ bản. Ta đổi hồ sơ giả định giữa bố cục chữ ngang trái sang phải và phải sang trái, xem các khai báo padding cạnh tranh, rồi sửa một lỗi định vị tuyệt đối. Bản xem thử chỉ đổi hướng. Tiếng Anh vẫn là tiếng Anh, tiếng Việt vẫn là tiếng Việt; cả hai không đại diện cho một sản phẩm đã bản địa hóa đầy đủ.

Dựng bản xem thử bằng điều khiển gốc

Đặt HTML và CSS sau vào trang riêng có khai báo viewport responsive thông thường, <meta name="viewport" content="width=device-width, initial-scale=1">. Select chỉ điều khiển vùng ví dụ. Hai đích liên kết là fragment thật trong cùng tài liệu.

<main class="logical-demo">
  <h1>Khoảng cách đi theo hướng đọc</h1>
  <label for="direction-choice">Hướng chữ xem thử</label>
  <select id="direction-choice">
    <option value="ltr">Trái sang phải</option>
    <option value="rtl">Phải sang trái</option>
  </select>
  <div id="profile-preview" dir="ltr">
    <article class="profile-row">
      <span class="profile-mark" aria-hidden="true">SE</span>
      <div class="profile-copy">
        <h2><bdi>Biên tập viên mẫu</bdi></h2>
        <p>Hồ sơ giả định để kiểm tra khoảng cách và thứ tự bàn phím.</p>
        <nav aria-label="Biên tập viên mẫu">
          <a href="#profile-notes">Đọc ghi chú</a>
          <a href="#profile-details">Xem chi tiết</a>
        </nav>
      </div>
    </article>
  </div>
  <p id="profile-notes">Liên kết đầu vẫn đứng đầu trong thứ tự nguồn.</p>
  <p id="profile-details">Xem thử hướng chữ không tự dịch nội dung.</p>
</main>

Chữ viết tắt chỉ trang trí nên bị ẩn khỏi cây khả năng tiếp cận; heading cung cấp tên hồ sơ. Phần tử bdi cô lập hướng chữ của tên mà không đổi hướng bố cục hồ sơ bao quanh. Liên kết giữ hành vi điều hướng gốc, nên có thể kiểm tra trực tiếp thứ tự nguồn và thao tác bàn phím.

body { margin: 0; }
.logical-demo {
  max-inline-size: 42rem;
  margin-inline: auto;
  padding: 1rem;
  color: #303326;
  background: #f4f0e6;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
  overflow-wrap: anywhere;
}
select { font: inherit; max-inline-size: 100%; }
#profile-preview { margin-block: 1.5rem; }
.profile-row {
  box-sizing: border-box;
  inline-size: min(100%, 28rem);
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  gap: 1rem;
  border-inline-start: 4px solid #77824d;
  padding-block: 1rem;
  padding-inline: 1.5rem .75rem;
}
.profile-mark {
  flex: 0 0 auto;
  inline-size: 3rem;
  block-size: 3rem;
  display: grid;
  place-items: center;
  background: #d5dcc1;
}
.profile-copy { flex: 1 1 12rem; min-inline-size: 0; text-align: start; }
.profile-copy h2, .profile-copy p { margin-block: 0; }
.profile-copy p { margin-block: .75rem; }
.profile-copy nav { display: flex; flex-wrap: wrap; gap: .5rem 1rem; }
a { color: inherit; text-underline-offset: .15em; }
a:focus-visible, select:focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 3px;
}

Đặt script này sau markup. Nó đặt thuộc tính hướng chữ của HTML và chạy một lần lúc đầu để bản xem thử khớp giá trị đang chọn. Nó không lưu tùy chọn, đổi ngôn ngữ tài liệu hay viết lại chữ. Khi không có JavaScript, thẻ trái sang phải ban đầu cùng hai liên kết vẫn tồn tại.

const directionChoice = document.querySelector('#direction-choice');
const profilePreview = document.querySelector('#profile-preview');
function updateDirection() {
  profilePreview.dir = directionChoice.value;
}
directionChoice.addEventListener('change', updateDirection);
updateDirection();

Đặc tả Writing Modes khuyến nghị dùng markup hướng chữ của HTML thay vì dựa vào thuộc tính CSS direction cho nội dung HTML. Ở đây thuộc tính của vùng xem thử chỉ rõ ranh giới phép kiểm tra. Bản dịch thật vẫn cần ngôn ngữ phù hợp, xử lý chữ hai hướng và quyết định nội dung theo locale.

Tách trục khỏi cạnh vật lý

Chiều inline và block mô tả dòng chữ. Trong chế độ chữ ngang, trục inline nằm ngang còn trục block thẳng đứng. Direction chọn đầu nào của trục inline là bắt đầu; đổi sang chế độ chữ dọc còn đổi chiều vật lý nào là inline.

Với các trường hợp thông thường dưới đây, dùng text orientation mixed mặc định, phép ánh xạ là:

Chế độ chữ và hướngInline startBlock startInline size
horizontal-tb, ltrtráitrênchiều rộng
horizontal-tb, rtlphảitrênchiều rộng
vertical-rl, ltrtrênphảichiều cao

Hàng thứ ba là phép kiểm tra trục, không phải khuyến nghị xoay hồ sơ tiếng Anh hay tiếng Việt này thành chữ dọc. Chế độ chữ và text orientation khác cần được thử riêng. Đặc biệt, chiều rộng logic không phải từ đồng nghĩa cố định của chiều rộng vật lý.

Với cỡ chữ gốc mười sáu pixel, padding đầu của thẻ là 24 pixel, padding cuối là mười hai, border đầu là bốn. Ở LTR, border và padding lớn hơn nằm bên trái; ở RTL, chúng chuyển sang phải. Padding theo block vẫn là mười sáu phía trên và dưới. Khi thẻ rộng đủ 448 pixel, chiều rộng bên trong là 408; dấu hồ sơ 48 pixel và gap mười sáu để lại 344 cho chữ.

Cùng hồ sơ đặt border đầu, padding đầu lớn hơn và flex item đầu ở bên trái trong LTR, bên phải trong RTL; gap vẫn nằm giữa dấu hồ sơ và phần chữMở ảnh kích thước đầy đủ ↗

Định nghĩa row của Flexbox đi theo trục inline hiện tại. Không cần thêm row-reverse cho RTL. Làm vậy sẽ đảo luồng vốn đã theo hướng chữ thêm lần nữa. Gap mô tả khoảng cách giữa các mục mà không gán margin cho cạnh vật lý nào.

Phần chữ có flex basis mười hai rem và có thể xuống flex line khác khi không thể chia sẻ một hàng hữu ích với dấu hồ sơ. Đó là quyết định nội dung riêng. Chỉ dùng khoảng cách logic chưa ngăn một hình đại diện không co lại ép chữ lớn vào cột rất hẹp.

Quan sát khai báo vật lý và logic dùng chung cạnh

Thuộc tính logic không có ưu tiên cao hơn thuộc tính vật lý. Quy tắc mô hình hộp logic ánh xạ các thuộc tính tương ứng theo chế độ chữ của chính phần tử, rồi xét cascade cho các khai báo ấy cùng nhau. Khi độ ưu tiên bằng nhau, thứ tự khai báo có thể quyết định giá trị thắng.

Thử class này trên hai div chưa có kiểu khác, một cái mang dir="ltr", cái còn lại mang dir="rtl":

.padding-probe {
  padding: 0;
  padding-inline-start: 24px;
  padding-left: 8px;
}

Ở LTR, inline-start ánh xạ sang trái, nên khai báo vật lý phía sau thắng: padding trái là tám, phải là 0. Ở RTL, inline-start ánh xạ sang phải. Hai khai báo lúc này tác động hai cạnh khác nhau: trái là tám, phải là hai mươi bốn. Không có việc quy tắc logic bị bỏ qua khó hiểu; cạnh được ánh xạ đã đổi.

Chỉ đảo vị trí hai khai báo cuối. LTR lúc này có hai mươi bốn pixel bên trái vì khai báo logic đứng sau. RTL vẫn có tám bên trái và hai mươi bốn bên phải. Giữ specificity, layer và importance bằng nhau trong phép so này; thứ tự khai báo không phải toàn bộ cascade.

Phần tử sở hữu thuộc tính cũng quan trọng. Token mã có hướng trái sang phải riêng nằm trong hàng RTL sẽ ánh xạ khoảng cách logic theo hướng của chính nó. Hướng ấy có thể khác cạnh inline-start của cha. Hồ sơ dùng gap ở cha và cô lập phần chữ tên bên trong, tránh margin có quan hệ mong muốn không khớp hướng của phần tử sở hữu.

Inset logic không biến translateX thành logic

Xét hình vuông trang trí hai mươi pixel cần nằm vắt qua cạnh đầu của hộp thử chữ ngang rộng 160 pixel. Hai hộp này cố ý minh họa một lần chuyển đổi còn dở:

<div class="edge-probe" dir="ltr"><span class="dot" aria-hidden="true"></span></div>
<div class="edge-probe" dir="rtl"><span class="dot" aria-hidden="true"></span></div>
.edge-probe {
  position: relative;
  inline-size: 160px;
  block-size: 80px;
  outline: 1px solid #77824d;
  margin: 2rem;
}
.dot {
  position: absolute;
  inline-size: 20px;
  block-size: 20px;
  inset-inline-start: 0;
  inset-block-start: 30px;
  background: #77824d;
  transform: translateX(-50%);
}

Phần trăm dịch chuyển ngang dùng chiều rộng hộp tham chiếu transform của phần tử. Hình vuông không có border hay padding, nên âm năm mươi phần trăm dịch nó mười pixel theo chiều X âm. Chuyển động ấy vẫn sang trái về mặt vật lý khi hướng chữ đổi.

Ở LTR, cạnh trái ban đầu bằng 0, phép dịch tạo khoảng từ âm mười tới dương mười. Tâm nằm đúng trên ranh giới trái. Ở RTL, inset đặt hình vuông từ 140 tới 160 trước phép dịch; dịch sang trái tạo khoảng 130 tới 150. Tâm lúc này nằm sâu hai mươi pixel vào trong so với ranh giới phải mong muốn.

Với hình vuông trang trí có cỡ cố định này, ta có thể mô tả toàn bộ độ lệch theo trục inline:

.dot {
  inset-inline-start: -10px;
  transform: none;
}

Bỏ transform đi, LTR lại trải từ âm mười tới dương mười. RTL lúc này trải từ 150 tới 170, tâm trên ranh giới phải. Inset logic âm bằng nửa inline size đã biết. Nếu cỡ hình vuông đổi, quan hệ ấy cũng phải đổi; đây không phải quy tắc căn giữa tự động cho nội dung tùy ý.

Hộp RTL mới chuyển một phần làm hình vuông dịch trái vào trong; thay phép dịch vật lý bằng inset logic âm mười pixel căn tâm hình vuông lên cạnh inline-start ở cả hai hướngMở ảnh kích thước đầy đủ ↗

Các tọa độ áp dụng cho hộp thử ngang đơn giản không border, không có transform ở tổ tiên. Có thể kiểm tra cùng độ lệch logic trong hộp thử dọc riêng, nơi inline-start thành cạnh trên với vertical-rl và hướng LTR. Đừng suy ra rằng đổi một inset sửa được các phép xoay, gốc transform không liên quan hay mọi phần tử định vị trong sản phẩm.

Giữ thứ tự thông tin khi kiểm tra thứ tự nhìn thấy

Liên kết đầu của hồ sơ vẫn đứng đầu trong DOM ở cả hai hướng. Trong hàng điều hướng RTL đủ rộng, nó xuất hiện bên phải liên kết thứ hai, khớp chiều tiến inline. Tab vẫn phải tới liên kết đầu trước liên kết thứ hai. Đặc tả Flexbox tách rõ luồng nhìn thấy bị đảo khỏi thứ tự điều hướng tuần tự; dùng row-reverse không viết lại trình tự tài liệu.

Cạnh vật lý vẫn đúng khi yêu cầu mang tính vật lý, chẳng hạn căn chú thích vào cạnh trái của biểu đồ giữ nguyên. Tương tự, không nên áp dụng lật CSS đồng loạt cho logo, ảnh chụp hay biểu tượng phát nội dung quen thuộc. Bố cục logic không thay quyết định về ý nghĩa của nội dung.

Để luyện tập có thể tái lập, dùng select gốc đổi hướng, xem giá trị padding và border ở bốn cạnh vật lý, rồi đi qua hai liên kết bằng Tab và Enter. Lặp lại ở chiều rộng 320 CSS pixel với chữ lớn và khoảng cách chữ rộng hơn. Kiểm tra phần chữ xuống hàng cùng vòng focus, không chỉ xem hộp ngoài vừa trang chưa. Sau đó tái hiện hai thứ tự khai báo padding và hai kết quả định vị. Những quan sát ấy tách lỗi ánh xạ, xung đột cascade, bố cục không co giãn và phép dịch vật lý chưa từng được chuyển đổi.

Nguồn tham khảo & đọc thêm

  1. MDN: logical properties and values
  2. CSS Logical Properties Level1: mapping and cascade
  3. CSS Writing Modes Level4: directions and dimensions
  4. CSS Flexbox Level1: flow direction and wrapping
  5. CSS Transforms Level1: reference boxes and translations
← Về trang bài viết

Hình minh họa

100%