jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

CSS 3D from Scratch · Part 2 — preserve-3d & Building Real Objects

Turn flat cards into solid objects: master transform-style: preserve-3d and backface-visibility, understand the local coordinate system, then assemble six faces into a real, rotatable CSS cube — step by step. With a live cube builder demo.

5 MIN READ

Mừng con trở lại, đồ đệ. Ở Phần 1 con đã học đặt máy ảnh và lật một tấm card. Nhưng card thì phẳng — hôm nay ta dựng một thứ có thể tích, một vật thể thật xoay được và ngắm quanh được: khối lập phương.

Đây là bài học khiến CSS 3D thôi là trò mua vui và thực sự “thông”. Và tất cả xoay quanh một property mà phần lớn tutorial cho qua loa: transform-style: preserve-3d.

Xoay cube, kéo “explode” để tách các mặt ra, và tắt preserve-3d để xem nó sụp xuống.

Mở demo đầy đủ:

Property làm cho vật thể trở nên khả thi

Đây là cái bẫy. Mặc định, khi con áp transform 3D lên các phần tử con, trình duyệt lặng lẽ dẹp phẳng chúng về mặt phẳng của cha sau khi render. Giá trị mặc định là transform-style: flat.

Nghĩa là con bị đẩy “ra sau” và con bị kéo “ra trước” đều bị ép vào cùng một lớp phẳng — như gập cuốn sách pop-up lại.

.cube {
  transform-style: preserve-3d; /* "kids, you may live in 3D space" */
}

transform-style: preserve-3d bảo một phần tử giữ các con trong cùng không gian 3D, thay vì dẹp phẳng chúng.

Trong demo, bấm preserve-3d: off. Cube chết ngay và biến thành một chồng ô vuông phẳng. Bật lại — thể tích trở về. Đó là toàn bộ lý do property này tồn tại.

Quy tắc sư phụ luôn nhớ:

Mọi phần tử có con đặt trong 3D đều cần preserve-3d. Nó KHÔNG di truyền — mỗi cấp lồng nhau cần chiều sâu phải tự khai báo.

Sáu ô vuông thành cube như thế nào

Đây là phần trông như phép thuật nhưng thuần logic. Cube có sáu mặt. Mỗi mặt là cùng một ô vuông, chỉ là hướng về một phía khác rồi đẩy ra ngoài bằng nửa kích thước cube.

Với cube 110px, “nửa” là 55px. Để ý quy luật — xoay để nhắm, rồi translateZ để đẩy ra:

.scene { perspective: 700px; }          /* camera (Part 1) */

.cube {
  position: relative;
  width: 110px; height: 110px;
  transform-style: preserve-3d;         /* the magic word */
}

.face {
  position: absolute;
  inset: 0;                             /* stack all six in the same spot */
  width: 110px; height: 110px;
}

/* Aim each face, then push it out by half the width (55px). */
.front  { transform: rotateY(0deg)    translateZ(55px); }
.back   { transform: rotateY(180deg)  translateZ(55px); }
.right  { transform: rotateY(90deg)   translateZ(55px); }
.left   { transform: rotateY(-90deg)  translateZ(55px); }
.top    { transform: rotateX(90deg)   translateZ(55px); }
.bottom { transform: rotateX(-90deg)  translateZ(55px); }

Vì sao nó chạy? Đọc như một câu chuyện, từ phải sang trái (nhớ bài transforms: transform áp dụng từ phải sang trái). Lấy .right: đầu tiên mặt ở giữa, rồi translateZ(55px) đẩy nó 55px về phía máy ảnh, rồi — khoan.

Cách nghĩ gọn nhất là thế này. Trình duyệt đọc rotateY(90deg) translateZ(55px) là: trước xoay cả hệ tọa độ của mặt 90° (để “hướng ra” cục bộ của nó giờ chỉ sang phải), rồi translateZ đẩy nó ra dọc theo Z vừa-xoay-đó — giờ đang chỉ sang phải.

