대화형 마우스 오버 카드 빌드

호버 카드는 사용자가 링크에 관심을 보일 때 (마우스 오버 또는 포커스) 콘텐츠의 대화형 미리보기를 제공합니다. 이 정보는 사용자가 탐색하기 전에 표시되며, 사용자에게 필요한 콘텐츠를 제공하거나 링크에 사용자가 찾는 콘텐츠가 포함되어 있는지 확인하는 데 도움이 됩니다.

구조화된 링크 미리보기 대신 일반 텍스트로 된 비대화형 도움말이 필요한 경우 마우스 오버 시 도움말 표시를 참고하세요.

1. 호버 카드 트리거

관심 호출자 API를 사용하여 호버 카드를 트리거합니다. 이렇게 하면 마우스 오버와 포커스 모두에서 카드가 표시되고 접근성 연결이 자동으로 처리됩니다.

  • 카드 컨테이너에 popover="hint" 속성을 추가합니다. hint를 사용하여 열려 있을 수 있는 관련 없는 팝오버 메뉴를 닫지 않도록 합니다.
  • 트리거 링크에 interestfor 속성을 추가하여 카드의 id를 가리킵니다.
<a href="/article" interestfor="article-preview">Article title</a>

<!-- The `id` must match the `interestfor` value on the trigger. -->
<div id="article-preview" popover="hint" class="hovercard">
  <h2>The buzz on hummingbirds</h2>
  <p class="byline">By Delphi Aguilar</p>
  <div>
    <img src="bird.jpg" alt="A vibrant hummingbird">
    <p>Brief summary of the article...</p>
  </div>
</div>

호버카드 내 콘텐츠에 대한 추가 링크는 트리거 링크와 중복되므로 포함할 필요가 없습니다.

interestfor에 기본 제공되는 접근성 기능

interestfor를 사용하면 브라우저가 보조 기술 연결을 처리합니다.

  • 암시적 시맨틱스: popover="hint"가 있는 타겟은 암시적 최소 역할 tooltip를 얻습니다. role="tooltip"를 직접 설정하지 마세요.
  • 암시적 연결: 브라우저는 타겟이 일반 텍스트인 경우 aria-describedby를 통해, 타겟에 상호작용 콘텐츠가 포함된 경우 aria-details를 통해 소스 요소를 타겟과 암시적으로 연결합니다. 트리거에 aria-describedby 또는 aria-details를 추가하지 마세요.
  • 상호작용 콘텐츠: 필요에 따라 연결이 aria-details로 전환되므로 타겟에 상호작용 콘텐츠 (예: '관심분야 카드' 내 링크)를 포함할 수 있습니다.

2. 앵커 위치 지정을 사용한 위치 지정

CSS 앵커 포지셔닝을 사용하여 트리거를 기준으로 호버 카드를 배치합니다. interestfor를 사용하면 트리거가 팝오버의 암시적 앵커가 됩니다.

[popover].hovercard {
  /* Position the card below the trigger, aligned with its inline-start edge
     and spanning toward the inline-end, flipping if it overflows the viewport. */
  position-area: block-end span-inline-end;
  position-try-fallbacks: flip-block, flip-inline;
}

3. 들어가기 및 나가기 애니메이션

기본적으로 키보드 및 마우스 사용자의 경우 0.5초 지연 후 관심이 표시되고 사라집니다. 이렇게 하면 페이지를 탭하거나 마우스를 움직이는 동안 타겟팅된 팝오버가 열리지 않고, 트리거에서 팝오버로 마우스를 이동하는 동안 팝오버가 닫히지 않습니다. 타이밍을 조정하려면 interest-delay CSS 속성을 사용합니다.

[interestfor]{
  /* Shorthand for interest-delay-start and interest-delay-end */
  interest-delay: 0.2s 0.1s;
}

사용자가 여러 트리거를 빠르게 연속해서 이동할 가능성이 있는 경우 관심 기반 트리거 팝오버가 이미 열려 있으면 초기 지연 시간을 0s로 설정합니다.

:root:has(:interest-source) [interestfor] {
  interest-delay-start: 0s;
}

@starting-style를 사용하여 들어가기 애니메이션을 정의하고 transition-behavior: allow-discrete(display 및 overlay 포함)를 사용하여 최상위 레이어에서 나가기 애니메이션을 적용하여 @media (prefers-reduced-motion: no-preference) 뒤의 전환을 제어합니다.

[popover].hovercard {
  opacity: 0;
  transform-origin: top;
}

[popover].hovercard:popover-open {
  opacity: 1;
  transform: translateY(0) scale(1);
}

@starting-style {
  [popover].hovercard:popover-open {
    opacity: 0;
    transform: translateY(-10px) scale(0.95);
  }
}

/* MANDATORY: Respect user preference for reduced motion by selectively applying transitions. */
@media (prefers-reduced-motion: no-preference) {
  [popover].hovercard {
    transition:
      opacity,
      transform,
      display,
      overlay;
    transition-duration: 0.4s;
    transition-behavior: allow-discrete;
  }
}

4. 선택사항: 다음 페이지로 원활하게 전환

원하는 경우 교차 문서 뷰 전환을 사용하여 호버 카드와 도착 페이지 간에 원활한 '모핑' 애니메이션을 만들 수 있습니다. 구현에 관한 안내는 일관된 교차 문서 전환을 참고하세요.

호버 카드의 요소와 다음 페이지의 해당 요소에 일치하는 view-transition-name 값을 할당하면 브라우저가 탐색 전반에 걸쳐 애니메이션을 적용합니다. 여러 요소 (예: 제목, 이미지, 바이라인)의 이름을 지정하여 복잡한 다중 요소 모핑 효과를 만들 수 있습니다. :popover-open를 사용하여 열린 팝오버의 요소에 view-transition-name를 할당하여 중복 view-transition-name를 만들거나 링크별 view-transition-name를 요구하지 않도록 합니다.

