대화상자 구성요소 빌드

<dialog> 요소를 사용하여 색상 적응형, 반응형, 접근성이 뛰어난 미니 및 메가 모달을 빌드하는 방법에 관한 기본 개요입니다.

이 게시물에서는 색상 적응성을 구축하는 방법과 <dialog> 요소를 사용하여 반응형과 액세스 가능한 미니 및 메가 모달 데모를 사용해 보고 출처!

밝은 테마와 어두운 테마의 메가 및 미니 대화상자를 보여줍니다.

동영상을 선호한다면 이 게시물의 YouTube 버전을 참고하세요.

개요

<dialog> 요소는 인페이지 문맥 정보 또는 작업에 적합합니다. 사용자가 여러 페이지 대신 동일한 페이지 작업을 통해 사용자 환경을 개선할 수 있음 양식이 작거나 확인합니다.

<dialog> 요소가 최근 여러 브라우저에서 안정화되었습니다.

브라우저 지원

  • 37
  • 79
  • 98
  • 15.4

소스

요소에 몇 가지가 누락되어 이 GUI에서 과제: 개발자 환경을 추가합니다. 예상되는 항목: 추가 이벤트, 가벼운 닫기, 맞춤 애니메이션 및 미니 있습니다.

마크업

<dialog> 요소의 필수 요소는 소박합니다. 이 요소는 자동으로 숨겨지고 콘텐츠를 오버레이하는 스타일이 내장되어 있습니다.

<dialog>
  …
</dialog>

이 기준을 개선할 수 있습니다.

일반적으로 대화상자 요소는 모달과 많은 부분을 공유하며 서로 바꿔서 사용할 수 있습니다. 여기서는 텍스트에 대화상자 요소를 자유롭게 작은 대화상자 팝업 (미니)과 전체 페이지 대화상자 (메가)를 모두 지원합니다. 이름을 메가와 미니 모두 두 가지로 구성되어 있으며, 두 대화상자 모두 서로 다른 사용 사례에 맞게 약간 조정되었습니다. 유형을 지정할 수 있도록 modal-mode 속성을 추가했습니다.

<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>

밝은 테마와 어두운 테마의 미니 및 메가 대화상자 스크린샷.

항상 그런 것은 아니지만, 일반적으로 대화상자 요소를 사용하여 상호작용 정보를 찾습니다. 대화상자 요소 내부의 양식은 어디서나 사용할 수 있습니다 함께 그룹화할 수 있습니다. 양식 요소가 대화상자 콘텐츠를 래핑하도록 하여 JavaScript는 사용자가 입력한 데이터에 액세스할 수 있습니다. 또한, 내부의 버튼은 method="dialog"를 사용하는 양식은 JavaScript 없이 대화상자를 닫고 데이터를 수집하는 데 사용됩니다

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    …
    <button value="cancel">Cancel</button>
    <button value="confirm">Confirm</button>
  </form>
</dialog>

메가 대화상자

메가 대화상자에는 양식 안에 세 가지 요소가 있습니다. <header>님, <article>, 및 <footer>: 이들은 시맨틱 컨테이너 역할을 할 뿐만 아니라 살펴보겠습니다. 헤더는 모달의 제목을 지정하고 버튼을 클릭합니다. 이 도움말은 양식 입력 및 정보를 위한 것입니다. 푸터에는 <menu>/ 실행할 수 있습니다

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    <header>
      <h3>Dialog title</h3>
      <button onclick="this.closest('dialog').close('close')"></button>
    </header>
    <article>...</article>
    <footer>
      <menu>
        <button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
        <button type="submit" value="confirm">Confirm</button>
      </menu>
    </footer>
  </form>
</dialog>

첫 번째 메뉴 버튼에는 autofocusonclick 인라인 이벤트 핸들러가 있습니다. autofocus 속성은 포커스를 두는 것이 좋습니다 취소 버튼을 클릭합니다. 이렇게 하면 우발적이어서는 안 됩니다.

미니 대화상자

미니 대화상자는 메가 대화상자와 매우 비슷하며, <header> 요소 이렇게 하면 더 작고 인라인으로 만들 수 있습니다.

<dialog id="MiniDialog" modal-mode="mini">
  <form method="dialog">
    <article>
      <p>Are you sure you want to remove this user?</p>
    </article>
    <footer>
      <menu>
        <button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
        <button type="submit" value="confirm">Confirm</button>
      </menu>
    </footer>
  </form>
</dialog>

