HextaUI

Aspect ratio

メディアの読み込み前も形を保ち、読み込み中はシマー表示になり、メディアをフェードインさせ、失敗時はフォールバックを表示するボックスです。

Sunset over mountains
import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioDemo() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} className="rounded-xl">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
      </AspectRatio>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/aspect-ratio.json

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

import { AspectRatio } from "@/components/ui/aspect-ratio"
<AspectRatio ratio={16 / 9} className="rounded-lg">
  <img src="/photo.jpg" alt="Sunset over mountains" />
</AspectRatio>

比率

ratio には、数値、"w/h" 形式の文字列、または "w:h" 形式の文字列を指定できます。

import {
  AspectRatio,
  type AspectRatioValue,
} from "@/components/ui/aspect-ratio"

const ratios: { label: string; ratio: AspectRatioValue }[] = [
  { label: "16 / 9", ratio: 16 / 9 },
  { label: "1", ratio: 1 },
  { label: '"4/3"', ratio: "4/3" },
  { label: '"21:9"', ratio: "21:9" },
]

export function AspectRatioRatios() {
  return (
    <div className="grid w-full max-w-md grid-cols-2 gap-3">
      {ratios.map(({ label, ratio }) => (
        <div key={label} className="flex flex-col gap-1.5">
          <AspectRatio ratio={ratio} className="rounded-lg">
            <img src="/preview/landscape.svg" alt="Sunset over mountains" />
          </AspectRatio>
          <span className="font-mono text-xs text-muted-foreground">
            {label}
          </span>
        </div>
      ))}
    </div>
  )
}

読み込みが遅い場合

画像が届くまでボックスは形を保ったままシマーが表示され、届くと画像がフェードインするため、下のコンテンツは動きません。Reload を押すともう一度確認できます。

"use client"

import * as React from "react"

import { AspectRatio } from "@/components/ui/aspect-ratio"
import { Button } from "@/components/ui/button"

export function AspectRatioSlowLoad() {
  const [src, setSrc] = React.useState<string | undefined>(undefined)
  const [attempt, setAttempt] = React.useState(0)

  React.useEffect(() => {
    const timer = setTimeout(
      () => setSrc(`/preview/landscape.svg?v=${attempt}`),
      1500
    )
    return () => clearTimeout(timer)
  }, [attempt])

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <AspectRatio ratio={16 / 9} className="rounded-xl">
        <img src={src} alt="Sunset over mountains" />
      </AspectRatio>
      <Button
        variant="outline"
        size="sm"
        className="self-start"
        onClick={() => {
          setSrc(undefined)
          setAttempt(attempt + 1)
        }}
      >
        Reload
      </Button>
    </div>
  )
}

壊れた画像

画像の読み込みに失敗すると、ブラウザの壊れた画像のグリフが隠れ、代わりにフォールバックのアイコンが表示されます。置き換えるには fallback を渡し、何も表示しない場合は fallback={null} を指定します。

import { IconMoodSad } from "@tabler/icons-react"

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioBrokenImage() {
  return (
    <div className="grid w-full max-w-md grid-cols-2 gap-3">
      <AspectRatio ratio={4 / 3} className="rounded-lg">
        <img src="/preview/does-not-exist.jpg" alt="Team photo" />
      </AspectRatio>
      <AspectRatio
        ratio={4 / 3}
        className="rounded-lg"
        fallback={
          <span className="flex flex-col items-center gap-1 text-xs">
            <IconMoodSad />
            Couldn’t load
          </span>
        }
      >
        <img src="/preview/does-not-exist.jpg" alt="Team photo" />
      </AspectRatio>
    </div>
  )
}

オーバーレイ

絶対配置された子要素は、メディアの上に重なります。ボックスは何もクリップしないため、オーバーレイ上のリンクのフォーカスリングも表示されたままです。

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioOverlay() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} className="rounded-xl">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
        <div className="absolute inset-x-3 bottom-3 flex items-center justify-between gap-3 rounded-lg bg-background/80 px-3 py-2 text-sm backdrop-blur-sm">
          <span className="truncate font-medium">Dolomites at dusk</span>
          <a
            href="#"
            className="shrink-0 rounded-sm underline underline-offset-4 outline-none focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden"
          >
            View
          </a>
        </div>
      </AspectRatio>
    </div>
  )
}

プレースホルダーなし

