Xây dựng thành phần trình đơn trò chơi 3D

Tổng quan cơ bản về cách tạo trình đơn trò chơi 3D có tính thích ứng, thích ứng và dễ tiếp cận.

Trong bài đăng này, tôi muốn chia sẻ cách tạo thành phần trình đơn trò chơi 3D. Dùng thử bản minh hoạ.

Bản minh hoạ

Nếu bạn thích xem video hơn, sau đây là phiên bản của bài đăng này trên YouTube:

Tổng quan

Trò chơi điện tử thường hiển thị cho người dùng một trình đơn sáng tạo và khác thường, có ảnh động và trong không gian 3D. Thực đơn này phổ biến trong các trò chơi thực tế tăng cường/thực tế ảo mới để làm cho trình đơn trông giống như lơ lửng trong không gian. Hôm nay, chúng tôi sẽ tái tạo những yếu tố cơ bản của hiệu ứng này nhưng với sự tinh tế hơn của bảng phối màu thích ứng và tính năng điều chỉnh cho người dùng những người thích chuyển động ít hơn.

HTML

Trình đơn trò chơi là một danh sách các nút. Cách tốt nhất để trình bày mã này trong HTML là sau:

<ul class="threeD-button-set">
  <li><button>New Game</button></li>
  <li><button>Continue</button></li>
  <li><button>Online</button></li>
  <li><button>Settings</button></li>
  <li><button>Quit</button></li>
</ul>

Một danh sách các nút sẽ tự thông báo rõ ràng cho các công nghệ trình đọc màn hình và hoạt động mà không cần JavaScript hoặc CSS.

một
danh sách dấu đầu dòng có vẻ rất chung chung với các nút thông thường là các mục.

CSS

Việc tạo kiểu cho danh sách nút được chia thành các bước tổng quát sau đây:

  1. Thiết lập thuộc tính tuỳ chỉnh.
  2. Bố cục hộp linh hoạt.
  3. Một nút tuỳ chỉnh có các phần tử giả trang trí.
  4. Đặt các thành phần vào không gian 3D.

Tổng quan về thuộc tính tuỳ chỉnh

Thuộc tính tuỳ chỉnh giúp phân biệt các giá trị bằng cách đưa ra tên thành các giá trị trông ngẫu nhiên, tránh lặp lại mã và chia sẻ các giá trị trong trẻ em.

Dưới đây là các truy vấn phương tiện được lưu dưới dạng biến CSS, còn gọi là tuỳ chỉnh nội dung đa phương tiện. Đây là những xu hướng toàn cầu và sẽ được sử dụng trong nhiều bộ chọn khác nhau để giữ cho mã ngắn gọn và dễ đọc. Chiến lược phát hành đĩa đơn thành phần trình đơn trò chơi sử dụng chuyển động lựa chọn ưu tiên, màu hệ thống hệ thống, và dải màu của màn hình.

@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);

Các thuộc tính tuỳ chỉnh sau quản lý bảng phối màu và giữ chuột giá trị vị trí để tạo trình đơn trò chơi có tính tương tác khi di chuột. Đặt tên tuỳ chỉnh giúp mã dễ đọc vì nó cho biết trường hợp sử dụng của giá trị hoặc tên phù hợp cho kết quả của giá trị.

.threeD-button-set {
  --y:;
  --x:;
  --distance: 1px;
  --theme: hsl(180 100% 50%);
  --theme-bg: hsl(180 100% 50% / 25%);
  --theme-bg-hover: hsl(180 100% 50% / 40%);
  --theme-text: white;
  --theme-shadow: hsl(180 100% 10% / 25%);

  --_max-rotateY: 10deg;
  --_max-rotateX: 15deg;
  --_btn-bg: var(--theme-bg);
  --_btn-bg-hover: var(--theme-bg-hover);
  --_btn-text: var(--theme-text);
  --_btn-text-shadow: var(--theme-shadow);
  --_bounce-ease: cubic-bezier(.5, 1.75, .75, 1.25);

  @media (--dark) {
    --theme: hsl(255 53% 50%);
    --theme-bg: hsl(255 53% 71% / 25%);
    --theme-bg-hover: hsl(255 53% 50% / 40%);
    --theme-shadow: hsl(255 53% 10% / 25%);
  }

  @media (--HDcolor) {
    @supports (color: color(display-p3 0 0 0)) {
      --theme: color(display-p3 .4 0 .9);
    }
  }
}