대화상자 요소는 전체 표시 영역 요소를 위한 강력한 기반을 제공하며 데이터 및 사용자 상호작용을 수집할 수 있습니다 이러한 필수 요소들은 흥미롭고 효과적인 상호작용을 수행할 수 있습니다

접근성

대화상자 요소의 기본 제공 접근성이 매우 우수합니다. 이러한 포드를 이미 많은 기능이 포함되어 있습니다.

포커스 복원 중

sidenav 빌드에서 직접 한 것처럼 구성요소를 사용할 때 무언가를 적절하게 열고 닫을 때 관련된 시작 및 종료에 초점을 맞춥니다. 버튼을 클릭합니다. 측면 탐색이 열리면 닫기 버튼에 포커스가 맞춰집니다. 이 닫기 버튼을 누르면 포커스가 열린 버튼으로 복원됩니다.

대화상자 요소를 사용하면 기본으로 제공되는 동작입니다.

안타깝게도 대화상자에 애니메이션을 적용하고 싶은 경우 이 기능을 사용하면 확인할 수 있습니다 JavaScript 섹션에서 해당 항목을 기능을 제공합니다

트래핑 포커스

대화상자 요소는 inert 에 대해 자세히 알아보세요. inert 이전에는 포커스를 감시하는 데 JavaScript가 사용되었습니다. 요소가 떠나면 요소가 가로채서 다시 배치합니다.

브라우저 지원

  • 102
  • 102
  • 112
  • 15.5

소스

inert 이후에는 문서의 모든 부분이 '고정'될 수 있습니다. Kubernetes가 더 이상 포커스 타겟이 없거나 마우스로 상호작용하지 않습니다. 트래핑하는 대신 포커스는 문서의 유일한 상호작용 부분으로 안내됩니다.

요소를 열고 자동으로 포커스 지정

기본적으로 대화상자 요소는 포커스 가능한 첫 번째 요소에 포커스를 할당합니다. 를 사용합니다. 사용자가 기본값으로 사용하기에 가장 적합한 요소가 아닌 경우 autofocus 속성을 사용합니다. 앞서 설명한 것처럼 확인 버튼이 아닌 취소 버튼에 두도록 해야 합니다 이렇게 하면 확인은 의도적인 것이며 실수가 아닙니다.

Esc 키로 닫기

방해가 될 수 있는 요소를 쉽게 닫는 것이 중요합니다. 다행히 대화상자 요소가 Esc 키를 처리하므로 오케스트레이션 부담에서 벗어날 수 있습니다

스타일

대화상자 요소의 스타일을 지정하는 쉬운 경로와 하드 경로가 있습니다. 간편한 대화상자의 표시 속성을 변경하지 않고 사용할 수 있습니다 맞춤 애니메이션을 제공하기 위해 많은 노력을 기울입니다. 대화상자 열기 및 닫기, display 속성 인계 등

열린 소품으로 스타일 지정

적응형 색상과 전반적인 디자인 일관성을 가속화하기 위해 당연하게도 CSS 변수 라이브러리 Open Props를 가져왔습니다. 포함 무료로 제공되는 변수 외에 정규화buttons, 둘 다 Open Props 선택적 가져오기로 제공됩니다. 이러한 가져오기를 이용하면 많은 스타일이 필요하지 않고 그것을 지원하고 보이게 만들기 위해 좋습니다.

<dialog> 요소의 스타일 지정

디스플레이 속성 소유

대화상자 요소의 기본 표시 및 숨기기 동작은 표시를 전환합니다. 속성을 block에서 none로 변경합니다. 이는 안타깝지만 애니메이션으로는 안팎으로, 안으로만 들어가야 합니다. 안팎으로 애니메이션을 적용하고 싶은데, 첫 번째 단계는 직접 설정 display 속성이 포함되어 있는 것을 볼 수 있습니다.

dialog {
  display: grid;
}

상당한 양의 스타일을 관리할 수 있지만, 적절한 사용자 환경을 촉진해야 합니다. 첫째, 대화상자의 기본 상태는 닫힘 이 상태를 시각적으로 표시하고 대화상자가 다음과 같이 표시되지 않도록 할 수 있습니다. 다음 스타일로 상호작용을 수신하는 방법:

dialog:not([open]) {
  pointer-events: none;
  opacity: 0;
}

이제 대화상자가 표시되지 않으며 열려 있지 않을 때는 상호작용할 수 없습니다. 나중에 대화상자의 inert 속성을 관리하기 위해 JavaScript를 몇 가지 추가하겠습니다. 키보드와 스크린 리더 사용자도 숨겨진 대화상자에 도달할 수 없습니다.

