Xây dựng thành phần nút phân tách

Tổng quan cơ bản về cách tạo một thành phần nút chia tách dễ tiếp cận.

Trong bài đăng này, tôi muốn chia sẻ cách tạo nút phân tách . Xem 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

Nút chia là các nút che giấu một nút chính và một danh sách các nút bổ sung. Chúng hữu ích để hiển thị một hành động phổ biến trong khi lồng phụ kiện, ít được sử dụng hơn hành động cho đến khi cần thiết. Nút phân tách có thể đóng vai trò quan trọng trong việc hỗ trợ một thiết kế bận rộn mang lại cảm giác tối giản. Nút phân tách nâng cao thậm chí có thể ghi nhớ thao tác gần đây nhất của người dùng và quảng bá ứng dụng đó ở vị trí chính.

Bạn có thể tìm thấy một nút chia tách thông thường trong ứng dụng email. Hành động chính được gửi, nhưng có lẽ bạn có thể gửi sau hoặc lưu thư nháp:

Ví dụ về nút phân tách như trong ứng dụng email.

Khu vực hành động được chia sẻ rất đẹp vì người dùng không cần nhìn quanh. Chúng hãy lưu ý rằng các thao tác thiết yếu đối với email nằm trong nút phân tách.

Phụ tùng

Hãy phân tích các phần thiết yếu của nút phân tách trước khi thảo luận về hoạt động điều phối tổng thể và trải nghiệm người dùng cuối cùng. Khả năng hỗ trợ tiếp cận của VisBug công cụ kiểm tra được sử dụng ở đây để hiển thị chế độ xem vĩ mô của thành phần, hiển thị các khía cạnh của HTML, kiểu và khả năng tiếp cận cho từng phần chính.

Các phần tử HTML tạo nên nút phân tách.

Vùng chứa nút chia tách cấp cao nhất

Thành phần cấp cao nhất là một hộp linh hoạt cùng dòng với một lớp gui-split-button, chứa hành động chính.gui-popup-button.

Lớp gui-Split-button đã kiểm tra và hiển thị các thuộc tính CSS được dùng trong lớp này.

Nút hành động chính

<button> có thể nhìn thấy và có thể làm tâm điểm ban đầu nằm vừa trong vùng chứa có hai hình dạng góc phù hợp cho lấy nét, di chuột và lượt tương tác đang hoạt động với xuất hiện có trong .gui-split-button.

Trình kiểm tra cho thấy các quy tắc CSS cho phần tử nút.

Nút bật tắt cửa sổ bật lên

"Nút bật lên" support dành cho việc kích hoạt và ám chỉ danh sách nút phụ. Xin lưu ý rằng mã này không phải là <button> và không thể làm tâm điểm. Tuy nhiên, đó là neo định vị cho .gui-popup và máy chủ lưu trữ cho :focus-within được sử dụng để hiển thị cửa sổ bật lên.

Trình kiểm tra cho thấy các quy tắc CSS cho lớp gui- khớp-button.

Thẻ bật lên

Đây là một thẻ con nổi trên thẻ neo vào phần neo .gui-popup-button, theo vị trí tuyệt đối và gói danh sách nút theo đúng ngữ nghĩa.

Trình kiểm tra hiển thị các quy tắc CSS cho lớp gui- ghé thăm

(Các) hành động phụ

<button> có thể làm tâm điểm có cỡ chữ nhỏ hơn một chút so với chính nút hành động có một biểu tượng và tạo kiểu cho nút chính.

Trình kiểm tra cho thấy các quy tắc CSS cho phần tử nút.

Thuộc tính tuỳ chỉnh

Các biến sau đây hỗ trợ tạo ra sự hài hoà màu sắc và là vị trí trung tâm để sửa đổi các giá trị được sử dụng trong toàn bộ thành phần.

@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --light (prefers-color-scheme: light);

