switch 구성요소 빌드

반응성이 높고 액세스가 용이한 스위치 구성요소를 빌드하는 방법에 관한 기본 개요입니다.

이 게시물에서는 스위치 구성요소를 빌드하는 방법에 대한 생각을 공유하고자 합니다. 데모 사용해 보기

데모

동영상을 선호하는 경우 이 게시물의 YouTube 버전을 참고하세요.

개요

스위치는 체크박스와 유사하게 작동하지만 부울 켜기/끄기 상태를 명시적으로 나타냅니다.

이 데모는 대부분의 기능에 <input type="checkbox" role="switch">를 사용합니다. 이 데모는 CSS 또는 JavaScript가 없어도 완전히 작동하고 액세스할 수 있다는 이점이 있습니다. CSS를 로드하면 오른쪽에서 왼쪽 언어, 세로 모드, 애니메이션 등이 지원됩니다. JavaScript를 로드하면 스위치를 드래그할 수 있고 만질 수 있습니다.

맞춤 속성

다음 변수는 스위치의 다양한 부분과 옵션을 나타냅니다. 최상위 클래스인 .gui-switch에는 구성요소 하위 요소 전반에서 사용되는 맞춤 속성과 중앙 집중식 맞춤설정을 위한 진입점이 포함되어 있습니다.

추적

길이 (--track-size), 패딩, 두 가지 색상:

.gui-switch {
  --track-size: calc(var(--thumb-size) * 2);
  --track-padding: 2px;

  --track-inactive: hsl(80 0% 80%);
  --track-active: hsl(80 60% 45%);

  --track-color-inactive: var(--track-inactive);
  --track-color-active: var(--track-active);

  @media (prefers-color-scheme: dark) {
    --track-inactive: hsl(80 0% 35%);
    --track-active: hsl(80 60% 60%);
  }
}

미리보기

크기, 배경 색상, 상호작용 강조 표시 색상:

.gui-switch {
  --thumb-size: 2rem;
  --thumb: hsl(0 0% 100%);
  --thumb-highlight: hsl(0 0% 0% / 25%);

  --thumb-color: var(--thumb);
  --thumb-color-highlight: var(--thumb-highlight);

  @media (prefers-color-scheme: dark) {
    --thumb: hsl(0 0% 5%);
    --thumb-highlight: hsl(0 0% 100% / 25%);
  }
}

모션 감소

명확한 별칭을 추가하고 반복을 줄이기 위해 감소된 모션 환경설정 사용자 미디어 쿼리는 이 미디어 쿼리 5의 초안 사양에 따라 PostCSS 플러그인을 사용하여 맞춤 속성에 배치할 수 있습니다.

@custom-media --motionOK (prefers-reduced-motion: no-preference);

마크업

<input type="checkbox" role="switch"> 요소를 <label>로 래핑하여 체크박스와 라벨 연결의 모호성을 피하기 위해 관계를 번들로 묶고 사용자에게 라벨과 상호작용하여 입력을 전환할 수 있는 기능을 제공했습니다.

스타일이 적용되지 않은 자연스러운 라벨과 체크박스

<label for="switch" class="gui-switch">
  Label text
  <input type="checkbox" role="switch" id="switch">
</label>

<input type="checkbox">API상태로 사전 빌드되어 제공됩니다. 브라우저는 checked 속성과 oninputonchanged와 같은 입력 이벤트를 관리합니다.

레이아웃

Flexbox, 그리드, 맞춤 속성은 이 구성요소의 스타일을 유지하는 데 중요합니다. 값을 중앙 집중화하고, 모호한 계산이나 영역에 이름을 지정하며, 간편한 구성요소 맞춤설정을 위해 작은 맞춤 속성 API를 사용 설정합니다.

.gui-switch

스위치의 최상위 레이아웃은 flexbox입니다. .gui-switch 클래스에는 하위 요소가 레이아웃을 계산하는 데 사용하는 비공개 및 공개 맞춤 속성이 포함되어 있습니다.

Flexbox DevTools가 가로 라벨과 스위치를 오버레이하여 공간의 레이아웃 분포를 보여줍니다.

.gui-switch {
  display: flex;
  align-items: center;
  gap: 2ch;
  justify-content: space-between;
}