대화상자에 적응형 색상 테마 지정

표면 색상을 보여주는 밝은 테마와 어두운 테마를 보여주는 메가 대화상자

color-scheme는 문서를 브라우저 제공으로 선택합니다. 밝은/어두운 시스템 환경설정에 맞춰 자동 조절 색상 테마를 적용하는 것이었습니다. 그보다 더 큰 크기입니다. Open Props는 몇 가지 표시 경로를 제공합니다. 자동으로 조정되는 색상 color-scheme 사용과 마찬가지로 밝은 시스템 설정과 어두운 시스템 환경설정 이러한 디자인에서 레이어를 만드는 데 적합하고 색상을 사용하여 레이어 표면의 이러한 모양을 시각적으로 지원합니다. 배경 색상: var(--surface-1); 해당 레이어 위에 배치하려면 var(--surface-2)를 사용합니다.

dialog {
  …
  background: var(--surface-2);
  color: var(--text-1);
}

@media (prefers-color-scheme: dark) {
  dialog {
    border-block-start: var(--border-size-1) solid var(--surface-3);
  }
}

헤더와 같은 하위 요소에는 나중에 더 많은 적응형 색상이 추가됩니다. 있습니다. 대화상자 요소의 경우 추가적으로 생각하지만 매력적이고 잘 설계된 대화상자 디자인을 만듭니다.

반응형 대화상자 크기 조정

대화상자는 기본적으로 파일의 크기를 콘텐츠로 위임합니다. 이는 일반적으로 좋습니다. 여기서 제 목표는 max-inline-size 읽을 수 있는 크기 (--size-content-3 = 60ch) 또는 표시 영역 너비의 90% 로 설정합니다. 이 대화상자가 모바일 기기에서 더 넓은 화면에 표시되지 않도록 하고 평범한 화면은 읽기 어렵습니다. 그런 다음 max-block-size 대화상자가 페이지의 높이를 초과하지 않도록 합니다. 이것은 또한 세로로 긴 너비일 때를 대비해 대화상자의 스크롤 가능 영역의 위치를 지정해야 합니다. 대화상자 요소입니다.

dialog {
  …
  max-inline-size: min(90vw, var(--size-content-3));
  max-block-size: min(80vh, 100%);
  max-block-size: min(80dvb, 100%);
  overflow: hidden;
}

max-block-size가 두 번 있는 것을 확인하셨나요? 첫 번째는 물리적 서버인 80vh를 사용합니다. 표시 영역 단위입니다. 제가 정말로 원하는 것은 대화가 상대적인 흐름에 있도록 하는 것입니다. 해외 사용자용이므로 논리적이고 최신이며 부분적으로만 dvb 단위를 지원합니다.

메가 대화상자 위치 지정

대화상자 요소의 위치를 지정하는 데 도움이 되도록 두 요소를 나누는 것이 좋습니다. 전체 화면 배경화면과 대화상자 컨테이너입니다. 배경화면은 모든 것을 커버할 수 있으며, 음영 효과를 제공하여 이 대화상자가 더 눈에 띄지 않게 뒤에 있는 콘텐츠에 액세스할 수 없습니다. 대화상자 컨테이너는 자유롭게 해당 배경의 중심에 맞추어 콘텐츠에 필요한 모양을 취합니다.

다음 스타일은 대화상자 요소를 창에 고정하여 각 요소로 늘립니다. margin: auto를 사용하여 콘텐츠를 중앙에 배치합니다.

dialog {
  …
  margin: auto;
  padding: 0;
  position: fixed;
  inset: 0;
  z-index: var(--layer-important);
}
모바일 메가 대화상자 스타일

작은 표시 영역에서는 전체 페이지 메가 모달 스타일을 약간 다르게 지정합니다. 난 하단 여백을 0로 설정합니다. 그러면 대화상자 콘텐츠가 있습니다. 몇 가지 스타일을 조정하면 대화상자를 사용자의 엄지손가락에 가까운 위치에 있는 작업 시트:

@media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    margin-block-end: 0;
    border-end-end-radius: 0;
    border-end-start-radius: 0;
  }
}

여백 간격을 오버레이하는 devtools의 스크린샷 
  표시됩니다.

미니 대화상자 포지셔닝

데스크톱 컴퓨터와 같이 더 큰 표시 영역을 사용할 때 확인할 수 있습니다. 그러려면 JavaScript가 필요합니다. g.co/newsinitiative에서 내가 사용하는 기술 여기, 하지만 이 내용은 이 글의 범위를 벗어나는 것 같습니다. JavaScript가 없으면 미니 대화상자가 메가 대화상자처럼 화면 중앙에 나타납니다.

