Autoplay Carousel
A carousel that advances on a timer, pauses on hover and focus, and respects reduced motion.
6 个框架高级
A slide carousel with prev/next arrows, dots and a polite live region.
<!--
A carousel is a region, not a div with arrows:
- role="region" + aria-roledescription="carousel" + aria-label names the whole
thing, so it appears in the landmark list and announces as a carousel
- the viewport is aria-live="polite": moving a slide announces the new one
without stealing focus. (Autoplay must NOT do this - see carousel-autoplay.)
- each slide is role="group" aria-roledescription="slide" aria-label="N of M"
- inactive slides are aria-hidden so their text is not read while off-screen
The track is one flex row translated by whole viewport widths; the script only
ever sets a transform and some attributes.
-->
<section class="carousel" aria-roledescription="carousel" aria-label="Product highlights" data-carousel>
<div class="carousel__viewport" aria-live="polite">
<div class="carousel__track" data-carousel-track>
<div class="carousel__slide carousel__slide--a" role="group" aria-roledescription="slide" aria-label="1 of 3">
<h3 class="carousel__title">Analytics</h3>
<p class="carousel__copy">Every metric that matters, updated the moment it changes.</p>
</div>
<div class="carousel__slide carousel__slide--b" role="group" aria-roledescription="slide" aria-label="2 of 3" aria-hidden="true">
<h3 class="carousel__title">Automations</h3>
<p class="carousel__copy">Turn a repeated click into a rule that runs itself.</p>
</div>
<div class="carousel__slide carousel__slide--c" role="group" aria-roledescription="slide" aria-label="3 of 3" aria-hidden="true">
<h3 class="carousel__title">Integrations</h3>
<p class="carousel__copy">Forty connectors, and a webhook for everything else.</p>
</div>
</div>
<button class="carousel__arrow carousel__arrow--prev" type="button" aria-label="Previous slide" data-carousel-prev>
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="m12.5 5-5 5 5 5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<button class="carousel__arrow carousel__arrow--next" type="button" aria-label="Next slide" data-carousel-next>
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="m7.5 5 5 5-5 5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
<div class="carousel__dots" data-carousel-dots>
<button class="carousel__dot" type="button" aria-label="Go to slide 1" aria-current="true"></button>
<button class="carousel__dot" type="button" aria-label="Go to slide 2"></button>
<button class="carousel__dot" type="button" aria-label="Go to slide 3"></button>
</div>
</section>
<script>
(function () {
document.querySelectorAll('[data-carousel]').forEach(function (root) {
var track = root.querySelector('[data-carousel-track]');
var slides = Array.prototype.slice.call(track.children);
var dots = Array.prototype.slice.call(root.querySelectorAll('[data-carousel-dots] button'));
var index = 0;
function go(next) {
index = (next + slides.length) % slides.length;
track.style.transform = 'translateX(' + index * -100 + '%)';
slides.forEach(function (slide, i) {
// Off-screen slides are still in the DOM: hide them from AT.
if (i === index) slide.removeAttribute('aria-hidden');
else slide.setAttribute('aria-hidden', 'true');
});
dots.forEach(function (dot, i) {
if (i === index) dot.setAttribute('aria-current', 'true');
else dot.removeAttribute('aria-current');
});
}
root.querySelector('[data-carousel-prev]').addEventListener('click', function () {
go(index - 1);
});
root.querySelector('[data-carousel-next]').addEventListener('click', function () {
go(index + 1);
});
dots.forEach(function (dot, i) {
dot.addEventListener('click', function () {
go(i);
});
});
});
})();
</script>| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
items必填 | Slide[] | - | 要渲染的项目数组。 |
onSelect | (index: number) => void | - | 用户选择菜单项时触发,参数为所选项。 |
className | string | - | 合并到根元素上的额外类名。 |
The root is a labelled `role="region"` with `aria-roledescription="carousel"`, and each slide is a `role="group"` labelled "N of M" - that pair is what tells a screen-reader user what they are in and where they are. The viewport is `aria-live="polite"`, so pressing next announces the new slide without stealing focus; off-screen slides get `aria-hidden` so their text is not read while parked. The track is one flex row translated by whole viewport widths, so the only thing the JS ever writes is a transform.