/* Opt-in to cross-document transitions on both pages */
@view-transition {
  navigation: auto;
}

/* Don't show transitions when loading this page */
::view-transition-group(*) {
  animation-duration: 0s;
}

/* Assign names ONLY when the popover is open to ensure only the correct elements are selected. */
[popover]:popover-open {
  view-transition-name: --hovercard;
  view-transition-class: morph;

  h2 {
    view-transition-name: --title;
    view-transition-class: morph;
  }

  img {
    view-transition-name: --image;
    view-transition-class: morph;
  }
}

필수: 전환이 안정적이고 깜박이지 않도록 하려면 도착 페이지에서 blocking="render" 및 <link rel="expect">를 사용하여 첫 번째 프레임을 렌더링하기 전에 중요한 콘텐츠가 파싱될 때까지 기다려야 합니다.

<!-- On the destination page: -->
<head>
  <!-- Block rendering until the hero image and title are parsed,
       ensuring the view transition snapshot is taken at the right time. -->
  <link rel="expect" href="#main-content" blocking="render">
</head>
<body>
  <main id="main-content">
    <h1>The buzz on hummingbirds</h1>
    <img src="bird.jpg">
  </main>
</body>
/* On the destination page: */
main {
  view-transition-name: --hovercard;
  view-transition-class: morph;
}
h1 {
  view-transition-name: --title;
  view-transition-class: morph;
}
img {
  view-transition-name: --image;
  view-transition-class: morph;
}

/* By default no transition */
::view-transition-group(*) {
  animation-duration: 0s;
}
/* Short transition for morphing elements */
::view-transition-group(.morph) {
  animation-duration: 0.4s;
}
/* Disable transition if no `old` view */
::view-transition-new(.morph):only-child {
  animation-duration: 0s;
}

대체 전략

트리거는 표준 <a href="..."> 링크이므로 호버 카드를 점진적 개선으로 처리하는 경우 지원되지 않는 브라우저에서도 클릭 시 탐색할 수 있습니다. 교차 브라우저 호버 카드 지원이 필요한 경우 필요에 따라 조건부로 폴리필을 로드합니다.

Popover의 대체 및 브라우저 지원

Popover API는 대부분 점진적 개선이지만, 최상위 레이어 승격, 가벼운 닫기, popovertarget 호출과 같은 정의 동작에는 CSS 전용에 상응하는 기능이 없습니다. 이전 브라우저에는 폴리필이 필요하며, 폴리필을 제공하지 않으려면 수동 대체가 필요합니다.

폴리필: 이전 브라우저에서 popover 속성을 지원하려면 조건부로 @oddbird/popover-polyfill를 로드합니다. 필수: HTMLElement.prototype에서 popover 속성을 확인하여 기능을 감지하고 네이티브 지원이 누락된 경우에만 폴리필을 로드합니다. 무조건 로드하지 마세요.

번들러 또는 가져오기 맵 사용:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

번들러가 없는 경우 <script type="module"> 내의 CDN에서 가져옵니다.

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

스타일 지정 주의사항: 폴리필은 실제 :popover-open 의사 클래스를 정의할 수 없으므로 대신 .\:popover-open 클래스를 적용합니다. 필수: :is() 또는 :where()를 사용하여 두 가지를 결합합니다. 그렇지 않으면 :popover-open가 없는 브라우저에서 전체 규칙을 삭제합니다.

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

또는 폴리필이 없는 기존 대체 기능의 경우 position: fixed를 사용하고 getBoundingClientRect()를 통해 좌표를 수동으로 계산하거나, 사용 사례에 적합한 경우 inset: auto를 사용하여 기본 위치 지정에 의존합니다.

관심 인보커의 대체 및 브라우저 지원

관심 요소 호출자는 NPM의 interestfor 폴리필 패키지를 사용하여 조건부로 폴리필해야 합니다. CDN을 사용하는 것보다 폴리필을 번들로 묶는 것이 좋습니다.

필수: HTMLButtonElement.prototype에서 interestForElement 속성을 확인하여 기능을 감지하고 네이티브 지원이 누락된 경우에만 폴리필을 로드합니다.

<script type="module">
  if (!HTMLButtonElement.prototype.hasOwnProperty("interestForElement")) {
    // CDN link only used for example, prefer bundling.
    await import("https://unpkg.com/interestfor@latest");
  }
</script>

CSS 폴리필 주의사항

폴리필을 사용할 때는 네이티브 의사 클래스 및 속성 지원 부족을 고려하여 CSS API가 약간 변경됩니다.

  • 의사 클래스: 폴리필은 실제 :interest-source 및 :interest-target 의사 클래스를 정의할 수 없으므로 대신 .interest-source 및 .interest-target 클래스를 적용합니다. 필수: :is()와 결합하여 네이티브 상태와 폴리필 상태를 모두 지원합니다.
/* Styles to apply when the effect is being previewed */
:is(:interest-source, .interest-source) { /* ... */ }
:is(:interest-target, .interest-target) { /* ... */ }
  • CSS 속성: interest-delay, interest-delay-start, interest-delay-end의 경우 폴리필은 모든 브라우저에서 맞춤 속성을 직접 읽을 수 없으므로 CSS 변수를 사용해야 합니다.
/* Adjust the start and end delay for interest invokers */
[interestfor] {
  --interest-delay-start: 0.2s;
  interest-delay-start: var(--interest-delay-start);
  --interest-delay-end: 0.1s;
  interest-delay-end: var(--interest-delay-end);
}