flexbox 레이아웃을 확장하고 수정하는 것은 다른 flexbox 레이아웃을 변경하는 것과 같습니다. 예를 들어 스위치 위 또는 아래에 라벨을 배치하거나 flex-direction를 변경하려면 다음을 실행합니다.

세로 라벨과 스위치를 오버레이하는 Flexbox DevTools

<label for="light-switch" class="gui-switch" style="flex-direction: column">
  Default
  <input type="checkbox" role="switch" id="light-switch">
</label>

추적

체크박스 입력은 일반 appearance: checkbox를 삭제하고 대신 자체 크기를 제공하여 스위치 트랙으로 스타일이 지정됩니다.

스위치 트랙에 오버레이된 그리드 DevTools로, 이름이 지정된 그리드 트랙 영역이 &#39;track&#39;라는 이름으로 표시됩니다.

.gui-switch > input {
  appearance: none;

  inline-size: var(--track-size);
  block-size: var(--thumb-size);
  padding: var(--track-padding);

  flex-shrink: 0;
  display: grid;
  align-items: center;
  grid: [track] 1fr / [track] 1fr;
}

또한 트랙은 썸네일이 소유권을 주장할 수 있는 1x1 단일 셀 그리드 트랙 영역을 만듭니다.

미리보기

스타일 appearance: none는 브라우저에서 제공하는 시각적 체크표시도 삭제합니다. 이 구성요소는 입력의 의사 요소:checked 의사 클래스를 사용하여 이 시각적 표시기를 대체합니다.

썸네일은 input[type="checkbox"]에 연결된 가상 요소 하위 요소이며 그리드 영역 track을 주장하여 트랙 아래가 아닌 위에 스택됩니다.

CSS 그리드 내에 배치된 가상 요소 썸네일을 보여주는 DevTools

.gui-switch > input::before {
  content: "";
  grid-area: track;
  inline-size: var(--thumb-size);
  block-size: var(--thumb-size);
}

스타일

맞춤 속성을 사용하면 색상 구성표, 오른쪽에서 왼쪽으로 읽는 언어, 모션 환경설정에 맞게 조정되는 다목적 스위치 구성요소를 사용할 수 있습니다.

스위치와 상태의 밝은 테마와 어두운 테마를 나란히 비교한 모습입니다.

터치 상호작용 스타일

휴대기기에서는 브라우저가 라벨과 입력란에 탭 강조 표시 및 텍스트 선택 기능을 추가합니다. 이로 인해 전환에 필요한 스타일과 시각적 상호작용 피드백에 부정적인 영향을 미쳤습니다. CSS 몇 줄로 이러한 효과를 삭제하고 나만의 cursor: pointer 스타일을 추가할 수 있습니다.

.gui-switch {
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

이러한 스타일은 유용한 시각적 상호작용 피드백이 될 수 있으므로 항상 삭제하는 것은 좋지 않습니다. 삭제하는 경우 맞춤 대안을 제공해야 합니다.

추적

이 요소의 스타일은 주로 모양과 색상과 관련이 있으며, 이는 캐스케이드를 통해 상위 .gui-switch에서 액세스합니다.

맞춤 트랙 크기 및 색상의 스위치 변형

.gui-switch > input {
  appearance: none;
  border: none;
  outline-offset: 5px;
  box-sizing: content-box;

  padding: var(--track-padding);
  background: var(--track-color-inactive);
  inline-size: var(--track-size);
  block-size: var(--thumb-size);
  border-radius: var(--track-size);
}

스위치 트랙의 다양한 맞춤설정 옵션은 4가지 맞춤 속성에서 가져옵니다. appearance: none가 일부 브라우저에서 체크박스의 테두리를 삭제하지 않으므로 border: none가 추가되었습니다.

미리보기

썸네일 요소가 이미 오른쪽 track에 있지만 원 스타일이 필요합니다.

.gui-switch > input::before {
  background: var(--thumb-color);
  border-radius: 50%;
}

원형 썸네일 가상 요소가 강조 표시된 DevTools

상호작용

맞춤 속성을 사용하여 마우스 오버 강조 표시 및 썸네일 위치 변경사항을 표시하는 상호작용을 준비합니다. 모션 또는 마우스 오버 강조 표시 스타일을 전환하기 전에 사용자의 환경설정도 확인됩니다.

.gui-switch > input::before {
  box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);

  @media (--motionOK) { & {
    transition:
      transform var(--thumb-transition-duration) ease,
      box-shadow .25s ease;
  }}
}