.gui-split-button {
  --theme:             hsl(220 75% 50%);
  --theme-hover:  hsl(220 75% 45%);
  --theme-active:  hsl(220 75% 40%);
  --theme-text:      hsl(220 75% 25%);
  --theme-border: hsl(220 50% 75%);
  --ontheme:         hsl(220 90% 98%);
  --popupbg:         hsl(220 0% 100%);

  --border: 1px solid var(--theme-border);
  --radius: 6px;
  --in-speed: 50ms;
  --out-speed: 300ms;

  @media (--dark) {
    --theme:             hsl(220 50% 60%);
    --theme-hover:  hsl(220 50% 65%);
    --theme-active:  hsl(220 75% 70%);
    --theme-text:      hsl(220 10% 85%);
    --theme-border: hsl(220 20% 70%);
    --ontheme:         hsl(220 90% 5%);
    --popupbg:         hsl(220 10% 30%);
  }
}

Bố cục và màu sắc

Markup (note: đây là tên ứng dụng)

Phần tử bắt đầu dưới dạng <div> có tên lớp tuỳ chỉnh.

<div class="gui-split-button"></div>

Thêm nút chính và các phần tử .gui-popup-button.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span>
</div>

Hãy lưu ý các thuộc tính aria aria-haspopuparia-expanded. Những gợi ý này quan trọng để trình đọc màn hình nhận biết được tính năng và trạng thái chia tách trải nghiệm nút. Thuộc tính title rất hữu ích cho mọi người.

Thêm biểu tượng <svg> và phần tử vùng chứa .gui-popup.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup"></ul>
  </span>
</div>

Để có vị trí đặt cửa sổ bật lên đơn giản, .gui-popup là phần tử con của nút mở rộng nó. Lợi ích duy nhất mà chiến lược này mang lại là .gui-split-button vùng chứa không thể sử dụng overflow: hidden, vì chế độ này sẽ cắt cửa sổ bật lên trình bày một cách trực quan.

Một <ul> chứa <li><button> nội dung sẽ tự thông báo là "nút danh sách" vào trình đọc màn hình, chính xác là giao diện được trình bày.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup">
      <li>
        <button>Schedule for later</button>
      </li>
      <li>
        <button>Delete</button>
      </li>
      <li>
        <button>Save draft</button>
      </li>
    </ul>
  </span>
</div>

Để tinh tế và tạo sự thú vị với màu sắc, tôi đã thêm biểu tượng vào các nút phụ tại https://heroicons.com. Biểu tượng là không bắt buộc cho cả hai nút chính và nút phụ.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup">
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
        </svg>
        Schedule for later
      </button></li>
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
        </svg>
        Delete
      </button></li>
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
        </svg>
        Save draft
      </button></li>
    </ul>
  </span>
</div>

Kiểu

Khi đã có HTML và nội dung, các kiểu sẽ sẵn sàng để cung cấp màu sắc và bố cục.

Tạo kiểu cho vùng chứa nút phân tách

Loại hiển thị inline-flex phù hợp với thành phần gói này vì nó phải vừa với các nút, thao tác hoặc phần tử phân tách khác.

