switch 구성요소 빌드

반응형이고 접근 가능한 스위치 구성요소를 빌드하는 방법에 관한 기본 개요입니다.

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

데모

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

개요

스위치는 체크박스와 유사하게 작동하지만 불리언 켜짐 및 꺼짐 상태를 명시적으로 나타냅니다.

이 데모는 대부분의 기능에 <input type="checkbox" role="switch">를 사용합니다. <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 속성과 oninput, onchanged와 같은 입력 이벤트를 관리합니다.

레이아웃

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

.gui-switch

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

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

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

플렉스박스 레이아웃을 확장하고 수정하는 것은 플렉스박스 레이아웃을 변경하는 것과 같습니다. 예를 들어 스위치 위 또는 아래에 라벨을 배치하거나 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);
}

스위치 트랙의 다양한 맞춤설정 옵션은 네 가지 맞춤 속성에서 비롯됩니다. border: noneappearance: 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를 소유하고 엄지손가락 가상 요소는 이를 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)
  );
}

분리된 오케스트레이션이 잘 작동한다고 생각했습니다. 썸 요소는 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 친구인 Elad Schecter와 저는 단일 변수를 뒤집어 오른쪽에서 왼쪽으로 쓰는 언어를 처리하는 CSS 변환을 사용하여 슬라이드 아웃 측면 메뉴를 함께 프로토타입으로 만들었습니다. CSS에는 논리적 속성 변환이 없으며 앞으로도 없을 수 있기 때문에 이렇게 했습니다. Elad는 맞춤 속성 값을 사용하여 백분율을 반전시켜 논리적 변환을 위한 자체 맞춤 로직을 단일 위치에서 관리할 수 있도록 하는 아이디어를 냈습니다. 이 스위치에서 동일한 기법을 사용했고 결과가 아주 좋았습니다.

.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 요소가 비활성 상태인 것처럼 느껴질 수 있습니다.

드래그 가능한 썸

엄지손가락 가상 요소는 .gui-switch > input 범위가 지정된 var(--thumb-position)에서 위치를 가져옵니다. JavaScript는 입력에 인라인 스타일 값을 제공하여 엄지손가락 위치를 동적으로 업데이트하여 포인터 동작을 따르는 것처럼 보이게 할 수 있습니다. 포인터가 해제되면 인라인 스타일을 삭제하고 맞춤 속성 --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()
}

요소와의 상호작용이 완료되었습니다. 입력 확인 속성을 설정하고 모든 동작 이벤트를 삭제해야 합니다. 체크박스가 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에서 소스 코드를 확인하세요.