엄지 위치

맞춤 속성은 트랙에서 썸네일을 배치하기 위한 단일 소스 메커니즘을 제공합니다. 사용 가능한 트랙 및 썸 크기는 계산 시 썸을 적절하게 오프셋하고 트랙 내에 유지하는 데 사용됩니다. 0%100%

input 요소는 위치 변수 --thumb-position를 소유하고 있으며 thumb 가상 요소는 이를 translateX 위치로 사용합니다.

.gui-switch > input {
  --thumb-position: 0%;
}

.gui-switch > input::before {
  transform: translateX(var(--thumb-position));
}

이제 CSS와 체크박스 요소에 제공된 의사 클래스에서 --thumb-position를 자유롭게 변경할 수 있습니다. 이 요소에서 앞서 조건부로 transition: transform var(--thumb-transition-duration) ease를 설정했으므로 변경 시 다음과 같은 변경사항이 애니메이션으로 처리될 수 있습니다.

/* positioned at the end of the track: track length - 100% (thumb width) */
.gui-switch > input:checked {
  --thumb-position: calc(var(--track-size) - 100%);
}

/* positioned in the center of the track: half the track - half the thumb */
.gui-switch > input:indeterminate {
  --thumb-position: calc(
    (var(--track-size) / 2) - (var(--thumb-size) / 2)
  );
}

이러한 분리된 조정이 잘 작동했다고 생각합니다. thumb 요소는 translateX 위치라는 하나의 스타일에만 관심이 있습니다. 입력은 모든 복잡성과 계산을 관리할 수 있습니다.

세로

input 요소에 CSS 변환을 사용하여 회전을 추가하는 수정자 클래스 -vertical를 사용하여 지원이 이루어졌습니다.

3D 회전된 요소는 구성요소의 전체 높이를 변경하지 않으므로 블록 레이아웃이 손상될 수 있습니다. --track-size--track-padding 변수를 사용하여 이를 고려합니다. 세로 버튼이 레이아웃에서 예상대로 흐르도록 하는 데 필요한 최소 공간을 계산합니다.

.gui-switch.-vertical {
  min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));

  & > input {
    transform: rotate(-90deg);
  }
}

(RTL) 오른쪽에서 왼쪽으로

CSS 친구인 엘라드 셰이커와 저는 단일 변수를 전환하여 오른쪽에서 왼쪽으로 언어를 처리하는 CSS 변환을 사용한 슬라이드 아웃 측면 메뉴의 프로토타입을 함께 제작했습니다. CSS에는 논리적 속성 변환이 없으며 앞으로도 없을 수 있기 때문에 이렇게 했습니다. 엘라드는 맞춤 속성 값을 사용하여 비율을 반전하여 논리 변환을 위한 자체 맞춤 로직을 단일 위치에서 관리할 수 있는 좋은 아이디어를 생각해 냈습니다. 이 스위치에서도 동일한 기법을 사용했는데 효과가 좋았습니다.

.gui-switch {
  --isLTR: 1;

  &:dir(rtl) {
    --isLTR: -1;
  }
}

--isLTR라는 맞춤 속성은 처음에 1 값을 보유합니다. 즉, 레이아웃이 기본적으로 왼쪽에서 오른쪽이므로 true입니다. 그런 다음 CSS 가상 클래스 :dir()를 사용하여 구성요소가 오른쪽에서 왼쪽 레이아웃 내에 있으면 값이 -1로 설정됩니다.

변환 내의 calc() 내에서 --isLTR를 사용하여 --isLTR를 실행합니다.

.gui-switch.-vertical > input {
  transform: rotate(-90deg);
  transform: rotate(calc(90deg * var(--isLTR) * -1));
}

이제 세로 스위치의 회전이 오른쪽에서 왼쪽 레이아웃에 필요한 반대 측 위치를 고려합니다.

반대쪽 요구사항을 고려하도록 썸 가상 요소의 translateX 변환도 업데이트해야 합니다.

.gui-switch > input:checked {
  --thumb-position: calc(var(--track-size) - 100%);
  --thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}

.gui-switch > input:indeterminate {
  --thumb-position: calc(
    (var(--track-size) / 2) - (var(--thumb-size) / 2)
  );
  --thumb-position: calc(
   ((var(--track-size) / 2) - (var(--thumb-size) / 2))
    * var(--isLTR)
  );
}