눈에 확 띄도록 만들기

마지막으로, 대화상자를 조금 더 생동감 있게 배치하여 먼 곳에 있는 부드러운 표면처럼 보이도록 합니다. 을 클릭합니다. 부드러움은 대화상자의 모서리를 둥글게 처리하여 만들 수 있습니다. 세심하게 제작된 Open Props의 그림자 중 하나로 깊이를 표현합니다. props를 제공합니다.

dialog {
  …
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-6);
}

배경화면 의사 요소 맞춤설정

저는 배경에 블러 효과를 추가하면서 배경화면을 아주 가볍게 조정해 봤습니다. backdrop-filter 메가 대화상자에 추가합니다.

브라우저 지원

  • 76
  • 79
  • 103
  • 18

소스

dialog[modal-mode="mega"]::backdrop {
  backdrop-filter: blur(25px);
}

또한 브라우저가 backdrop-filter에서 를 사용하면 향후 배경화면 요소를 전환할 수 있습니다.

dialog::backdrop {
  transition: backdrop-filter .5s ease;
}

다채로운 아바타의 배경을 흐리게 하는 메가 대화상자의 스크린샷

추가 스타일 지정

이 섹션을 'extras'라고 부릅니다. 대화상자 요소와 더 많은 관련이 있기 때문입니다. 데모보다 데모를 더 쉽게 할 수 있습니다.

스크롤 포함

대화상자가 표시되면 사용자는 여전히 그 뒤로 페이지를 스크롤할 수 있습니다. 원하지 않는 사항:

일반적으로 overscroll-behavior 일반적인 해결책이 될 수 있지만 사양 스크롤 포트가 아니기 때문에 대화상자에는 아무런 영향을 미치지 않습니다. 지양해야 할 것이 없습니다 JavaScript를 사용하여 이 가이드의 새 이벤트(예: '폐쇄됨') '열림'을 선택하고 문서에 대한 overflow: hidden 작업을 하거나 :has()가 다음 위치에서 안정화될 때까지 기다릴 수 있습니다. 모든 브라우저:

브라우저 지원

  • 105
  • 105
  • 121
  • 15.4

소스

html:has(dialog[open][modal-mode="mega"]) {
  overflow: hidden;
}

이제 메가 대화상자가 열리면 HTML 문서에 overflow: hidden가 표시됩니다.

<form> 레이아웃

상호작용 정보를 수집하는 데 매우 중요한 요소일 뿐만 아니라 여기서 이 정보를 사용하여 머리글, 바닥글 및 기사 요소를 사용할 수 있습니다. 이 레이아웃에서는 기사 하위 요소를 지정할 수 있습니다. 이 목표를 달성하기 위해 grid-template-rows 기사 요소가 1fr로 지정되고 양식 자체의 최대값이 동일합니다. 높이를 대화상자 요소로 사용합니다. 이 확고한 높이와 확고한 행 크기를 설정하는 것은 를 사용하면 기사 요소를 제한하고 오버플로될 때 스크롤할 수 있습니다.

dialog > form {
  display: grid;
  grid-template-rows: auto 1fr auto;
  align-items: start;
  max-block-size: 80vh;
  max-block-size: 80dvb;
}

행 위에 그리드 레이아웃 정보를 오버레이하는 devtools의 스크린샷

대화상자 <header> 스타일 지정

이 요소의 역할은 대화상자 콘텐츠와 혜택의 제목을 제공하는 것입니다. 닫기 버튼을 쉽게 찾을 수 있어야 합니다. 또한 특정 이미지를 보이게 하기 위해 표면 색상이 지정됩니다. 대화상자 기사 콘텐츠 뒤에 표시됩니다. 이러한 요구사항으로 인해 Flexbox는 가장자리까지 간격을 두고 세로로 정렬된 항목, 패딩과 간격을 사용하여 제목과 닫기 버튼에 약간의 공간을 제공합니다.

dialog > form > header {
  display: flex;
  gap: var(--size-3);
  justify-content: space-between;
  align-items: flex-start;
  background: var(--surface-2);
  padding-block: var(--size-3);
  padding-inline: var(--size-5);
}

@media (prefers-color-scheme: dark) {
  dialog > form > header {
    background: var(--surface-1);
  }
}

대화상자 헤더에 Flexbox 레이아웃 정보를 오버레이하는 Chrome Devtools 스크린샷