Nền hình tròn theo giao diện sáng và tối

Giao diện sáng có màu dao động từ cyan đến deeppink âm thanh chuyển màu còn giao diện tối có hiệu ứng chuyển màu conic huyền ảo tối. Để xem thêm về bạn có thể sử dụng hiệu ứng chuyển màu conic, xem conic.style.

html {
  background: conic-gradient(at -10% 50%, deeppink, cyan);

  @media (--dark) {
    background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
  }
}
Hình minh hoạ sự thay đổi về nền giữa các lựa chọn ưu tiên về màu sáng và tối.

Bật phối cảnh 3D

Để các phần tử tồn tại trong không gian 3D của một trang web, một chế độ xem có góc nhìn đa chiều cần được khởi chạy. Tôi chọn đặt phối cảnh trên phần tử body và sử dụng các đơn vị khung nhìn để tạo ra kiểu tôi thích.

body {
  perspective: 40vw;
}

Đây là loại quan điểm tác động có thể có.

Tạo kiểu danh sách nút <ul>

Phần tử này chịu trách nhiệm về bố cục macro danh sách nút tổng thể cũng như là thẻ nổi 3D và có tính tương tác. Sau đây là cách để đạt được điều đó.

Bố cục nhóm nút

Hộp linh hoạt có thể quản lý bố cục vùng chứa. Thay đổi hướng linh hoạt mặc định từ các hàng đến cột có flex-direction và đảm bảo mỗi mục có kích thước bằng nội dung của nhóm bằng cách thay đổi từ stretch thành start cho align-items.

.threeD-button-set {
  /* remove <ul> margins */
  margin: 0;

  /* vertical rag-right layout */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2.5vh;
}

Tiếp theo, hãy thiết lập vùng chứa làm ngữ cảnh không gian 3D và thiết lập CSS clamp() chức năng nhằm đảm bảo thẻ không xoay quá mức xoay dễ đọc. Lưu ý rằng giá trị ở giữa của kẹp là một thuộc tính tuỳ chỉnh, các --x--y này các giá trị sẽ được đặt từ JavaScript khi di chuột tương tác sau.

.threeD-button-set {
  

  /* create 3D space context */
  transform-style: preserve-3d;

  /* clamped menu rotation to not be too extreme */
  transform:
    rotateY(
      clamp(
        calc(var(--_max-rotateY) * -1),
        var(--y),
        var(--_max-rotateY)
      )
    )
    rotateX(
      clamp(
        calc(var(--_max-rotateX) * -1),
        var(--x),
        var(--_max-rotateX)
      )
    )
  ;
}

Tiếp theo, nếu người dùng truy cập chấp nhận chuyển động, hãy thêm gợi ý vào trình duyệt thì sự biến đổi của mục này sẽ liên tục thay đổi với will-change. Ngoài ra, bật nội suy bằng cách đặt transition trên phép biến đổi. Chiến dịch này quá trình chuyển đổi sẽ diễn ra khi chuột tương tác với thẻ, cho phép chuyển đổi mượt mà chuyển đổi sang các thay đổi xoay. Ảnh động là một ảnh động chạy liên tục biểu thị không gian 3D của thẻ, ngay cả khi chuột không thể hoặc không tương tác với thành phần này.