.gui-split-button {
  display: inline-flex;
  border-radius: var(--radius);
  background: var(--theme);
  color: var(--ontheme);
  fill: var(--ontheme);

  touch-action: manipulation;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

Nút phân tách.

Kiểu <button>

Các nút rất hữu ích trong việc nguỵ trang số lượng mã cần thiết. Bạn có thể cần huỷ hoặc thay thế các kiểu mặc định của trình duyệt, nhưng bạn cũng sẽ cần thực thi một số tính kế thừa, thêm trạng thái tương tác cũng như điều chỉnh cho phù hợp với các lựa chọn ưu tiên của người dùng và loại đầu vào. Kiểu nút sẽ tăng lên nhanh chóng.

Các nút này khác với các nút thông thường vì chúng dùng chung một nền có phần tử mẹ. Thông thường, một nút sở hữu màu nền và màu văn bản. Tuy nhiên, những chủ đề này chia sẻ và chỉ áp dụng kiến thức nền tảng của riêng họ về hoạt động tương tác.

.gui-split-button button {
  cursor: pointer;
  appearance: none;
  background: none;
  border: none;

  display: inline-flex;
  align-items: center;
  gap: 1ch;
  white-space: nowrap;

  font-family: inherit;
  font-size: inherit;
  font-weight: 500;

  padding-block: 1.25ch;
  padding-inline: 2.5ch;

  color: var(--ontheme);
  outline-color: var(--theme);
  outline-offset: -5px;
}

Thêm trạng thái tương tác qua một vài CSS lớp giả và sử dụng phương pháp so khớp thuộc tính tuỳ chỉnh cho trạng thái:

.gui-split-button button {
  

  &:is(:hover, :focus-visible) {
    background: var(--theme-hover);
    color: var(--ontheme);

    & > svg {
      stroke: currentColor;
      fill: none;
    }
  }

  &:active {
    background: var(--theme-active);
  }
}

Nút chính cần một vài kiểu đặc biệt để hoàn tất hiệu ứng thiết kế:

.gui-split-button > button {
  border-end-start-radius: var(--radius);
  border-start-start-radius: var(--radius);

  & > svg {
    fill: none;
    stroke: var(--ontheme);
  }
}

Cuối cùng, để tinh tế hơn, nút và biểu tượng giao diện sáng sẽ có bóng:

.gui-split-button {
  @media (--light) {
    & > button,
    & button:is(:focus-visible, :hover) {
      text-shadow: 0 1px 0 var(--theme-active);
    }
    & > .gui-popup-button > svg,
    & button:is(:focus-visible, :hover) > svg {
      filter: drop-shadow(0 1px 0 var(--theme-active));
    }
  }
}

Một nút lớn đã chú ý đến các tương tác vi mô và các chi tiết nhỏ.

Ghi chú về :focus-visible

Hãy lưu ý cách các kiểu nút sử dụng :focus-visible thay vì :focus. :focus là một điểm nhấn quan trọng để tạo ra một giao diện người dùng dễ tiếp cận nhưng cũng có suy thoái: đây là điều không thông minh về việc người dùng có cần xem ứng dụng hoặc không, nó sẽ áp dụng cho bất kỳ tiêu điểm nào.

Video dưới đây cố gắng chia nhỏ tương tác vi mô này để trình bày cách :focus-visible là giải pháp thay thế thông minh.

Tạo kiểu cho nút bật lên

Hộp linh hoạt 4ch dùng để căn giữa biểu tượng và cố định danh sách nút bật lên. Thích nút chính, nút này trong suốt cho đến khi được di chuột hoặc tương tác và kéo giãn để lấp đầy.

Phần mũi tên của nút phân tách dùng để kích hoạt cửa sổ bật lên.

.gui-popup-button {
  inline-size: 4ch;
  cursor: pointer;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-inline-start: var(--border);
  border-start-end-radius: var(--radius);
  border-end-end-radius: var(--radius);
}

Tạo lớp trong các trạng thái di chuột, lấy tiêu điểm và đang hoạt động bằng CSS Lồng ghép và Bộ chọn chức năng :is():

.gui-popup-button {
  

  &:is(:hover,:focus-within) {
    background: var(--theme-hover);
  }

  /* fixes iOS trying to be helpful */
  &:focus {
    outline: none;
  }

  &:active {
    background: var(--theme-active);
  }
}

Các kiểu này là nội dung hấp dẫn chính để hiển thị và ẩn cửa sổ bật lên. Khi .gui-popup-buttonfocus trên bất kỳ phần tử con nào, đặt opacity, vị trí và pointer-events, trên biểu tượng và cửa sổ bật lên.

.gui-popup-button {
  

  &:focus-within {
    & > svg {
      transition-duration: var(--in-speed);
      transform: rotateZ(.5turn);
    }
    & > .gui-popup {
      transition-duration: var(--in-speed);
      opacity: 1;
      transform: translateY(0);
      pointer-events: auto;
    }
  }
}

Sau khi hoàn tất các kiểu vào và ra, bước cuối cùng là có điều kiện biến đổi hiệu ứng chuyển đổi tuỳ thuộc vào lựa chọn ưu tiên về chuyển động của người dùng:

.gui-popup-button {
  

  @media (--motionOK) {
    & > svg {
      transition: transform var(--out-speed) ease;
    }
    & > .gui-popup {
      transform: translateY(5px);

      transition:
        opacity var(--out-speed) ease,
        transform var(--out-speed) ease;
    }
  }
}

Chú ý đến mã sẽ nhận thấy độ mờ vẫn được chuyển đổi cho người dùng những người thích chuyển động ít hơn.

Tạo kiểu cửa sổ bật lên

Phần tử .gui-popup là một danh sách nút thẻ nổi sử dụng các thuộc tính tuỳ chỉnh và đơn vị tương đối nhỏ hơn một cách tinh tế, được kết hợp tương tác với và trên thương hiệu bằng cách sử dụng màu sắc. Lưu ý rằng các biểu tượng có độ tương phản thấp hơn mỏng hơn và bóng có pha chút màu xanh thương hiệu. Giống như với các nút, những chi tiết nhỏ này xuất hiện trong giao diện người dùng và trải nghiệm người dùng.

Một thành phần thẻ nổi.

.gui-popup {
  --shadow: 220 70% 15%;
  --shadow-strength: 1%;

  opacity: 0;
  pointer-events: none;

  position: absolute;
  bottom: 80%;
  left: -1.5ch;

  list-style-type: none;
  background: var(--popupbg);
  color: var(--theme-text);
  padding-inline: 0;
  padding-block: .5ch;
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font-size: .9em;
  transition: opacity var(--out-speed) ease;

  box-shadow:
    0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
    0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
    0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
    0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
    0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
    0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
  ;
}

Các biểu tượng và nút có màu sắc của thương hiệu để tạo kiểu đẹp mắt trong mỗi tối và thẻ theo chủ đề sáng:

Các đường liên kết và biểu tượng để thanh toán, thanh toán nhanh và Lưu để sử dụng sau.

.gui-popup {
  

  & svg {
    fill: var(--popupbg);
    stroke: var(--theme);

    @media (prefers-color-scheme: dark) {
      stroke: var(--theme-border);
    }
  }

  & button {
    color: var(--theme-text);
    width: 100%;
  }
}

Cửa sổ bật lên trong giao diện tối có văn bản và bóng biểu tượng bổ sung, cùng với một chút bóng hộp đậm:

Cửa sổ bật lên trong giao diện tối.

.gui-popup {
  

  @media (--dark) {
    --shadow-strength: 5%;
    --shadow: 220 3% 2%;

    & button:not(:focus-visible, :hover) {
      text-shadow: 0 1px 0 var(--ontheme);
    }

    & button:not(:focus-visible, :hover) > svg {
      filter: drop-shadow(0 1px 0 var(--ontheme));
    }
  }
}

Kiểu biểu tượng <svg> chung

Tất cả biểu tượng đều có kích thước tương đối với nút font-size mà chúng được dùng sử dụng đơn vị ch làm đơn vị inline-size. Mỗi đường dẫn cũng được cung cấp một số kiểu để giúp phác thảo các biểu tượng mềm và mượt mà.

.gui-split-button svg {
  inline-size: 2ch;
  box-sizing: content-box;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2px;
}

Bố cục từ phải sang trái

Thuộc tính logic thực hiện tất cả các công việc phức tạp. Dưới đây là danh sách các thuộc tính logic được sử dụng: – display: inline-flex tạo một phần tử linh hoạt cùng dòng. - padding-blockpadding-inline theo cặp, thay vì padding nói ngắn gọn, tận dụng lợi ích của khoảng đệm cho các cạnh logic. – border-end-start-radiusbạn bè sẽ các góc tròn dựa trên hướng tài liệu. – inline-size thay vì width đảm bảo kích thước không gắn liền với kích thước thực. – border-inline-start thêm một đường viền vào phần đầu, đường viền này có thể ở bên phải hoặc bên trái tuỳ theo hướng của tập lệnh.

JavaScript

Hầu như toàn bộ JavaScript sau đây đều dùng để tăng cường khả năng hỗ trợ tiếp cận. Hai trong số các thư viện trợ giúp được dùng để giúp các tác vụ trở nên dễ dàng hơn một chút. BlingBlingJS được sử dụng để ngắn gọn Truy vấn DOM và thiết lập trình nghe sự kiện dễ dàng, trong khi roving-ux giúp người dùng dễ tiếp cận hoạt động tương tác với bàn phím và tay điều khiển trò chơi cho cửa sổ bật lên.

import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'

const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')

Sau khi nhập các thư viện ở trên cũng như các thành phần được chọn và lưu vào biến số lớn, nên bạn chỉ cần nâng cấp một vài hàm là hoàn tất.

Chỉ số lưu động

Khi bàn phím hoặc trình đọc màn hình lấy tiêu điểm .gui-popup-button, chúng ta muốn chuyển tiếp tiêu điểm vào nút đầu tiên (hoặc được lấy tiêu điểm gần đây nhất) trong .gui-popup Thư viện giúp chúng ta thực hiện điều này bằng elementtarget tham số.

popupButtons.forEach(element =>
  rovingIndex({
    element,
    target: 'button',
  }))

Phần tử hiện chuyển tiêu điểm đến phần tử con <button> mục tiêu và cho phép điều hướng bằng phím mũi tên chuẩn để duyệt qua các lựa chọn.

Bật/tắt aria-expanded

Mặc dù rõ ràng là một cửa sổ bật lên đang hiển thị và đang ẩn, nhưng trình đọc màn hình không chỉ cần chỉ dẫn bằng hình ảnh. JavaScript được dùng ở đây để bổ trợ cho hoạt động tương tác :focus-within do CSS điều hướng bằng cách bật/tắt một thuộc tính phù hợp của trình đọc màn hình.

popupButtons.on('focusin', e => {
  e.currentTarget.setAttribute('aria-expanded', true)
})

popupButtons.on('focusout', e => {
  e.currentTarget.setAttribute('aria-expanded', false)
})

Bật khoá Escape

Sự tập trung của người dùng được cố tình đưa vào một cái bẫy, do đó chúng ta cần đưa ra cách rời đi. Cách phổ biến nhất là cho phép sử dụng khoá Escape. Để làm việc này, hãy chú ý đến các lần nhấn phím trên nút bật lên, vì mọi sự kiện bàn phím trên con sẽ được bong bóng đến gần cha mẹ này.

popupButtons.on('keyup', e => {
  if (e.code === 'Escape')
    e.target.blur()
})

Nếu nút bật lên thấy bất kỳ lần nhấn phím Escape nào, thì nút này sẽ xoá tiêu điểm khỏi chính nút đó thông qua tính năng blur().

Số lần nhấp vào nút phân tách

Cuối cùng, nếu người dùng nhấp, nhấn hoặc bàn phím tương tác với các nút, ứng dụng cần thực hiện hành động thích hợp. Tính năng bong bóng trò chuyện được sử dụng ở đây một lần nữa, nhưng lần này là trên vùng chứa .gui-split-button để bắt số lượt nhấp từ một cửa sổ bật lên con hoặc hành động chính.

splitButtons.on('click', event => {
  if (event.target.nodeName !== 'BUTTON') return
  console.info(event.target.innerText)
})

Kết luận

Giờ bạn đã biết cách tôi thực hiện điều đó, bạn sẽ làm cách nào‽ 🙂

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