헤더 닫기 버튼 스타일 지정

데모에서는 Open Props 버튼을 사용하므로 닫기 버튼이 맞춤설정됩니다. 다음과 같이 원형 아이콘 중심의 버튼으로 만들 수 있습니다.

dialog > form > header > button {
  border-radius: var(--radius-round);
  padding: .75ch;
  aspect-ratio: 1;
  flex-shrink: 0;
  place-items: center;
  stroke: currentColor;
  stroke-width: 3px;
}

헤더 닫기 버튼의 크기 및 패딩 정보를 오버레이하는 Chrome DevTools의 스크린샷

대화상자 <article> 스타일 지정

기사 요소는 이 대화상자에서 특별한 역할을 합니다. 즉, 기사 요소를 대화상자가 길거나 긴 대화상자의 경우 스크롤해야 할 수도 있습니다.

이를 위해 상위 양식 요소는 이 기사 요소가 도착할 때 도달할 제약 조건을 제공합니다. 너무 높음 스크롤바가 필요할 때만 표시되도록 overflow-y: auto를 설정합니다. 내부에 overscroll-behavior: contain로 스크롤이 포함되어 있고 나머지는 맞춤 프레젠테이션 스타일입니다.

dialog > form > article {
  overflow-y: auto; 
  max-block-size: 100%; /* safari */
  overscroll-behavior-y: contain;
  display: grid;
  justify-items: flex-start;
  gap: var(--size-3);
  box-shadow: var(--shadow-2);
  z-index: var(--layer-1);
  padding-inline: var(--size-5);
  padding-block: var(--size-3);
}

@media (prefers-color-scheme: light) {
  dialog > form > article {
    background: var(--surface-1);
  }
}

바닥글의 역할은 작업 버튼 메뉴를 포함하는 것입니다. Flexbox는 콘텐츠를 바닥글 인라인 축 끝에 정렬한 다음 버튼에 공간을 좀 주세요.

dialog > form > footer {
  background: var(--surface-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-3);
  justify-content: space-between;
  align-items: flex-start;
  padding-inline: var(--size-5);
  padding-block: var(--size-3);
}

@media (prefers-color-scheme: dark) {
  dialog > form > footer {
    background: var(--surface-1);
  }
}

바닥글 요소에 Flexbox 레이아웃 정보를 오버레이하는 Chrome Devtools 스크린샷

menu 요소는 대화상자의 작업 버튼을 포함하는 데 사용됩니다. 이는 래핑을 사용함 버튼 사이에 공간을 제공하는 gap가 있는 Flexbox 레이아웃 메뉴 요소 <ul>와 같은 패딩이 있어야 합니다. 또한 해당 스타일은 필요하지 않으므로 삭제합니다.

dialog > form > footer > menu {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-3);
  padding-inline-start: 0;
}

dialog > form > footer > menu:only-child {
  margin-inline-start: auto;
}

바닥글 메뉴 요소에 Flexbox 정보를 오버레이하는 Chrome Devtools 스크린샷

애니메이션

대화상자 요소는 창에 들어가고 나오기 때문에 애니메이션으로 표시되는 경우가 많습니다. 대화창에 이 입구와 출구를 지지하는 동작을 보여주면 사용자가 흐름에 적응합니다

일반적으로 대화상자 요소는 안쪽으로만 애니메이션을 적용할 수 있습니다. 이는 브라우저가 요소의 display 속성을 전환합니다. 앞서 이 가이드에서는 디스플레이를 그리드로 설정하고 아무것도 안함으로 설정하지 않습니다. 이렇게 하면 애니메이션을 적용할 수 있습니다.

Open Props는 여러 키프레임과 함께 제공됩니다. 애니메이션을 사용하면 쉽게 읽을 수 있습니다. 다음은 애니메이션 목표와 레이어로 접근 방식:

  1. 감소된 모션은 기본 전환이며 간단한 불투명도 페이드 인 및 아웃입니다.
  2. 모션이 정상인 경우 슬라이드 및 배율 애니메이션이 추가됩니다.
  3. 메가 대화상자의 반응형 모바일 레이아웃이 슬라이드아웃되도록 조정됩니다.

안전하고 의미 있는 기본 전환

Open Props에는 페이드 인/아웃을 위한 키프레임이 제공되지만 레이어 애니메이션을 기반으로 하여 키프레임 애니메이션을 얻을 수 있습니다. 앞에서 이미 대화상자의 공개 상태 스타일을 불투명도, [open] 속성에 따라 1 또는 0를 조정합니다. 받는사람 0% 에서 100% 사이로 전환되는 경우, 브라우저에 이징(ease)을 실행할 수 있습니다.