@media (--motionOK) {
  .threeD-button-set {
    /* browser hint so it can be prepared and optimized */
    will-change: transform;

    /* transition transform style changes and run an infinite animation */
    transition: transform .1s ease;
    animation: rotate-y 5s ease-in-out infinite;
  }
}

Ảnh động rotate-y chỉ đặt khung hình chính ở giữa tại 50% vì sẽ mặc định 0%100% về kiểu mặc định của phần tử. Chiến dịch này là viết tắt của các hoạt ảnh thay thế, cần bắt đầu và kết thúc cùng một lúc vị trí. Đây là một cách hay để tạo hiệu ứng chuyển động xen kẽ vô hạn.

@keyframes rotate-y {
  50% {
    transform: rotateY(15deg) rotateX(-6deg);
  }
}

Tạo kiểu cho các phần tử <li>

Mỗi mục trong danh sách (<li>) chứa nút và các phần tử đường viền của nút đó. Chiến lược phát hành đĩa đơn Kiểu của display đã thay đổi nên mục sẽ không hiển thị ::marker. Kiểu position được đặt thành relative để các phần tử giả nút sắp tới có thể định vị trong toàn bộ vùng mà nút sử dụng.

.threeD-button-set > li {
  /* change display type from list-item */
  display: inline-flex;

  /* create context for button pseudos */
  position: relative;

  /* create 3D space context */
  transform-style: preserve-3d;
}

Ảnh chụp màn hình danh sách được xoay trong không gian 3D để hiển thị phối cảnh và
mỗi mục danh sách không còn dấu đầu dòng.

Tạo kiểu cho các phần tử <button>

Nút tạo kiểu có thể là một công việc khó khăn, có nhiều trạng thái và kiểu tương tác để tính đến. Các nút này nhanh chóng trở nên phức tạp do cần cân bằng phần tử giả, ảnh động và tương tác.

Kiểu <button> ban đầu

Dưới đây là các kiểu cơ bản sẽ hỗ trợ các trạng thái khác.

.threeD-button-set button {
  /* strip out default button styles */
  appearance: none;
  outline: none;
  border: none;

  /* bring in brand styles via props */
  background-color: var(--_btn-bg);
  color: var(--_btn-text);
  text-shadow: 0 1px 1px var(--_btn-text-shadow);

  /* large text rounded corner and padded*/
  font-size: 5vmin;
  font-family: Audiowide;
  padding-block: .75ch;
  padding-inline: 2ch;
  border-radius: 5px 20px;
}

Ảnh chụp màn hình danh sách nút dưới góc nhìn 3D, lần này được tạo kiểu
các nút.

Phần tử giả dạng nút

Đường viền của nút không phải là đường viền truyền thống, chúng là vị trí tuyệt đối phần tử giả có đường viền.

Ảnh chụp màn hình bảng điều khiển Phần tử Chrome Devtools có nút hiển thị
Các phần tử ::before và ::after.

Những yếu tố này đóng vai trò quan trọng trong việc hiển thị phối cảnh 3D đã được thiết lập. Một trong những phần tử giả này sẽ được đẩy ra khỏi nút, và một thẻ sẽ được kéo lại gần người dùng hơn. Tác động này dễ nhận thấy nhất ở nút trên cùng và dưới cùng.

.threeD-button button {
  

  &::after,
  &::before {
    /* create empty element */
    content: '';
    opacity: .8;

    /* cover the parent (button) */
    position: absolute;
    inset: 0;

    /* style the element for border accents */
    border: 1px solid var(--theme);
    border-radius: 5px 20px;
  }

  /* exceptions for one of the pseudo elements */
  /* this will be pushed back (3x) and have a thicker border */
  &::before {
    border-width: 3px;

    /* in dark mode, it glows! */
    @media (--dark) {
      box-shadow:
        0 0 25px var(--theme),
        inset 0 0 25px var(--theme);
    }
  }
}

Kiểu biến đổi 3D

