网站英雄轮播:轮流展示的照片,以及确保它们始终如一的测试
页面顶部的一个照片展示区域,其中两张或多张照片每隔几秒轮流切换,每张照片对应一个圆点指示器。这看起来简单,实则不然:一张照片上的说明文字可能导致页面每次切换时跳动;白色圆点在浅色照片上会消失;手机需要比鼠标更大的点击目标;而移动内容本身也有自己的可访问性规则。本技能交付了组件,更重要的是,交付了能够捕捉所有这些问题的测试。
为什么这样构建?(请用自己的话对项目负责人说明)
- 页面从不跳动。 所有照片共享一个位置,裁剪成统一形状,因此第一张照片的高度与最后一张相同。照片上没有说明文字或图片来源:它们正是导致原始网站页面跳动的原因。图片来源应归属到网站的图片版权页面。
- 圆点在任何照片上都清晰可读。 它们位于一个小的深色药丸形背景上,因此在浅色天空和深色墙壁上都能显示。测试会通过测量浅色和深色照片上像素的实际对比度来验证这一点。
- 无 JavaScript 也能工作。 此时照片会从上到下依次堆叠,且不显示任何控制按钮。
- 保持礼貌。 当鼠标悬停、标签页隐藏、键盘焦点进入时,轮播会暂停;对于在设备上设置了“减少动效”的用户,轮播默认也不会启动。
- 不存储、不发送任何信息。 无 Cookie、无本地存储、无跨站请求。
以及坦诚的弱点,让项目负责人首先从您这里听到:
- 轮播照片并不总是更好的设计。 一张单一的强照片更安静、加载更快,且不会激怒任何人。请明确说明;由项目负责人决定。
- 每一张额外的照片都会增加访客在手机上的下载时间。 三张照片各四种尺寸是可以的;十张则不行。请控制在少数几张。
- “圆点暂停”模式用更简洁的照片展示换取了可见的控制按钮(请参见下方“暂停”部分)。
0. 这是正确的工具吗?(安装前请确认)
- 项目负责人是否确定要使用轮播? 一张照片不需要任何这些。请直接、坦诚地询问一次。
- 有多少张照片,并且它们的形状是否一致? 建议 2 到 5 张。任何形状都可行(每张照片会被裁剪成轮播的宽高比),但主体靠近边缘的照片在裁剪后可能会丢失部分内容:请用轮播的默认宽高比(16 / 9)和手机屏幕尺寸逐一查看每张照片。
- 照片是否链接到其他页面? 任何幻灯片都可以链接到一个页面(例如一个项目页面);混合链接和未链接的幻灯片也是可以的(CI 页面就是这样做的)。
- 选择哪种暂停模式? 请向项目负责人阅读下方的“暂停”部分,并让他们做出选择。默认为按钮模式。
1. 它为网站添加了什么
| 文件 | 来自本技能的 templates/ | 说明 |
|---|---|---|
src/components/HeroCarousel.astro | HeroCarousel.astro | 标记、圆点、样式 |
src/components/hero-carousel-text.ts | hero-carousel-text.ts | 轮播显示的文本,包含英文和德文 |
public/js/hero-carousel.js | hero-carousel.js | 脚本:将所有幻灯片放置在同一位置、旋转、处理控制按钮 |
tests/hero-carousel.spec.ts | hero-carousel.spec.ts | 从访客视角测试轮播(列表如下) |
请安装所有四个文件。模板中不包含任何图片文件:照片是网站自己的资源,通过 props 传入。
一句话工作原理。 服务端会发送每一张幻灯片,一张接一张堆叠,控制按钮默认隐藏。脚本作为网站自己的一个文件存放在 public/js/ 目录下(而非内联脚本,这样严格的 script-src 'self' CSP 策略就能允许它运行),它会将幻灯片放置在一个网格单元格中,显示控制按钮,并标记当前显示的幻灯片。当前显示哪张幻灯片通过一个数据属性控制;CSS 负责剩下的所有工作,包括一秒钟的淡入效果。只有在其图片加载完成后,该幻灯片才会被显示,因此加载慢或加载失败的图片永远不会留下空白区域。
测试规格检查什么(从访客视角,测试构建好的实际页面):标记结构和每个圆点的名称;冻结时钟后精确到毫秒的切换间隔;Pause(暂停)按钮或圆点点击停止轮播,包括按钮标签和活动区域;键盘焦点(停止轮播、保持停止状态、Resume(恢复)按钮);鼠标悬停和移开后重新设定新间隔;减少动效;隐藏的标签页;延迟加载和加载失败的第二张图片;无 JavaScript 时的页面效果;在 1280、768 和 390 像素宽度下无高度跳动;确保未显示的幻灯片无法通过键盘焦点或辅助功能树访问;确保照片链接正常工作,且点击圆点永远不会打开链接;32 像素的点击目标(触屏设备为 44 像素);通过渲染像素检测圆点相对于每张照片上的药丸形背景的对比度;强制颜色模式;明暗两种主题下的 axe 可访问性测试;无存储和无网络请求;脚本是外部文件而非内联;淡入效果在动画中途和结束后进行验证,且在减少动效模式下不执行。
2. 安装(在分支上进行,并创建为 Pull Request:请参见 AGENTS.md §2)
- 将第 1 节中提到的四个文件复制到对应位置(先执行
mkdir -p public/js)。 - 将轮播组件放到页面上,位于该页面的
<Base>组件内部,靠近顶部位置:--- import HeroCarousel from '../components/HeroCarousel.astro'; import first from '../assets/hero-1.jpg'; import second from '../assets/hero-2.jpg'; --- <HeroCarousel label="作品照片" slides={[ { image: first, alt: '第一张照片展示的内容', label: '短名称', href: '/projects/one' }, { image: second, alt: '第二张照片展示的内容', label: '另一个名称' }, ]} />label:为屏幕阅读器说明轮播是什么。必填。- 每张幻灯片:
image(导入的照片)、alt(描述图片内容的句子,至少 10 个字符:构建步骤会在少于 10 个字符时报错)、label(圆点的短名称)、href(可选)。 pause:"button"(默认)或"dots"。swap:"instant"(默认)或"fade"。intervalMs:默认 6000,不得低于 2000。ratio:默认"16 / 9"。lang:页面自身的语言。 照片:JPEG 或 PNG 格式,宽度约为 2,400 像素,数量控制在页面所需范围内(构建步骤会为每张照片生成四种尺寸,AVIF 格式并带有 WebP 回退)。第一张照片会以高优先级加载;其余照片以低优先级加载并保持其位置。
- 语言。 组件内置英文和德文,并且会使用页面自身的语言(
Base.astro会将其作为Astro.locals.lang向下传递)。如需其他语言:请先按照下方的“添加语言”步骤操作。 - 在
tests/hero-carousel.spec.ts中,将PAGE(包含轮播的页面)、PAUSE和SWAP设置为与页面传入的值一致(组件的默认值是button和instant;测试规格需要显式写出这些值)。如果某个值为空,测试会失败。仅当页面传递了lang参数时才需要设置LANG。 用于对比度测试的照片: 测试规格会使用轮播的真实照片,因此它反映的是项目负责人自己图片的实际情况。如果所有照片都是深色,那么“浅色照片上的白色圆点”测试就无法失败;为了观察测试失败的情况,可以手动添加一张浅色照片进行测试(第 3 节中的变更检查会这么做)。 - 运行
npm run check && npm test。
暂停(请在选择前向项目负责人说明)
自动启动、持续时间超过五秒且与其他内容相邻的移动内容,属于 WCAG 2.2.2,A 级标准,该标准要求“提供一种机制,让用户可以暂停、停止或隐藏它”(请阅读 https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html)。同一页面将“暂停”定义为“由用户请求停止,且直到用户请求恢复之前不再继续”,并且并未要求该机制必须是可见的。该页面也没有讨论幻灯片指示器。
pause="button"(默认)。 在圆点旁边显示一个 Pause(暂停)按钮(暂停后变为 Resume(恢复))。可见、有名称,是公认且无争议的阅读方式,也是访客最易发现的方式。它的代价是:照片上会多出一个小小的文字。pause="dots"。 没有按钮。点击一个圆点会显示对应照片并停止轮播,直到访客重新加载页面;键盘焦点进入时也会停止轮播;同时,“减少动效”模式下轮播会以停止状态启动。这很可能符合 2.2.2 标准的“机制”要求,但是没有任何方式告知访客点击圆点可以停止轮播,也没有任何方式可以恢复轮播。第一个使用此模式的网站是为了获得更干净的照片展示效果,并充分知晓这一取舍。
本技能不对其是否符合标准做出结论: “圆点暂停”模式是否被接受,由网站自己的审核者决定。 如果项目负责人选择了此模式,请在网站文档中写下一行说明,包含日期。
添加语言
当需要为网站添加新的语言,或者网站之后新增语言时。
- 首先阅读网站的语气指南(
CONTENT_GUIDE.md;德文中需要注意使用“du”或“Sie”等尊称)。需要翻译的文本只有寥寥几个词:在语言允许的情况下,尽量不具体指向任何人。 - 在
src/components/hero-carousel-text.ts中,将TEXT的en条目复制一份,将键名改为该语言的两位字母代码(例如fr,而不是fr-CA),并翻译每一个值;如果有任一值未翻译,npm run check会失败。 - 按照第 2 节步骤 3 和 4 配置页面及其测试。
3. 试运行一次(供项目负责人查看),以及技能背后的检查机制
执行 npm run build && npm run preview 后,或者通过 Pull Request 的预览地址,项目负责人可以:
- 等待:照片会自动切换,当前显示的照片对应的圆点也会随之变化。
- 将鼠标悬停在轮播上:轮播会暂停。移开鼠标:经过一个完整间隔后,轮播继续。
- 在手机上:点击圆点(会显示对应照片);页面不会横向滚动;圆点易于点击。
- 在按钮模式下:按下“暂停”按钮,看到按钮变为“恢复”,再次按下。
本技能在构建时使用的检查清单,以便于评估模板的变更:每个检查点对应一个测试规格,当受保护的功能被移除时,对应的测试会失败。当模板发生变更时,手动执行一次验证(CI 作业会将这些模板安装到一个全新站点中,并在该站点使用的三种设置组合下运行测试规格):
从 HeroCarousel.astro 中移除以下内容 | 对应的测试会失败 |
|---|---|
网格规则(display: grid, grid-area) | “所有幻灯片共享一个位置…” |
照片上的 aspect-ratio | 同一个测试(一张 4:3 的照片会更高) |
药丸形背景的 background | “在所有照片上渲染的对比度…”(浅色照片上的白色约为 1:1) |
当前显示圆点的 border-width: 5px | 强制颜色测试 |
transition 规则 | 淡入测试(“动画中途的一帧”) |
未显示幻灯片上的 pointer-events: none | “照片链接到其页面…”(正在渐出的幻灯片会接收点击) |
脚本(hero-carousel.js)为未显示幻灯片设置的 inert 和 aria-hidden | Tab 键和辅助功能树测试(正在移出的幻灯片的链接在淡出期间仍然可访问和可读) |
脚本的 recover() 方法(当前正在显示的照片加载失败时,由第一张其他成功加载或即将加载的照片替换) | “第一张图片加载失败…” |
4. 隐私页面,以及如何回退
轮播不存储和不发送任何信息:无需在隐私页面中添加任何内容。
要移除轮播:删除四个文件,从页面中删除 <HeroCarousel> 代码块以及它使用的 import HeroCarousel 行(以及仅由它使用的图片导入);然后放回一张普通照片。系统中没有其他部分引用这些文件,除了受 PAGES 驱动的测试套件,但它们只会看到页面本身。
5. 本技能不包含的内容(以免造成误解)
上一张 / 下一张箭头、滑动手势、照片上的说明文字、照片上的来源信息、视频幻灯片、页面上多个轮播(其 ID 是固定的)、后续照片的懒加载。每一项都可以后续添加;但第一版中不需要任何这些功能。