dialog {
  transition: opacity .5s var(--ease-3);
}

전환에 모션 추가

사용자가 움직임에 동의하면 메가 및 미니 대화상자가 모두 슬라이드되어야 합니다. 위로 들어가고, 출구로 확장될 때가 있습니다. 이 작업은 prefers-reduced-motion 미디어 쿼리 및 몇 가지 Open Prop:

@media (prefers-reduced-motion: no-preference) {
  dialog {
    animation: var(--animation-scale-down) forwards;
    animation-timing-function: var(--ease-squish-3);
  }

  dialog[open] {
    animation: var(--animation-slide-in-up) forwards;
  }
}

모바일용으로 나가기 애니메이션 조정

스타일 지정 섹션의 앞부분에서 메가 대화상자 스타일이 모바일에 맞게 조정되었습니다. 마치 작은 종이가 미끄러지는 것처럼 작업지와 같아 보입니다. 여전히 맨 아래에 붙어 있습니다. 규모 이탈 애니메이션은 이 새로운 디자인에 잘 맞지 않으므로 몇 가지 미디어 쿼리와 몇 가지 개방형 Prop가 있습니다.

@media (prefers-reduced-motion: no-preference) and @media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    animation: var(--animation-slide-out-down) forwards;
    animation-timing-function: var(--ease-squish-2);
  }
}

자바스크립트

JavaScript를 사용하여 추가할 수 있는 항목은 다음과 같습니다.

// dialog.js
export default async function (dialog) {
  // add light dismiss
  // add closing and closed events
  // add opening and opened events
  // add removed event
  // removing loading attribute
}

이러한 추가사항은 알림 닫기 (대화상자 클릭)를 배경화면), 애니메이션, 기타 이벤트를 추가하여 콘텐츠를 확인할 수 있습니다.

조명 닫기 추가 중

이 작업은 간단하며 사용할 수 없는 대화상자 요소에 추가되는 있습니다. 상호작용은 대화상자의 클릭을 보면서 이루어집니다. 사용하여 이벤트를 버블링 어떤 클릭이 클릭되었는지 평가하고 close() 가 표시됩니다.

export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
}

const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

dialog.close('dismiss')를 확인합니다. 이벤트가 호출되고 문자열이 제공됩니다. 이 문자열은 다른 JavaScript에서 검색하여 대화상자가 닫혔습니다. 제가 전화를 걸 때마다 닫는 문자열도 제공됩니다. 내 애플리케이션에 컨텍스트를 제공하기 위해 사용자 상호작용입니다.

종결 및 비공개 이벤트 추가

대화상자 요소는 닫기 이벤트와 함께 제공되며, 대화상자 close() 함수가 호출됩니다. 이 요소에 애니메이션을 적용하고 있으므로 애니메이션 전후를 위한 이벤트가 있어서 대화상자 양식을 재설정해야 합니다. 여기에서는 inert 속성을 설정하고, 데모에서는 이 속성을 사용하여 사용자가 새 이미지를 제출한 경우 아바타 목록입니다.

이렇게 하려면 closingclosed라는 새 이벤트 두 개를 만듭니다. 그런 다음 대화상자에 내장된 닫기 이벤트를 수신 대기합니다. 여기에서 대화상자를 inert를 호출하고 closing 이벤트를 전달합니다. 다음 작업은 대화상자의 실행을 끝내고 closed 이벤트를 사용합니다.

const dialogClosingEvent = new Event('closing')
const dialogClosedEvent  = new Event('closed')

export default async function (dialog) {
  …
  dialog.addEventListener('close', dialogClose)
}

const dialogClose = async ({target:dialog}) => {
  dialog.setAttribute('inert', '')
  dialog.dispatchEvent(dialogClosingEvent)

  await animationsComplete(dialog)

  dialog.dispatchEvent(dialogClosedEvent)
}

const animationsComplete = element =>
  Promise.allSettled(
    element.getAnimations().map(animation => 
      animation.finished))

토스트 메시지 빌드에도 사용되는 animationsComplete 함수 구성요소가 포함된 함수는 애니메이션 및 전환 프로미스의 완료를 나타냅니다. 이러한 이유로 dialogClose 비동기식 함수; 그러면 await 프라미스가 반환되고 닫힌 이벤트로 자신 있게 앞으로 진행할 수 있습니다.

개시 및 열린 이벤트 추가