Bên dưới transform-style được đặt thành preserve-3d để trẻ có thể đặt không gian trên trục z. transform được đặt thành --distance và sẽ được tăng lên khi di chuột và .

.threeD-button-set button {
  

  transform: translateZ(var(--distance));
  transform-style: preserve-3d;

  &::after {
    /* pull forward in Z space with a 3x multiplier */
    transform: translateZ(calc(var(--distance) / 3));
  }

  &::before {
    /* push back in Z space with a 3x multiplier */
    transform: translateZ(calc(var(--distance) / 3 * -1));
  }
}

Kiểu ảnh động có điều kiện

Nếu người dùng ổn khi chuyển động, nút này sẽ gợi ý cho trình duyệt biết rằng thuộc tính biến đổi phải sẵn sàng thay đổi và chuyển đổi đã được thiết lập cho Thuộc tính transformbackground-color. Hãy chú ý đến sự khác biệt về thời lượng của video, tôi cảm thấy nó tạo ra hiệu ứng so le tinh tế.

.threeD-button-set button {
  

  @media (--motionOK) {
    will-change: transform;
    transition:
      transform .2s ease,
      background-color .5s ease
    ;

    &::before,
    &::after {
      transition: transform .1s ease-out;
    }

    &::after    { transition-duration: .5s }
    &::before { transition-duration: .3s }
  }
}

Kiểu tương tác khi di chuột và tập trung

Mục tiêu của ảnh động tương tác là trải các lớp tạo nên nút phẳng. Hãy hoàn thành việc này bằng cách đặt biến --distance, ban đầu là 1px. Bộ chọn hiển thị trong các đoạn mã ví dụ sau đây sẽ kiểm tra để xem liệu thiết bị có đang di chuột hay đặt tiêu điểm vào nút này và bạn sẽ thấy chỉ báo lấy tiêu điểm, và không được kích hoạt. Nếu có, CSS sẽ thực hiện việc sau:

  • Áp dụng màu nền khi di chuột.
  • Tăng khoảng cách .
  • Thêm hiệu ứng nhẹ nhàng.
  • Xoay vòng chuyển đổi phần tử giả.
.threeD-button-set button {
  

  &:is(:hover, :focus-visible):not(:active) {
    /* subtle distance plus bg color change on hover/focus */
    --distance: 15px;
    background-color: var(--_btn-bg-hover);

    /* if motion is OK, setup transitions and increase distance */
    @media (--motionOK) {
      --distance: 3vmax;

      transition-timing-function: var(--_bounce-ease);
      transition-duration: .4s;

      &::after  { transition-duration: .5s }
      &::before { transition-duration: .3s }
    }
  }
}

Góc nhìn 3D vẫn rất gọn gàng đối với lựa chọn chuyển động reduced. Các phần tử trên cùng và dưới cùng thể hiện hiệu ứng một cách tinh tế và thú vị.

Các cải tiến nhỏ với JavaScript

Giao diện này có thể sử dụng được từ bàn phím, trình đọc màn hình, tay điều khiển trò chơi, thao tác chạm và nhưng chúng ta có thể thêm một số chi tiết nhỏ cho JavaScript để tình huống cụ thể.

Các phím mũi tên hỗ trợ

Bạn có thể dùng phím tab để di chuyển trong trình đơn một cách hiệu quả, nhưng tôi cho rằng tính năng này sẽ điều hướng bàn điều khiển hoặc cần điều khiển để di chuyển tiêu điểm trên tay điều khiển trò chơi. Chiến lược phát hành đĩa đơn thư viện roving-ux thường dùng cho GUI Giao diện thử thách sẽ xử lý các phím mũi tên. Đoạn mã dưới đây cho biết thư viện để bẫy tiêu điểm trong .threeD-button-set và chuyển tiếp tiêu điểm đến nút con.

import {rovingIndex} from 'roving-ux'

rovingIndex({
  element: document.querySelector('.threeD-button-set'),
  target: 'button',
})

