CYTRUS — owocowy slider plakatowy
AnimacjePodgląd
Ładowanie podglądu…
Duża typografia, soczyste fotografie i przekątna przesłona. Paralaksa kadru reaguje na kursor oraz przewijanie. Fotografie: Pexels, na licencji Pexels. Kod: MIT.
Kod źródłowy
Licencja MIT — także do użytku komercyjnego. Zachowaj informacje o autorze i licencji.
HTML
<main><section class="slider cytrus" aria-label="Cytrusy w trzech kadrach" aria-roledescription="karuzela" tabindex="0"><header><span>Małe przyjemności.</span><span>Dużo słońca.</span></header><div class="stage"><h1>CYT<br>RUS!</h1><article class="slide is-active" role="group" aria-roledescription="slajd" aria-label="1 z 3" aria-hidden="false" data-title="Z fantazją" style="--offset:0;--ring:0"><div class="photo"><img fetchpriority="high" src="https://codepen.pl/media/codepen/sliders-2026/cytrus-1.webp" alt="Plastry cytrusów zawieszone na słomce na żółtym tle" width="1520" height="1900" decoding="async" draggable="false"></div><div class="caption"><h2>Z fantazją</h2><p>Odrobina słońca na żółtym tle.</p></div></article><article class="slide" role="group" aria-roledescription="slajd" aria-label="2 z 3" aria-hidden="true" data-title="Do szklanki" style="--offset:1;--ring:1"><div class="photo"><img src="https://codepen.pl/media/codepen/sliders-2026/cytrus-2.webp" alt="Szklanka soku otoczona plastrami pomarańczy i cytryny" width="1600" height="1067" decoding="async" draggable="false"></div><div class="caption"><h2>Do szklanki</h2><p>Świeżo wyciśnięte. Bez pośpiechu.</p></div></article><article class="slide" role="group" aria-roledescription="slajd" aria-label="3 z 3" aria-hidden="true" data-title="Po kawałku" style="--offset:2;--ring:-1"><div class="photo"><img src="https://codepen.pl/media/codepen/sliders-2026/cytrus-3.webp" alt="Stos soczystych cząstek pomarańczy w zbliżeniu" width="1600" height="1067" decoding="async" draggable="false"></div><div class="caption"><h2>Po kawałku</h2><p>Cała przyjemność w prostych rzeczach.</p></div></article><span class="vertical-label" aria-hidden="true">DOBRZE BYĆ PO TEJ STRONIE LATA</span></div><div class="fruit-nav"><div class="controls"><button type="button" class="arrow" data-step="-1" aria-label="Poprzedni slajd">←</button><span class="position" aria-hidden="true">01 / 03</span><button type="button" class="arrow" data-step="1" aria-label="Następny slajd">→</button></div><div class="selectors" role="group" aria-label="Wybierz zdjęcie"><button type="button" data-slide="0" aria-label="Slajd 1: Z fantazją" aria-pressed="true">01</button><button type="button" data-slide="1" aria-label="Slajd 2: Do szklanki" aria-pressed="false">02</button><button type="button" data-slide="2" aria-label="Slajd 3: Po kawałku" aria-pressed="false">03</button></div><p>Wybierz swoją porcję koloru.</p></div></section><p class="announcement sr-only" role="status" aria-atomic="true"></p></main><footer class="pen-footer"><small>Fotografie: JJ Jordan, AS Photography, Ilo Frey / Pexels.<br>Zdjęcia: licencja Pexels (pexels.com/license). Kod: MIT.</small><button type="button" class="motion" aria-pressed="true">Wyłącz paralaksę</button></footer><!-- Źródła fotografii: https://www.pexels.com/photo/a-sliced-lemon-and-orange-fruits-on-a-straw-7545837/ | https://www.pexels.com/photo/lemon-fruits-96974/ | https://www.pexels.com/photo/close-up-photo-of-sliced-orange-fruits-2661834/ -->
CSS
*{box-sizing:border-box}html{color-scheme:light}body{margin:0}h1,h2,p,figure{margin:0}button{font:inherit;color:inherit;border-radius:0;cursor:pointer;min-height:44px;min-width:44px;touch-action:manipulation}button:focus-visible,[tabindex]:focus-visible{outline:3px solid var(--focus,currentColor);outline-offset:5px}img{display:block;width:100%;height:100%;object-fit:cover}button:disabled{cursor:default}main{overflow:hidden}.slider{position:relative;isolation:isolate;--mx:0px;--my:0px;--sy:0px;--turn:0deg}.stage{position:relative;touch-action:pan-y}.slide{margin:0}.photo{overflow:hidden;background:#374740}.photo img{will-change:transform}.controls{display:flex;align-items:center;gap:12px}.arrow{background:transparent;border:1px solid currentColor;font-size:25px;width:52px;height:52px;line-height:1}.arrow:hover{background:var(--ink);color:var(--paper)}.position{font:12px/1.5 monospace;min-width:62px;text-align:center}.selectors{display:flex;gap:8px}.selectors button{background:transparent;border:0;border-bottom:2px solid transparent;padding:10px 12px}.selectors button[aria-pressed="true"]{border-color:currentColor}.sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.pen-footer{padding:22px 5%;display:flex;align-items:center;justify-content:space-between;gap:24px;border-top:1px solid currentColor;font:11px/1.6 monospace}.pen-footer small{font-size:inherit;max-width:80ch}.motion{background:none;border:1px solid currentColor;padding:9px 12px;font:11px monospace;flex-shrink:0}.motion:hover{text-decoration:underline;text-underline-offset:4px}.motion-off .photo img{transform:none!important;transition:none!important}.motion-off *{transition:none!important}.load-error{position:absolute;bottom:10px;left:10px;background:#fff;color:#111;padding:8px;font:12px sans-serif}.slider .slide[aria-hidden="true"]{pointer-events:none}@media(max-width:600px){.pen-footer{align-items:flex-start;flex-direction:column;gap:16px}.controls{gap:8px}.arrow{width:48px;height:48px}.selectors{gap:2px}}@media(prefers-reduced-motion:reduce){body:not(.motion-enabled) *,body:not(.motion-enabled) *::before,body:not(.motion-enabled) *::after{animation:none!important;transition:none!important}body:not(.motion-enabled) .photo img{transform:none!important}}
body{background:#ffad18;color:#442211;font-family:Arial,sans-serif;--ink:#442211;--paper:#ffad18;--focus:#442211}.cytrus header{display:flex;justify-content:space-between;padding:25px 5%;border-bottom:2px solid #442211;font:12px monospace}.cytrus .stage{min-height:650px;height:77svh;max-height:900px;overflow:hidden}.cytrus h1{position:absolute;left:5%;top:50%;transform:translateY(-55%);font-family:Impact,'Arial Narrow',sans-serif;font-size:clamp(110px,17vw,245px);line-height:.86;letter-spacing:-.04em;font-weight:900;z-index:3;pointer-events:none;text-shadow:2px 2px 0 #ffad18}.cytrus .slide{position:absolute;inset:24px 9% 24px 38%;opacity:0;clip-path:polygon(0 0,0 0,-20% 100%,-20% 100%);transition:clip-path .8s cubic-bezier(.65,0,.2,1),opacity .4s;transform:rotate(-3deg)}.cytrus .slide.is-active{opacity:1;clip-path:polygon(0 0,100% 0,100% 100%,0 100%);z-index:1}.cytrus .photo{position:absolute;inset:0 0 108px;border:2px solid #442211}.cytrus .photo img{transform:translate3d(var(--mx),calc(var(--my) + var(--sy)),0) scale(1.16);object-position:50% 50%;transition:transform .65s ease}.cytrus .caption{position:absolute;bottom:0;left:0;right:0;height:110px;background:#442211;color:#fff1cd;padding:20px 25px;transform:translateY(30px);transition:transform .8s}.cytrus .is-active .caption{transform:translateY(0)}.cytrus h2{font-size:31px;letter-spacing:-1px}.cytrus .caption p{font-size:12px;line-height:1.5;margin-top:8px}.vertical-label{position:absolute;right:4%;top:50%;writing-mode:vertical-rl;font:11px monospace;letter-spacing:.13em;transform:translateY(-50%)}.fruit-nav{display:flex;justify-content:space-between;align-items:center;padding:24px 5%;border-top:2px solid #442211;gap:25px}.fruit-nav p{font-size:13px;font-weight:700}.cytrus .selectors button{font:12px monospace}.cytrus .arrow{border:2px solid #442211}.cytrus .pen-footer{border-top-width:2px}@media(max-width:600px){.cytrus header{font-size:10px}.cytrus .stage{height:auto;min-height:660px}.cytrus h1{top:25px;left:6%;transform:none;font-size:91px;line-height:1;letter-spacing:-5px}.cytrus h1 br{display:none}.cytrus .slide{inset:140px 9% 22px 9%;transform:rotate(-3deg)}.cytrus .photo{bottom:103px}.cytrus .caption{height:105px;padding:18px}.cytrus h2{font-size:29px}.cytrus .caption p{font-size:11px}.vertical-label{display:none}.fruit-nav{flex-wrap:wrap;gap:20px}.fruit-nav p{width:100%;font-size:12px}.cytrus .selectors button{min-width:40px;padding:6px}}
.slider:focus-visible{outline-offset:-4px}
JavaScript
function wrapSlide(index, count) { return ((index % count) + count) % count; }
function swipeDirection(dx, dy) { return Math.abs(dx) >= 50 && Math.abs(dx) > Math.abs(dy) * 1.5 ? (dx < 0 ? 1 : -1) : 0; }
const depth = { x: 28, y: 16, scroll: 48 };
$(function () {
const root = document.querySelector('.slider'), slides = Array.from(root.querySelectorAll('.slide'));
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
let current = 0, disabled = reduce.matches, queued = false, pointerX = 0, pointerY = 0, start = null;
function paint() {
queued = false;
const rect = root.getBoundingClientRect();
const scroll = Math.max(-1, Math.min(1, (window.innerHeight / 2 - rect.top - rect.height / 2) / window.innerHeight));
root.style.setProperty('--mx', (disabled ? 0 : pointerX * depth.x) + 'px');
root.style.setProperty('--my', (disabled ? 0 : pointerY * depth.y) + 'px');
root.style.setProperty('--sy', (disabled ? 0 : scroll * depth.scroll) + 'px');
root.style.setProperty('--turn', (disabled ? 0 : pointerX * 3) + 'deg');
}
function requestPaint() { if (!queued) { queued = true; requestAnimationFrame(paint); } }
function show(index, announce) {
current = wrapSlide(index, slides.length);
slides.forEach(function (slide, i) {
const active = i === current;
slide.classList.toggle('is-active', active);
slide.setAttribute('aria-hidden', String(!active));
slide.inert = !active;
slide.style.setProperty('--offset', String(i - current));
slide.style.setProperty('--ring', String(wrapSlide(i - current + 1, slides.length) - 1));
});
root.dataset.current = String(current);
root.style.setProperty('--index', String(current));
$('[data-slide]').each(function () { $(this).attr('aria-pressed', String(Number($(this).attr('data-slide')) === current)); });
$('.position').text(String(current + 1).padStart(2, '0') + ' / 03');
if (announce) $('.announcement').text('Slajd ' + (current + 1) + ' z 3: ' + slides[current].dataset.title);
requestPaint();
}
$('[data-step]').on('click', function () { show(current + Number($(this).attr('data-step')), true); });
$('[data-slide]').on('click', function () { show(Number($(this).attr('data-slide')), true); });
$(root).on('keydown', function (event) {
if (event.altKey || event.ctrlKey || event.metaKey || /INPUT|TEXTAREA|SELECT/.test(event.target.tagName)) return;
const target = { ArrowLeft: current - 1, ArrowRight: current + 1, Home: 0, End: slides.length - 1 }[event.key];
if (target !== undefined) { event.preventDefault(); show(target, true); }
});
const stage = root.querySelector('.stage');
stage.addEventListener('pointerdown', function (event) { if (event.isPrimary && event.button === 0 && !event.target.closest('button')) start = { id: event.pointerId, x: event.clientX, y: event.clientY }; });
stage.addEventListener('pointerup', function (event) { if (!start || start.id !== event.pointerId) return; const step = swipeDirection(event.clientX - start.x, event.clientY - start.y); start = null; if (step) show(current + step, true); });
stage.addEventListener('pointercancel', function () { start = null; });
stage.addEventListener('pointerleave', function () { start = null; pointerX = 0; pointerY = 0; requestPaint(); });
stage.addEventListener('pointermove', function (event) { if (disabled || event.pointerType !== 'mouse') return; const r = stage.getBoundingClientRect(); pointerX = (event.clientX - r.left) / r.width - .5; pointerY = (event.clientY - r.top) / r.height - .5; requestPaint(); });
stage.querySelectorAll('img').forEach(function (img) { img.draggable = false; img.addEventListener('error', function () { if (!img.parentElement.querySelector('.load-error')) { const note = document.createElement('span'); note.className = 'load-error'; note.textContent = 'Nie udało się wczytać zdjęcia.'; img.parentElement.append(note); } }); });
function motion() {
document.body.classList.toggle('motion-off', disabled);
document.body.classList.toggle('motion-enabled', !disabled);
$('.motion').attr('aria-pressed', String(!disabled)).text(disabled ? 'Włącz paralaksę' : 'Wyłącz paralaksę');
requestPaint();
}
$('.motion').on('click', function () { disabled = !disabled; motion(); });
reduce.addEventListener('change', function () { disabled = reduce.matches; motion(); });
window.addEventListener('scroll', requestPaint, { passive: true });
window.addEventListener('resize', requestPaint);
motion(); show(0, false);
});
Edytuj swój pen
Podaj adres e-mail użyty przy zapisie. Wyślemy link do edycji.