이 접근 방식은 논리적 CSS 변환과 같은 개념과 관련된 모든 요구사항을 해결하는 데는 적합하지 않지만, 많은 사용 사례에 DRY 원칙을 제공합니다.

내장 input[type="checkbox"]를 사용하려면 :checked, :disabled, :indeterminate, :hover과 같은 다양한 상태를 처리해야 합니다. :focus는 의도적으로 그대로 두고 오프셋만 조정했습니다. 포커스 링은 Firefox 및 Safari에서 멋지게 표시되었습니다.

Firefox 및 Safari의 스위치에 초점을 맞춘 포커스 링의 스크린샷

선택됨

<label for="switch-checked" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>

이 상태는 on 상태를 나타냅니다. 이 상태에서 입력 '트랙' 배경은 활성 색상으로 설정되고 썸 위치는 '끝'으로 설정됩니다.

.gui-switch > input:checked {
  background: var(--track-color-active);
  --thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}

사용 중지됨

<label for="switch-disabled" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-disabled" disabled="true">
</label>

:disabled 버튼은 시각적으로 다르게 보일 뿐만 아니라 요소를 변경 불가하게 만들어야 합니다.상호작용 불변성은 브라우저에서 자유롭지만 시각적 상태에는 appearance: none를 사용하기 때문에 스타일이 필요합니다.

.gui-switch > input:disabled {
  cursor: not-allowed;
  --thumb-color: transparent;

  &::before {
    cursor: not-allowed;
    box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 50%);

    @media (prefers-color-scheme: dark) { & {
      box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 50%);
    }}
  }
}

사용 중지, 선택, 선택 해제 상태의 어두운 스타일 스위치

이 상태는 사용 중지 및 선택 상태가 모두 있는 어두운 테마와 밝은 테마가 필요하므로 까다롭습니다. 스타일 조합의 유지보수 부담을 덜기 위해 이러한 상태에 스타일적으로 최소한의 스타일을 선택했습니다.

불확실

종종 잊어버리는 상태는 체크박스가 선택 또는 선택 해제되지 않은 :indeterminate입니다. 재미있는 상태로, 매력적이고 겸손한 상태입니다. 불리언 상태는 중간 상태가 은밀하게 있을 수 있다는 것을 상기시켜 주는 좋은 예입니다.

체크박스를 불확정으로 설정하는 것은 까다롭습니다. JavaScript만 설정할 수 있습니다.

<label for="switch-indeterminate" class="gui-switch">
  Indeterminate
  <input type="checkbox" role="switch" id="switch-indeterminate">
  <script>document.getElementById('switch-indeterminate').indeterminate = true</script>
</label>

확정되지 않은 상태로, 트랙 썸네일이 가운데에 있어 결정되지 않았음을 나타냅니다.

이 상태는 소박하고 매력적이므로 스위치 엄지손가락 위치를 가운데에 배치하는 것이 적절하다고 생각했습니다.

.gui-switch > input:indeterminate {
  --thumb-position: calc(
    calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
    * var(--isLTR)
  );
}

마우스 오버

마우스 오버 상호작용은 연결된 UI를 시각적으로 지원하고 대화형 UI로의 방향을 제공해야 합니다. 이 스위치는 라벨이나 입력란에 마우스를 가져가면 반투명 링으로 썸네일을 강조 표시합니다. 그러면 이 마우스 오버 애니메이션이 대화형 썸네일 요소를 향한 방향을 제공합니다.

'강조 표시' 효과는 box-shadow로 실행됩니다. 사용 중지되지 않은 입력의 마우스 오버 시 --highlight-size 크기를 늘립니다. 사용자가 모션을 괜찮게 생각하면 box-shadow를 전환하여 크기가 커지는 것을 확인할 수 있고, 모션을 괜찮게 생각하지 않으면 강조 표시가 즉시 표시됩니다.

.gui-switch > input::before {
  box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);

  @media (--motionOK) { & {
    transition:
      transform var(--thumb-transition-duration) ease,
      box-shadow .25s ease;
  }}
}

.gui-switch > input:not(:disabled):hover::before {
  --highlight-size: .5rem;
}

자바스크립트