Tương tác thị sai chuột

Theo dõi chuột và nghiêng trình đơn nhằm bắt chước tính năng Thực tế tăng cường (AR) và Thực tế ảo (VR) giao diện trò chơi điện tử, trong đó bạn có thể dùng con trỏ ảo thay vì dùng chuột. Điều này có thể thú vị khi các phần tử nhận biết rõ ràng về con trỏ.

Vì đây là một tính năng bổ sung nhỏ, nên chúng ta sẽ đặt tương tác sau một truy vấn sở thích chuyển động của người dùng. Ngoài ra, trong quá trình thiết lập, hãy lưu trữ danh sách nút thành phần vào bộ nhớ bằng querySelector và lưu các giới hạn của phần tử vào bộ nhớ đệm menuRect Sử dụng các giới hạn này để xác định độ lệch xoay được áp dụng cho thẻ dựa vào vị trí chuột.

const menu = document.querySelector('.threeD-button-set')
const menuRect = menu.getBoundingClientRect()

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

Tiếp theo, chúng ta cần một hàm chấp nhận vị trí chuột xy rồi trả về một giá trị mà chúng ta có thể dùng để xoay thẻ. Hàm sau đây sử dụng chuột để xác định xem nó nằm ở bên nào của hộp và giá trị của nó là bao nhiêu. Chiến lược phát hành đĩa đơn delta được trả về từ hàm.

const getAngles = (clientX, clientY) => {
  const { x, y, width, height } = menuRect

  const dx = clientX - (x + 0.5 * width)
  const dy = clientY - (y + 0.5 * height)

  return {dx,dy}
}

Cuối cùng, hãy xem thao tác di chuyển của chuột, truyền vị trí vào hàm getAngles() đồng thời sử dụng các giá trị delta làm kiểu thuộc tính tuỳ chỉnh. Tôi chia cho 20 để đặt delta và giảm bớt sự phức tạp, có thể có cách tốt hơn để làm việc đó. Nếu bạn hãy nhớ ngay từ đầu, chúng ta đã đặt đạo cụ --x--y ở giữa clamp(), ngăn không cho vị trí chuột xoay quá mức thẻ vào vị trí khó đọc.

if (motionOK) {
  window.addEventListener('mousemove', ({target, clientX, clientY}) => {
    const {dx,dy} = getAngles(clientX, clientY)

    menu.attributeStyleMap.set('--x', `${dy / 20}deg`)
    menu.attributeStyleMap.set('--y', `${dx / 20}deg`)
  })
}

Bản dịch và hướng dẫn

Có một lỗi xảy ra khi thử nghiệm trình đơn trò chơi ở các chế độ viết khác và ngôn ngữ.

Các phần tử <button> có kiểu !important cho writing-mode trong người dùng biểu định kiểu của tác nhân. Tức là HTML của trình đơn trò chơi cần thay đổi cho phù hợp với thiết kế mong muốn. Việc thay đổi danh sách nút thành danh sách đường liên kết sẽ cho phép các thuộc tính để thay đổi hướng trình đơn, vì các phần tử <a> không có trình duyệt đã cung cấp kiểu !important.

Kết luận

Giờ bạn đã biết cách tôi thực hiện việc đó, bạn sẽ làm cách nào‽ 🙂 Bạn có thể thêm gia tốc kế không tương tác với trình đơn, vì vậy, việc xếp điện thoại sẽ xoay trình đơn? Chúng tôi có thể cải thiện không trải nghiệm không chuyển động?

Hãy đa dạng hoá phương pháp tiếp cận và tìm hiểu tất cả các cách xây dựng ứng dụng trên web. Tạo một bản minh hoạ, tweet cho tôi các đường liên kết và tôi sẽ thêm vào vào phần bản phối lại của cộng đồng dưới đây!

Bản phối lại của cộng đồng

Chưa có gì để xem ở đây!