輪播透過類似幻燈片的方式接連展示一部份的物件。
通常輪播一次只會呈現一張幻燈片,
用戶可以控制輪播的當前幻燈片像是往前一張或是往後一張。
在部分實作,切換幻燈片會在頁面載入後自動開始,
他也可能會在幻燈片都播放一遍後自動停止。
幻燈片可以包含任何形式的資訊,像是文字或圖片。
確保所有的用戶可以簡單的控制,且不會因為幻燈片輪播而產生負面影響。
以下是輪播會用到的一些詞彙。
Slide 幻燈片
單個內容用於輪播。
Rotation Control 輪播控制
可交互元素用於暫停或是開啟自動輪播。
Next Slide Control 下張幻燈片控制
可交互元素,用於更換下張幻燈片。
Previous Slide Control 上張幻燈片控制
可交互元素,用於更換上張幻燈片。
Slide Picker Controls 幻燈片選擇
通常會用小點作為樣式,用戶可以選擇要看哪張幻燈片。
輪播需要帶有 role="region"
,且必須要有 aria-label
或是 aria-labelledby
。
it("defines the carousel and its controls as a landmark region", () => {
render(<Carousel aria-label="test" />);
expect(screen.queryByRole("region")).toBeInTheDocument();
});
it("provides a label that describes the content in the carousel region", () => {
render(<Carousel aria-label="test" />);
expect(screen.queryByRole("region")).toHaveAccessibleName();
});
it('informs assistive technologies to identify the element as a "carousel" rather than a "region"', () => {
render(<Carousel aria-label="test" />);
expect(screen.queryByRole("region")).toHaveAttribute(
"aria-roledescription",
"carousel"
);
});
section
元素如果有可達性名稱會有隱含的 region
landmark。
透過 aria-roledescription
客製化 landmark,
讓輔助科技識別這個元件時識別為 carousel
。
type CarouselProps = PCP<"section", {}>;
export function Carousel(props: CarouselProps) {
return <section aria-roledescription="carousel" {...props} />;
}
function setup() {
render(
<Carousel aria-label="test">
<Carousel.Item>Dynamic Europe: Amsterdam, Prague, Berlin</Carousel.Item>
<Carousel.Item>Travel to Southwest England and Paris</Carousel.Item>
<Carousel.Item>Great Children's Programming on Public TV</Carousel.Item>
<Carousel.Item>Foyle’s War Revisited</Carousel.Item>
<Carousel.Item>Great Britain Vote: 7 pm Sat.</Carousel.Item>
<Carousel.Item>Mid-American Gardener: Thursdays at 7 pm</Carousel.Item>
</Carousel>
);
}
it("enables assistive technology users to perceive the boundaries of a slide.", () => {
setup();
expect(
screen.getByText("Dynamic Europe: Amsterdam, Prague, Berlin")
).toHaveAttribute("role", "group");
expect(
screen.getByText("Travel to Southwest England and Paris")
).toHaveAttribute("role", "group");
expect(
screen.getByText("Great Children's Programming on Public TV")
).toHaveAttribute("role", "group");
expect(screen.getByText("Foyle’s War Revisited")).toHaveAttribute(
"role",
"group"
);
expect(screen.getByText("Great Britain Vote: 7 pm Sat.")).toHaveAttribute(
"role",
"group"
);
expect(
screen.getByText("Mid-American Gardener: Thursdays at 7 pm")
).toHaveAttribute("role", "group");
});
it('informs assistive technologies to identify the element as a "slide" rather than a "group"', () => {
setup();
expect(
screen.getByText("Dynamic Europe: Amsterdam, Prague, Berlin")
).toHaveAttribute("aria-roledescription", "slide");
expect(
screen.getByText("Travel to Southwest England and Paris")
).toHaveAttribute("aria-roledescription", "slide");
expect(
screen.getByText("Great Children's Programming on Public TV")
).toHaveAttribute("aria-roledescription", "slide");
expect(screen.getByText("Foyle’s War Revisited")).toHaveAttribute(
"aria-roledescription",
"slide"
);
expect(screen.getByText("Great Britain Vote: 7 pm Sat.")).toHaveAttribute(
"aria-roledescription",
"slide"
);
expect(
screen.getByText("Mid-American Gardener: Thursdays at 7 pm")
).toHaveAttribute("aria-roledescription", "slide");
});
這邊用到 compound component,
想了解詳細可以看 如何製作月曆 compound components【 calendar | 我不會寫 React Component 】。
type ItemProps = PCP<"div", {}>;
function Item(props: ItemProps) {
return <div role="group" aria-roledescription="slide" {...props} />;
}
// ...
Carousel.Item = Item;
it(
"provides each slide with a distinct label " +
"that helps the user understand which of the 6 slides is displayed.",
() => {
setup();
expect(
screen.getByText("Dynamic Europe: Amsterdam, Prague, Berlin")
).toHaveAccessibleName("1 of 6");
expect(
screen.getByText("Travel to Southwest England and Paris")
).toHaveAccessibleName("2 of 6");
expect(
screen.getByText("Great Children's Programming on Public TV")
).toHaveAccessibleName("3 of 6");
expect(screen.getByText("Foyle’s War Revisited")).toHaveAccessibleName(
"4 of 6"
);
expect(
screen.getByText("Great Britain Vote: 7 pm Sat.")
).toHaveAccessibleName("5 of 6");
expect(
screen.getByText("Mid-American Gardener: Thursdays at 7 pm")
).toHaveAccessibleName("6 of 6");
}
);
先過濾出 Item
在動態產生 label
。
type CarouselProps = PCP<"section", {}>;
export function Carousel(props: CarouselProps) {
const items: ReturnType<typeof Item>[] = [];
Children.forEach(props.children, (element) => {
if (isValidElement(element) && element.type === Item) {
items.push(element);
}
});
const defaultItemLabel = (index: number) => `${index + 1} of ${items.length}`;
return (
<section aria-roledescription="carousel" {...props}>
{items.map((item, index) =>
cloneElement(item, {
key: defaultItemLabel(index),
"aria-label": defaultItemLabel(index),
...item.props,
})
)}
</section>
);
}
function setup() {
render(
<Carousel aria-label="test">
<Carousel.Items>
<Carousel.Item>Dynamic Europe: Amsterdam, Prague, Berlin</Carousel.Item>
<Carousel.Item>Travel to Southwest England and Paris</Carousel.Item>
<Carousel.Item>Great Children's Programming on Public TV</Carousel.Item>
<Carousel.Item>Foyle’s War Revisited</Carousel.Item>
<Carousel.Item>Great Britain Vote: 7 pm Sat.</Carousel.Item>
<Carousel.Item>Mid-American Gardener: Thursdays at 7 pm</Carousel.Item>
</Carousel.Items>
</Carousel>
);
}
describe("aria-live", () => {
it("applied to a div element that contains all the slides", () => {
setup();
expect(
screen.getByText("Dynamic Europe: Amsterdam, Prague, Berlin")
.parentElement
).toHaveAttribute("aria-live");
});
});
將原本在 Carousel
的邏輯抽出,搬到 Items
元件下。
type ItemsProps = PCP<"div", {}>;
function Items(props: ItemsProps) {
const items: ReturnType<typeof Item>[] = [];
Children.forEach(props.children, (element) => {
if (isValidElement(element) && element.type === Item) {
items.push(element);
}
});
const defaultItemLabel = (index: number) => `${index + 1} of ${items.length}`;
return (
<div aria-live="off">
{items.map((item, index) =>
cloneElement(item, {
key: defaultItemLabel(index),
"aria-label": defaultItemLabel(index),
...item.props,
})
)}
</div>
);
}
type CarouselProps = PCP<"section", {}>;
export function Carousel(props: CarouselProps) {
return <section aria-roledescription="carousel" {...props} />;
}
Carousel.Items = Items;