이러한 이벤트는 기본 제공되는 대화상자 요소가 추가되어야 하므로 닫을 때와 마찬가지로 열린 이벤트를 제공합니다. I use a a MutationObserver 대화상자의 속성 변화에 대한 유용한 정보를 제공합니다. 이 관찰자에서는 open 속성의 변경사항을 살펴보고 맞춤 이벤트를 관리하겠습니다. 변경할 수 있습니다

마감 및 비공개 이벤트를 시작한 방법과 비슷하게 새 이벤트 두 개를 만듭니다. openingopened입니다. 이전에 대화상자를 리슨한 위치 닫기 이벤트 발생 시, 이번에는 생성된 변형 관찰자를 사용하여 대화상자의 속성

…
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent  = new Event('opened')

export default async function (dialog) {
  …
  dialogAttrObserver.observe(dialog, { 
    attributes: true,
  })
}

const dialogAttrObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(async mutation => {
    if (mutation.attributeName === 'open') {
      const dialog = mutation.target

      const isOpen = dialog.hasAttribute('open')
      if (!isOpen) return

      dialog.removeAttribute('inert')

      // set focus
      const focusTarget = dialog.querySelector('[autofocus]')
      focusTarget
        ? focusTarget.focus()
        : dialog.querySelector('button').focus()

      dialog.dispatchEvent(dialogOpeningEvent)
      await animationsComplete(dialog)
      dialog.dispatchEvent(dialogOpenedEvent)
    }
  })
})

대화상자가 표시되면 변형 관찰자 콜백 함수가 호출됩니다. 속성이 변경되어 변경사항 목록을 배열로 제공합니다. 반복 속성이 변경되어 attributeName가 열려 있는지 찾습니다. 그런 다음 요소에 속성이 있는지 여부: 이 속성은 대화상자에 알 수 있습니다. 열린 경우 inert 속성을 삭제하고 포커스를 설정합니다. 요청을 보내는 요소 또는 autofocus 또는 대화상자에서 찾은 첫 번째 button 요소 마지막으로, 닫는 시작 이벤트를 즉시 전달하고, 애니메이션이 끝날 때까지 열린 이벤트를 전달합니다.

삭제된 일정 추가

단일 페이지 애플리케이션에서는 종종 경로를 기반으로 대화상자가 추가되고 삭제됩니다. 또는 기타 애플리케이션 요구사항과 상태를 파악할 수 있습니다 이벤트를 정리하거나 저장된 데이터를 유지할 수 있습니다.

다른 변형 관찰자를 사용하여 이 작업을 수행할 수 있습니다. 이번에는 대화상자 요소의 속성을 관찰하여 본문의 하위 요소를 관찰합니다. 요소가 제거되는지 확인하고 대화상자 요소가 제거되는지 살펴봅니다.

…
const dialogRemovedEvent = new Event('removed')

export default async function (dialog) {
  …
  dialogDeleteObserver.observe(document.body, {
    attributes: false,
    subtree: false,
    childList: true,
  })
}

const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(mutation => {
    mutation.removedNodes.forEach(removedNode => {
      if (removedNode.nodeName === 'DIALOG') {
        removedNode.removeEventListener('click', lightDismiss)
        removedNode.removeEventListener('close', dialogClose)
        removedNode.dispatchEvent(dialogRemovedEvent)
      }
    })
  })
})

하위 요소가 추가되거나 삭제될 때마다 변형 관찰자 콜백이 호출됩니다. 추출해야 합니다. 감시 중인 특정 변이는 removedNodes nodeName/ 있습니다. 대화상자가 삭제된 경우 클릭 및 닫기 이벤트가 제거되어 메모리가 확보되고, 삭제된 맞춤 이벤트가 전달됩니다.

로드 속성 삭제

에 추가되었을 때 대화상자 애니메이션이 종료 애니메이션을 재생하지 않도록 하기 위해 로드 속성이 대화상자에 추가되어 있을 때 발생합니다. 이 다음 스크립트는 대화상자 애니메이션 실행이 완료될 때까지 대기한 다음 속성 이제 대화상자에 애니메이션을 자유롭게 적용할 수 있습니다. 주의를 분산시키는 애니메이션을 효과적으로 숨겼습니다.

