Przejdź do treści
<code>pen.pl

MONO — pionowy slider architektury

Animacje

Podgląd

Ładowanie podglądu…

Architektoniczny slider z pionową przesłoną, numerowanym indeksem i fotografią przesuwającą się w przeciwnym kierunku. Fotografie: Pexels, na licencji Pexels. Kod: MIT.

0,0 / 5 Brak ocen

Kod źródłowy

Licencja MIT — także do użytku komercyjnego. Zachowaj informacje o autorze i licencji.

HTML
<main><section class="slider mono" aria-label="Studium architektury" aria-roledescription="karuzela" tabindex="0"><header><span class="brand">M / ARCHIWUM FORMY</span><h1>Forma.<br> Światło.<br> Materiał.</h1><p>Architektura bez adresów.<br>Patrzymy na detale.</p><div class="index"><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: Beton" aria-pressed="true">Beton</button><button type="button" data-slide="1" aria-label="Slajd 2: Krawędź" aria-pressed="false">Krawędź</button><button type="button" data-slide="2" aria-label="Slajd 3: Rytm" aria-pressed="false">Rytm</button></div></div></header><div class="stage"><article class="slide is-active" role="group" aria-roledescription="slajd" aria-label="1 z 3" aria-hidden="false" data-title="Beton" style="--offset:0;--ring:0"><div class="photo"><img fetchpriority="high" src="https://codepen.pl/media/codepen/sliders-2026/mono-1.webp" alt="Surowa betonowa fasada z geometrycznymi oknami" width="1600" height="1067" decoding="async" draggable="false"></div><div class="caption"><h2>Beton</h2><p>Ciężar materiału. Lekkość cienia.</p></div></article><article class="slide" role="group" aria-roledescription="slajd" aria-label="2 z 3" aria-hidden="true" data-title="Krawędź" style="--offset:1;--ring:1"><div class="photo"><img src="https://codepen.pl/media/codepen/sliders-2026/mono-2.webp" alt="Ostry narożnik betonowego budynku pod zachmurzonym niebem" width="1258" height="1900" decoding="async" draggable="false"></div><div class="caption"><h2>Krawędź</h2><p>Budynek zamknięty w kilku liniach.</p></div></article><article class="slide" role="group" aria-roledescription="slajd" aria-label="3 z 3" aria-hidden="true" data-title="Rytm" style="--offset:2;--ring:-1"><div class="photo"><img src="https://codepen.pl/media/codepen/sliders-2026/mono-3.webp" alt="Wysoka fasada wieżowca z rytmem pionowych okien" width="1600" height="1304" decoding="async" draggable="false"></div><div class="caption"><h2>Rytm</h2><p>Powtarzalność, która buduje fasadę.</p></div></article><div class="ruler" aria-hidden="true"></div></div></section><p class="announcement sr-only" role="status" aria-atomic="true"></p></main><footer class="pen-footer"><small>Fotografie: Markus Winkler, Italo Melo, Gotta Be Worth It / 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/concrete-brutalist-building-3188582/ | https://www.pexels.com/photo/concrete-building-2220483/ | https://www.pexels.com/photo/photo-of-concrete-building-1021166/ -->
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:#f1f0e9;color:#20221f;font-family:Arial,sans-serif;--ink:#20221f;--paper:#f1f0e9;--focus:#a93313}.mono{display:grid;grid-template-columns:minmax(260px,.72fr) 1.7fr;min-height:780px}.mono header{padding:40px 35px 45px;border-right:1px solid #20221f;display:flex;flex-direction:column;align-items:flex-start}.brand{font:11px monospace;letter-spacing:.06em}.mono h1{font-size:clamp(48px,5vw,76px);letter-spacing:-.065em;line-height:1.03;margin:75px 0 25px}.mono header>p{font-size:13px;line-height:1.7}.mono .index{margin-top:auto;padding-top:55px;width:100%;display:flex;flex-direction:column-reverse;gap:35px}.mono .selectors{flex-direction:column;gap:0;counter-reset:photo}.mono .selectors button{text-align:left;border-bottom:1px solid #a9aaa3;padding:12px 0;font-size:17px;display:flex;gap:20px;align-items:center}.mono .selectors button::before{counter-increment:photo;content:counter(photo,decimal-leading-zero);font:11px monospace}.mono .selectors button[aria-pressed="true"]{color:#a93313;border-bottom:2px solid #a93313}.mono .selectors button[aria-pressed="true"]::after{content:'↗';margin-left:auto}.mono .stage{margin:30px 30px 30px 0;overflow:hidden;min-height:720px}.mono .slide{position:absolute;inset:0;clip-path:inset(0 0 100% 0);transition:clip-path .8s cubic-bezier(.65,0,.25,1);background:#f1f0e9}.mono .slide.is-active{clip-path:inset(0);z-index:1}.mono .photo{position:absolute;inset:0 0 112px 30px}.mono .photo img{transform:translate3d(var(--mx),calc(var(--sy) - 35px),0) scale(1.18);transition:transform 1s ease;filter:saturate(.5)}.mono .is-active .photo img{transform:translate3d(var(--mx),var(--sy),0) scale(1.18)}.mono .caption{position:absolute;bottom:0;left:30px;right:0;border-top:1px solid #20221f;padding:20px 0;display:flex;justify-content:space-between;align-items:baseline;gap:22px}.mono h2{font-size:28px;letter-spacing:-1px}.mono .caption p{font-size:12px;line-height:1.6;max-width:24ch}.ruler{position:absolute;z-index:2;width:12px;left:0;top:0;bottom:112px;background:repeating-linear-gradient(to bottom,#20221f 0,#20221f 1px,transparent 1px,transparent 12px)}@media(max-width:700px){.mono{display:block;min-height:0}.mono header{padding:25px 6%;border:0}.mono h1{font-size:53px;margin:35px 0 18px}.mono h1 br{display:none}.mono h1 br::after{content:' '}.mono header>p{display:none}.mono .index{padding-top:25px;flex-direction:row;align-items:center;gap:20px}.mono .controls{gap:0}.mono .position{display:none}.mono .arrow{width:44px;height:44px;border:0}.mono .selectors{flex-direction:row;flex:1}.mono .selectors button{font-size:12px;padding:10px 8px}.mono .selectors button::before,.mono .selectors button::after{display:none}.mono .stage{min-height:520px;margin:0 6% 20px}.mono .photo{left:22px;bottom:95px}.mono .caption{left:22px;gap:15px;padding:20px 0}.mono h2{font-size:24px}.mono .caption p{font-size:11px;max-width:20ch}.ruler{bottom:95px}}
.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: 14, y: 10, scroll: 55 };
$(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);
});
Biblioteki

JavaScript

Edytuj swój pen

Podaj adres e-mail użyty przy zapisie. Wyślemy link do edycji.

Link jest ważny przez 30 minut.