placeholder={false} は、読み込み中のシマー、フェードイン、フォールバックを無効にし、shadcn の標準的な動作にします。

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioWithoutPlaceholder() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} placeholder={false} className="rounded-lg">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
      </AspectRatio>
    </div>
  )
}

レスポンシブ

aspect クラスでブレークポイントごとに比率を上書きできます。この例は、小さい画面では正方形で、md 以上では md:aspect-video になります。

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioResponsive() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={1} className="rounded-lg md:aspect-video">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
      </AspectRatio>
    </div>
  )
}

中央揃えの flex カラム内

ボックスはデフォルトで全幅のため、親が子要素を中央揃えにしていても、幅が 0 に潰れず列いっぱいに広がります。

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioFlexColumn() {
  return (
    <div className="flex w-full max-w-md flex-col items-center rounded-lg border border-dashed p-3">
      <AspectRatio ratio={16 / 9} className="rounded-lg">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
      </AspectRatio>
    </div>
  )
}

テキストコンテンツ

メディア以外の子要素にはボックスだけが適用され、それ以外は何も適用されません。位置は自分で指定してください。

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioTextContent() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={3} className="rounded-lg border">
        <div className="absolute inset-0 grid place-items-center p-4 text-center text-sm text-muted-foreground">
          Any content can sit in the box.
        </div>
      </AspectRatio>
    </div>
  )
}

figure として使う

キャプションは、比率が変わらないようボックスの外に置きます。

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioFigure() {
  return (
    <figure className="flex w-full max-w-md flex-col gap-2">
      <AspectRatio ratio={16 / 9} className="rounded-lg">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
      </AspectRatio>
      <figcaption className="text-xs text-muted-foreground">
        Dolomites at dusk, photographed from Seceda.
      </figcaption>
    </figure>
  )
}

無効な比率

0、負の数、解析できない文字列は正方形にフォールバックし、開発中は警告をログに出力します。

import {
  AspectRatio,
  type AspectRatioValue,
} from "@/components/ui/aspect-ratio"

export function AspectRatioInvalidRatio() {
  return (
    <div className="grid w-full max-w-md grid-cols-2 gap-3">
      <AspectRatio ratio={0} className="rounded-lg border" />
      <AspectRatio
        ratio={"abc" as AspectRatioValue}
        className="rounded-lg border"
      />
    </div>
  )
}

右から左

start-3 のような論理プロパティで配置したオーバーレイは、読む方向に従います。

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioRtl() {
  return (
    <div dir="rtl" className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} className="rounded-lg">
        <img src="/preview/landscape.svg" alt="غروب الشمس فوق الجبال" />
        <span className="absolute start-3 top-3 rounded-md bg-background/80 px-2 py-1 text-xs">
          جديد
        </span>
      </AspectRatio>
    </div>
  )
}
  • メディアの読み込み中、ボックスは aria-busy になります。
  • フォールバックは装飾であり、支援技術からは隠されます。画像の読み込みに失敗しても alt テキストは利用できるため、必ず記述してください。
  • モーションの低減が有効な場合、メディアはフェードなしで表示されます。

レンダリングする要素の属性をすべて受け付けます。直下に置いたメディア(<img>、<picture>、<video>)は、object-cover でボックスを埋め、角丸を引き継ぎます。

プロパティ型デフォルト
ratio
number | `${number}/${number}` | `${number}:${number}`1
placeholderメディアの読み込み中はシマーを、失敗時はフォールバックを表示します。
booleantrue
fallbackメディアの読み込みに失敗したときに表示されます。null を指定すると何も表示しません。
ReactNode<IconPhotoOff />
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="aspect-ratio"CSS でボックスを指定します。
data-stateloading、loaded、error のいずれか。placeholder が有効で、ボックスにメディアが含まれている場合にのみ設定されます。
aria-busyメディアの読み込み中、付与されます。
--ratio数値として解析された比率。
data-slot="aspect-ratio-placeholder"読み込み中またはエラー後に表示されるシマー。
data-slot="aspect-ratio-fallback"フォールバックを囲むラッパー。

parseAspectRatio(ratio) は、受け付ける任意の比率を数値に変換し、失敗した場合は 1 にフォールバックします。他の要素にも同じ方法でサイズを与えるのに使えます。AspectRatioValue と AspectRatioProps の型もエクスポートされています。