스위치 인터페이스는 물리적 인터페이스를 에뮬레이션하려고 시도할 때, 특히 트랙 안에 원이 있는 이러한 종류의 스위치는 기이하게 느껴질 수 있습니다. iOS는 스위치를 통해 이 문제를 해결했습니다. 스위치를 좌우로 드래그할 수 있으며 이 옵션을 사용할 수 있어 매우 만족스럽습니다. 반대로 드래그 동작을 시도했는데 아무 일도 일어나지 않으면 UI 요소가 비활성 상태로 느껴질 수 있습니다.

드래그 가능한 썸네일

thumb 가상 요소는 .gui-switch > input 범위의 var(--thumb-position)에서 위치를 수신합니다. JavaScript는 입력에 인라인 스타일 값을 제공하여 thumb 위치를 동적으로 업데이트하여 포인터 동작을 따르는 것처럼 보이게 할 수 있습니다. 포인터가 해제되면 인라인 스타일을 삭제하고 맞춤 속성 --thumb-position를 사용하여 드래그가 꺼짐에 더 가까웠는지 또는 켜짐에 더 가까웠는지 확인합니다. 이는 솔루션의 핵심입니다. 포인터 이벤트는 조건부로 포인터 위치를 추적하여 CSS 맞춤 속성을 수정합니다.

이 스크립트가 표시되기 전에 구성요소는 이미 100% 작동하고 있었으므로 라벨을 클릭하여 입력을 전환하는 것과 같은 기존 동작을 유지하려면 상당한 작업이 필요합니다. JavaScript는 기존 기능을 희생하여 기능을 추가해서는 안 됩니다.

touch-action

드래그는 커스텀 동작이므로 touch-action 이점을 활용하기에 적합합니다. 이 스위치의 경우 가로 동작은 스크립트에서 처리하거나 세로 스위치 변형에 대해 캡처된 세로 동작을 처리해야 합니다. touch-action를 사용하면 브라우저에 이 요소에서 처리할 동작을 알려 스크립트가 경쟁 없이 동작을 처리할 수 있습니다.

다음 CSS는 포인터 동작이 이 스위치 트랙 내에서 시작되면 세로 동작을 처리하고 가로 동작은 아무것도 하지 않도록 브라우저에 지시합니다.

.gui-switch > input {
  touch-action: pan-y;
}

원하는 결과는 페이지를 화면 이동하거나 스크롤하지 않는 가로 동작입니다. 포인터는 입력 내에서 시작을 세로로 스크롤하고 페이지를 스크롤할 수 있지만, 가로 포인터는 맞춤 처리됩니다.

픽셀 값 스타일 유틸리티

설정 시와 드래그 중에 요소에서 계산된 다양한 숫자 값을 가져와야 합니다. 다음 JavaScript 함수는 CSS 속성에 따라 계산된 픽셀 값을 반환합니다. 이 값은 getStyle(checkbox, 'padding-left')와 같은 설정 스크립트에 사용됩니다.

​​const getStyle = (element, prop) => {
  return parseInt(window.getComputedStyle(element).getPropertyValue(prop));
}

const getPseudoStyle = (element, prop) => {
  return parseInt(window.getComputedStyle(element, ':before').getPropertyValue(prop));
}

export {
  getStyle,
  getPseudoStyle,
}

window.getComputedStyle()가 두 번째 인수인 타겟 가상 요소를 허용하는 방식을 확인합니다. JavaScript가 가상 요소에서조차 요소의 많은 값을 읽을 수 있다는 것은 꽤 멋진 일입니다.

dragging

이는 드래그 로직의 핵심 순간이며 함수 이벤트 핸들러에서 몇 가지 주목할 사항이 있습니다.

const dragging = event => {
  if (!state.activethumb) return

  let {thumbsize, bounds, padding} = switches.get(state.activethumb.parentElement)
  let directionality = getStyle(state.activethumb, '--isLTR')

  let track = (directionality === -1)
    ? (state.activethumb.clientWidth * -1) + thumbsize + padding
    : 0

  let pos = Math.round(event.offsetX - thumbsize / 2)

  if (pos < bounds.lower) pos = 0
  if (pos > bounds.upper) pos = bounds.upper

  state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
}