Đó là insight then chốt của mọi việc dựng hình CSS 3D:

translateZ luôn di chuyển dọc theo trục Z CỤC BỘ của phần tử — mà phép xoay đã quay sẵn để chỉ về hướng con nhắm.

Kéo slider explode trong demo và con sẽ thấy mỗi mặt trượt ra theo đúng hướng nó đang quay mặt. Đó là translateZ trên trục đã xoay, được hiện hình.

Xoay cả khối cube

Vì cube có preserve-3d, xoay phần tử cha sẽ xoay cả vật thể đã ghép — sáu mặt cùng di chuyển như một khối đặc:

.cube { transform: rotateX(-22deg) rotateY(32deg); }

Kéo rotateX/rotateY trong demo. Con không di chuyển sáu thứ — con di chuyển một vật thể mà bên trong tình cờ là sáu mặt. Đó là phần thưởng của preserve-3d.

backface-visibility — bạn của flip, tùy chọn cho cube

Mỗi mặt có một mặt trước và một mặt sau. backface-visibility: hidden làm một mặt biến mất khi nó quay lưng lại với con.

  • Flip card (Phần 1): thiết yếu — không có nó, chữ của mặt sau hiện xuyên qua mặt trước, bị lật gương.
  • Cube đặc: tùy chọn — các mặt đặc nên đằng nào cũng không nhìn xuyên được. Nhưng hidden là một chút lợi hiệu năng (GPU bỏ qua việc vẽ mặt khuất).

Bật/tắt backface trong demo khi cube tự xoay và xem các mặt bên trong chớp tắt.

Xoay mãi mãi

Một cube tự xoay chỉ là ba dòng:

@keyframes spin {
  from { transform: rotateX(-22deg) rotateY(0deg); }
  to   { transform: rotateX(-22deg) rotateY(360deg); }
}
.cube { animation: spin 12s linear infinite; }

Ta sẽ làm motion xịn hơn nhiều ở Phần 4. Giờ hãy để ý đó là một animation trên cha di chuyển cả khối — không phải sáu animation.

Lời cảnh báo của sư phụ

  • preserve-3d không di truyền. Nếu con lồng vật 3D trong vật 3D, mỗi cấp giữ chiều sâu đều cần preserve-3d riêng.
  • overflow: hidden âm thầm giết preserve-3d. Đặt overflow (hoặc clip-path, filter, opacity < 1, mask) lên cha 3D sẽ ép dẹp phẳng. Đây là lỗi “sao cube của tôi phẳng?” số 1.
  • translateZ = nửa kích thước. Cube N pixel thì đẩy mặt ra N/2. Sai số này thì cube bị hở hoặc chồng mặt.

Luyện tập, không thì coi như chưa học

  1. Dựng lại từ trí nhớ: dựng lại cube sáu mặt mà không nhìn. Quy luật rotate … translateZ(nửa) phải thành phản xạ cơ bắp.
  2. Làm hộp chữ nhật: biến cube thành hộp 120×80×200. (Gợi ý: các mặt không còn là ô vuông giống nhau, và mỗi trục dùng translateZ khác.)
  3. Xúc xắc 3D: đặt chấm (1–6) lên các mặt và xoay cube để một số đã chọn quay ra trước.
  4. Cố tình làm hỏng: thêm overflow: hidden vào .cube trong DevTools và xác nhận nó dẹp phẳng. Giờ con sẽ nhận ra lỗi này ngoài đời ngay lập tức.

Phần tiếp theo

Giờ con dựng được vật thể 3D đặc, không chỉ card phẳng. Con hiểu property (preserve-3d), quy luật dựng hình, và các bẫy làm dẹp phẳng khung cảnh.

Phần 3, ta làm 3D phản hồi con người — một tấm card nghiêng theo chuột con theo thời gian thực, với chiều sâu parallax phân lớp khiến nó như tấm kính lơ lửng trên trang. Đây là lúc 3D trở thành UX thú vị, không chỉ là demo.