Banner-Slider auf der Startseite
Komponente banner-slider liest data/banners.json; banner.js übernimmt Autoplay/Pause/Steuerung (progressive enhancement). Slider sitzt zwischen Hero und Instagram-Feed. Bilder vorerst als Platzhalter-SVGs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
This commit is contained in:
115
public/assets/js/banner.js
Normal file
115
public/assets/js/banner.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/* Werbe-/Sponsoren-Banner-Slider: Auto-Rotation über die Fortschrittsanimation
|
||||
des aktiven Indikators (animationend → nächste Folie).
|
||||
|
||||
Barrierefreiheit:
|
||||
- explizite Pause/Play-Taste (WCAG 2.2.2 „Pause, Stop, Hide"),
|
||||
- zusätzlich Pause bei Hover/Fokus (Komfort),
|
||||
- aria-live: im Autoplay "off" (kein Vorlese-Spam), bei Pause/manuell "polite",
|
||||
- bei prefers-reduced-motion läuft keine Animation → keine Auto-Rotation;
|
||||
Indikatoren bleiben per Klick bedienbar, die Pause-Taste entfällt. */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var root = document.querySelector('[data-banner]');
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
var track = root.querySelector('#ad-banner-track');
|
||||
var slides = Array.prototype.slice.call(root.querySelectorAll('[data-banner-slide]'));
|
||||
var dots = Array.prototype.slice.call(root.querySelectorAll('[data-banner-dot]'));
|
||||
var controls = root.querySelector('[data-banner-controls]');
|
||||
var toggle = root.querySelector('[data-banner-toggle]');
|
||||
var toggleLabel = root.querySelector('[data-banner-toggle-label]');
|
||||
if (slides.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
var index = 0;
|
||||
var hoverPaused = false;
|
||||
var userPaused = false;
|
||||
|
||||
function syncPaused() {
|
||||
// Bei reduzierter Bewegung gilt jeder Wechsel als manuell → ansagen.
|
||||
var paused = hoverPaused || userPaused || reduce;
|
||||
root.classList.toggle('is-paused', paused);
|
||||
if (track) {
|
||||
track.setAttribute('aria-live', paused ? 'polite' : 'off');
|
||||
}
|
||||
}
|
||||
|
||||
function activate(i) {
|
||||
index = (i % slides.length + slides.length) % slides.length;
|
||||
slides.forEach(function (slide, n) {
|
||||
slide.hidden = n !== index;
|
||||
});
|
||||
dots.forEach(function (dot, n) {
|
||||
var on = n === index;
|
||||
dot.classList.toggle('is-active', on);
|
||||
if (on) {
|
||||
dot.setAttribute('aria-current', 'true');
|
||||
} else {
|
||||
dot.removeAttribute('aria-current');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sobald die Fortschrittsanimation des aktiven Indikators durchgelaufen ist,
|
||||
// zur nächsten Folie wechseln. (Beim Wechsel wird die alte Animation
|
||||
// abgebrochen → 'animationcancel', nicht 'animationend' → kein Doppelsprung.)
|
||||
root.addEventListener('animationend', function (event) {
|
||||
if (event.animationName === 'ad-progress') {
|
||||
activate(index + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Pause bei Hover/Fokus.
|
||||
root.addEventListener('mouseenter', function () {
|
||||
hoverPaused = true;
|
||||
syncPaused();
|
||||
});
|
||||
root.addEventListener('mouseleave', function () {
|
||||
hoverPaused = false;
|
||||
syncPaused();
|
||||
});
|
||||
root.addEventListener('focusin', function () {
|
||||
hoverPaused = true;
|
||||
syncPaused();
|
||||
});
|
||||
root.addEventListener('focusout', function (event) {
|
||||
if (!root.contains(event.relatedTarget)) {
|
||||
hoverPaused = false;
|
||||
syncPaused();
|
||||
}
|
||||
});
|
||||
|
||||
dots.forEach(function (dot, n) {
|
||||
dot.addEventListener('click', function () {
|
||||
activate(n);
|
||||
});
|
||||
});
|
||||
|
||||
// Steuerung einblenden (ohne JS bleibt sie verborgen, da funktionslos).
|
||||
if (controls) {
|
||||
controls.hidden = false;
|
||||
}
|
||||
|
||||
// Pause/Play nur anbieten, wenn überhaupt automatisch rotiert wird.
|
||||
if (toggle && !reduce) {
|
||||
toggle.hidden = false;
|
||||
toggle.addEventListener('click', function () {
|
||||
userPaused = !userPaused;
|
||||
toggle.setAttribute('aria-pressed', String(userPaused));
|
||||
if (toggleLabel) {
|
||||
toggleLabel.textContent = userPaused
|
||||
? 'Automatischen Wechsel fortsetzen'
|
||||
: 'Automatischen Wechsel pausieren';
|
||||
}
|
||||
syncPaused();
|
||||
});
|
||||
}
|
||||
|
||||
activate(0);
|
||||
syncPaused();
|
||||
})();
|
||||
Reference in New Issue
Block a user