스크립트 주인공은 state.activethumb입니다. 이 스크립트는 포인터와 함께 작은 원을 배치합니다. switches 객체는 키가 .gui-switch이고 값이 스크립트의 효율성을 유지하는 캐시된 경계 및 크기인 Map()입니다. 오른쪽에서 왼쪽은 CSS가 --isLTR인 것과 동일한 맞춤 속성을 사용하여 처리되며 이를 사용하여 로직을 반전하고 RTL을 계속 지원할 수 있습니다. event.offsetX도 유용합니다. 엄지손가락 배치에 유용한 델타 값이 포함되어 있기 때문입니다.

state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)

이 CSS의 마지막 줄은 썸네일 요소에서 사용되는 맞춤 속성을 설정합니다. 이 값 할당은 시간이 지남에 따라 전환되지만 이전 포인터 이벤트가 일시적으로 --thumb-transition-duration0s로 설정하여 느린 상호작용을 제거했습니다.

dragEnd

사용자가 스위치 밖으로 멀리 드래그한 다음 놓을 수 있도록 하려면 전역 창 이벤트를 등록해야 합니다.

window.addEventListener('pointerup', event => {
  if (!state.activethumb) return

  dragEnd(event)
})

사용자가 자유롭게 드래그할 수 있고 인터페이스가 이를 고려할 만큼 스마트해야 한다고 생각합니다. 이 스위치로 이를 처리하는 데는 많은 시간이 걸리지 않았지만 개발 과정에서는 신중하게 고려해야 했습니다.

const dragEnd = event => {
  if (!state.activethumb) return

  state.activethumb.checked = determineChecked()

  if (state.activethumb.indeterminate)
    state.activethumb.indeterminate = false

  state.activethumb.style.removeProperty('--thumb-transition-duration')
  state.activethumb.style.removeProperty('--thumb-position')
  state.activethumb.removeEventListener('pointermove', dragging)
  state.activethumb = null

  padRelease()
}

요소와의 상호작용이 완료되었습니다. 이제 입력 checked 속성을 설정하고 모든 동작 이벤트를 삭제합니다. 체크박스는 state.activethumb.checked = determineChecked()로 변경됩니다.

determineChecked()

dragEnd에서 호출되는 이 함수는 썸 현재가 트랙 경계 내에 있는 위치를 확인하고 트랙의 절반 지점과 같거나 그 이상인 경우 true를 반환합니다.

const determineChecked = () => {
  let {bounds} = switches.get(state.activethumb.parentElement)

  let curpos =
    Math.abs(
      parseInt(
        state.activethumb.style.getPropertyValue('--thumb-position')))

  if (!curpos) {
    curpos = state.activethumb.checked
      ? bounds.lower
      : bounds.upper
  }

  return curpos >= bounds.middle
}

추가 생각

드래그 동작은 선택한 초기 HTML 구조로 인해 약간의 코드 부채가 발생했습니다. 특히 입력을 라벨로 래핑하는 경우가 그렇습니다. 라벨은 상위 요소이므로 입력 후 클릭 상호작용을 수신합니다. dragEnd 이벤트 끝에 padRelease()가 이상한 함수로 표시되었을 수 있습니다.

const padRelease = () => {
  state.recentlyDragged = true

  setTimeout(_ => {
    state.recentlyDragged = false
  }, 300)
}

이는 사용자가 실행한 상호작용을 선택 해제하거나 선택할 수 있으므로 라벨이 나중에 클릭을 받음을 고려하기 위함입니다.

이 작업을 다시 해야 한다면 라벨 클릭을 직접 처리하고 내장 동작과 충돌하지 않는 요소를 만들기 위해 UX 업그레이드 중에 JavaScript로 DOM을 조정하는 것이 좋을 수 있습니다.

이런 종류의 JavaScript는 작성하기가 가장 싫습니다. 조건부 이벤트 버블링을 관리하고 싶지 않습니다.

const preventBubbles = event => {
  if (state.recentlyDragged)
    event.preventDefault() && event.stopPropagation()
}

결론

이 작은 스위치 구성요소는 지금까지의 모든 GUI 챌린지 중 가장 많은 작업이 필요했습니다. 이제 제가 어떻게 했는지 알았으니 어떻게 하시겠어요? 🙂

접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다. 데모를 만들어 트윗해 주시면 아래의 커뮤니티 리믹스 섹션에 추가해 드리겠습니다.

커뮤니티 리믹스

리소스

.gui-switch GitHub의 소스 코드를 찾습니다.