export default async function (dialog) {
  …
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

페이지 로드 시 키프레임 애니메이션 방지 문제 자세히 알아보기 여기를 참조하세요.

통합

다음은 dialog.js 전체 내용입니다. 이제 각 섹션에 대해 다음 안내를 따르세요.

// custom events to be added to <dialog>
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent  = new Event('closed')
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent  = new Event('opened')
const dialogRemovedEvent = new Event('removed')

// track opening
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(async mutation => {
    if (mutation.attributeName === 'open') {
      const dialog = mutation.target

      const isOpen = dialog.hasAttribute('open')
      if (!isOpen) return

      dialog.removeAttribute('inert')

      // set focus
      const focusTarget = dialog.querySelector('[autofocus]')
      focusTarget
        ? focusTarget.focus()
        : dialog.querySelector('button').focus()

      dialog.dispatchEvent(dialogOpeningEvent)
      await animationsComplete(dialog)
      dialog.dispatchEvent(dialogOpenedEvent)
    }
  })
})

// track deletion
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(mutation => {
    mutation.removedNodes.forEach(removedNode => {
      if (removedNode.nodeName === 'DIALOG') {
        removedNode.removeEventListener('click', lightDismiss)
        removedNode.removeEventListener('close', dialogClose)
        removedNode.dispatchEvent(dialogRemovedEvent)
      }
    })
  })
})

// wait for all dialog animations to complete their promises
const animationsComplete = element =>
  Promise.allSettled(
    element.getAnimations().map(animation => 
      animation.finished))

// click outside the dialog handler
const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

const dialogClose = async ({target:dialog}) => {
  dialog.setAttribute('inert', '')
  dialog.dispatchEvent(dialogClosingEvent)

  await animationsComplete(dialog)

  dialog.dispatchEvent(dialogClosedEvent)
}

// page load dialogs setup
export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
  dialog.addEventListener('close', dialogClose)

  dialogAttrObserver.observe(dialog, { 
    attributes: true,
  })

  dialogDeleteObserver.observe(document.body, {
    attributes: false,
    subtree: false,
    childList: true,
  })

  // remove loading attribute
  // prevent page load @keyframes playing
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

dialog.js 모듈 사용

모듈에서 내보낸 함수가 호출되고 대화상자가 전달될 것으로 예상됨 요소에 다음과 같이 새 이벤트 및 기능을 추가합니다.

import GuiDialog from './dialog.js'

const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')

GuiDialog(MegaDialog)
GuiDialog(MiniDialog)

이와 마찬가지로 두 개의 대화상자는 가볍게 닫는 애니메이션으로 업그레이드됩니다. 로드 수정, 작업할 더 많은 이벤트가 포함됩니다.

새 맞춤 이벤트 수신 대기

이제 업그레이드된 각 대화상자 요소는 다음과 같은 5개의 새 이벤트를 수신할 수 있습니다.

MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)

MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)

MegaDialog.addEventListener('removed', dialogRemoved)

다음은 이러한 이벤트를 처리하는 두 가지 예입니다.

const dialogOpening = ({target:dialog}) => {
  console.log('Dialog opening', dialog)
}

const dialogClosed = ({target:dialog}) => {
  console.log('Dialog closed', dialog)
  console.info('Dialog user action:', dialog.returnValue)

  if (dialog.returnValue === 'confirm') {
    // do stuff with the form values
    const dialogFormData = new FormData(dialog.querySelector('form'))
    console.info('Dialog form data', Object.fromEntries(dialogFormData.entries()))

    // then reset the form
    dialog.querySelector('form')?.reset()
  }
}

대화상자 요소로 빌드한 데모에서는 닫힌 이벤트를 사용하고 양식 데이터를 클릭하여 목록에 새 아바타 요소를 추가합니다. 적절한 시기에 대화상자가 종료 애니메이션을 완료하면 일부 스크립트가 애니메이션으로 을 클릭합니다. 새로운 이벤트 덕분에 사용자 환경 조정은 원활할 수 있습니다

dialog.returnValue: 여기에는 대화상자 close() 이벤트가 호출됩니다. dialogClosed 이벤트에서 대화상자가 닫혔는지, 취소되었는지, 확인되었는지 알 수 없습니다. 확인되면 그런 다음 스크립트가 양식 값을 가져와서 양식을 재설정합니다. 초기화가 유용하므로 대화상자가 다시 표시되면 비어 있고 새로 제출할 준비가 된 것입니다.

결론

이제 제가 어떻게 했는지 알게 되셨으니 어떻게 하면 좋을까요?‽ 보상

접근방식을 다각화하고 웹에서 빌드하는 방법을 모두 알아보겠습니다.

데모를 만들고 링크를 트윗하여 추가하면 됩니다. 아래 커뮤니티 리믹스 섹션을 공유해 주세요

커뮤니티 리믹스

리소스