Galériák és képek
Lapozható fotóalbum
Élő előnézet – a komponens teljes méretben.
Élő előnézet
HTML · CSS · JS
<section class="we-album" aria-label="Lapozható fotóalbum">
<header class="we-album__intro">
<span class="we-album__eyebrow">EMLÉKEK KÖNYVE</span>
<h2>Helyek, ahová jó visszatérni</h2>
<p>Egy kis utazás képekben — lapozz bele.</p>
</header>
<div class="we-album__stage">
<div class="we-album__book"
tabindex="0"
aria-label="Fotóalbum. Lapozás a bal és jobb nyílbillentyűkkel.">
<div class="we-album__left">
<div class="we-album__photo">
<img data-left-image
src="https://images.unsplash.com/photo-1470770841072-f978cf4d019e?auto=format&fit=crop&w=900&q=85"
alt="Hegyek és tó">
</div>
<span class="we-album__page-number" data-left-number>01</span>
</div>
<div class="we-album__right">
<div class="we-album__photo">
<img data-right-image
src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=900&q=85"
alt="Napfényes hegyvidék">
</div>
<div class="we-album__caption">
<span data-caption-kicker>01 / UTAZÁS</span>
<h3 data-caption-title>A csend pillanata</h3>
<p data-caption-text>
Van, amikor elég csak megállni, és körülnézni.
</p>
</div>
<span class="we-album__page-number" data-right-number>02</span>
<div class="we-album__turn-page" data-turn-page aria-hidden="true">
<div class="we-album__turn-face we-album__turn-front">
<img data-turn-front
src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=900&q=85"
alt="">
</div>
<div class="we-album__turn-face we-album__turn-back">
<img data-turn-back
src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&q=85"
alt="">
</div>
</div>
</div>
</div>
</div>
<nav class="we-album__controls" aria-label="Albumlapozás">
<button class="we-album__nav"
type="button"
data-prev
aria-label="Előző oldalpár">
<span aria-hidden="true">←</span>
<span>Előző</span>
</button>
<div class="we-album__progress" aria-live="polite">
<span data-current>02</span>
<i></i>
<span data-total>08</span>
</div>
<button class="we-album__nav"
type="button"
data-next
aria-label="Következő oldalpár">
<span>Következő</span>
<span aria-hidden="true">→</span>
</button>
</nav>
<p class="we-album__hint">
Lapozz a nyilakkal, vagy használd a billentyűzet bal és jobb nyilát.
</p>
</section>
.we-album {
--album-bg: #101b18;
--album-paper: #f4f0e6;
--album-ink: #25342d;
--album-gold: #c7a76a;
--album-muted: #b8c0b9;
position: relative;
isolation: isolate;
width: 100%;
box-sizing: border-box;
padding: clamp(28px, 5vw, 64px) 20px 36px;
overflow: hidden;
color: #f4f0e6;
background:
radial-gradient(ellipse at 50% 40%, #263c31 0%, transparent 60%),
var(--album-bg);
font-family: Georgia, "Times New Roman", serif;
}
.we-album *,
.we-album *::before,
.we-album *::after {
box-sizing: border-box;
}
.we-album__intro {
position: relative;
z-index: 1;
max-width: 760px;
margin: 0 auto 36px;
text-align: center;
}
.we-album__eyebrow {
display: inline-block;
margin-bottom: 14px;
color: var(--album-gold);
font-family: Arial, sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.28em;
}
.we-album__intro h2 {
margin: 0;
color: #f5f0e5;
font-size: clamp(26px, 4vw, 44px);
font-weight: 400;
line-height: 1.2;
}
.we-album__intro p {
margin: 14px 0 0;
color: var(--album-muted);
font-family: Arial, sans-serif;
font-size: 15px;
line-height: 1.7;
}
.we-album__stage {
width: 100%;
max-width: 1000px;
margin: 0 auto;
perspective: 1800px;
}
.we-album__book {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
width: 100%;
max-width: 900px;
margin: 0 auto;
aspect-ratio: 1.45 / 1;
min-height: 300px;
background: #d7cbb1;
border: 8px solid #26372e;
border-radius: 5px;
box-shadow:
0 28px 65px rgb(0 0 0 / 35%),
0 0 0 1px rgb(199 167 106 / 35%);
transform-style: preserve-3d;
}
.we-album__book:focus-visible {
outline: 2px solid var(--album-gold);
outline-offset: 6px;
}
.we-album__left,
.we-album__right {
position: relative;
min-width: 0;
min-height: 0;
overflow: hidden;
background: var(--album-paper);
color: var(--album-ink);
}
.we-album__left {
border-right: 1px solid rgb(55 44 28 / 20%);
box-shadow: inset -12px 0 18px -18px #000;
}
.we-album__right {
border-left: 1px solid rgb(55 44 28 / 15%);
box-shadow: inset 12px 0 18px -18px #000;
}
.we-album__photo {
position: absolute;
inset: 0;
overflow: hidden;
background: #d8d4c8;
}
.we-album__photo img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.we-album__left .we-album__photo img {
object-position: center;
}
.we-album__right .we-album__photo img {
object-position: center;
}
.we-album__left::after,
.we-album__right::after {
position: absolute;
z-index: 1;
inset: 0;
content: "";
pointer-events: none;
}
.we-album__left::after {
background: linear-gradient(
90deg,
rgb(0 0 0 / 4%),
transparent 35%,
rgb(0 0 0 / 9%)
);
}
.we-album__right::after {
background: linear-gradient(
90deg,
rgb(0 0 0 / 9%),
transparent 25%,
rgb(0 0 0 / 3%)
);
}
.we-album__page-number {
position: absolute;
z-index: 3;
right: 18px;
bottom: 13px;
color: #5e665c;
font-family: Arial, sans-serif;
font-size: 11px;
letter-spacing: 0.12em;
}
.we-album__caption {
position: absolute;
z-index: 2;
right: 0;
bottom: 0;
left: 0;
padding: 30px 24px 48px;
color: #fff;
background: linear-gradient(
transparent,
rgb(8 16 12 / 85%)
);
}
.we-album__caption > span {
color: #e5c98e;
font-family: Arial, sans-serif;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.2em;
}
.we-album__caption h3 {
margin: 10px 0 8px;
color: #fff;
font-size: clamp(19px, 2.5vw, 29px);
font-weight: 400;
line-height: 1.2;
}
.we-album__caption p {
max-width: 330px;
margin: 0;
color: rgb(255 255 255 / 85%);
font-family: Arial, sans-serif;
font-size: 13px;
line-height: 1.6;
}
/* A lap, amely átfordul a jobb oldalon */
.we-album__turn-page {
position: absolute;
z-index: 5;
inset: 0;
overflow: visible;
pointer-events: none;
transform: rotateY(0deg);
transform-origin: left center;
transform-style: preserve-3d;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
.we-album__turn-page.is-turning-next {
transform: rotateY(-180deg);
}
.we-album__turn-page.is-turning-prev {
transform: rotateY(180deg);
}
.we-album__right.is-animating .we-album__turn-page {
transition: transform 720ms cubic-bezier(.62, .02, .32, 1);
}
.we-album__turn-face {
position: absolute;
inset: 0;
overflow: hidden;
background: var(--album-paper);
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
.we-album__turn-face img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.we-album__turn-back {
transform: rotateY(180deg);
}
/* Lapozógombok */
.we-album__controls {
display: flex;
align-items: center;
justify-content: center;
gap: clamp(18px, 4vw, 48px);
margin: 28px auto 0;
}
.we-album__nav {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
min-height: 44px;
padding: 10px 15px;
border: 1px solid rgb(199 167 106 / 45%);
border-radius: 2px;
background: transparent;
color: #f4f0e6;
font-family: Arial, sans-serif;
font-size: 13px;
cursor: pointer;
transition:
color 180ms ease,
background 180ms ease,
border-color 180ms ease;
}
.we-album__nav span:first-child {
font-size: 17px;
}
.we-album__nav:hover:not(:disabled) {
border-color: var(--album-gold);
background: var(--album-gold);
color: #18221c;
}
.we-album__nav:focus-visible {
outline: 2px solid var(--album-gold);
outline-offset: 4px;
}
.we-album__nav:disabled {
opacity: 0.35;
cursor: not-allowed;
}
.we-album__progress {
display: flex;
align-items: center;
gap: 12px;
color: var(--album-gold);
font-family: Arial, sans-serif;
font-size: 12px;
letter-spacing: 0.12em;
}
.we-album__progress i {
display: block;
width: 42px;
height: 1px;
background: rgb(199 167 106 / 55%);
}
.we-album__hint {
margin: 20px auto 0;
color: #aab4ac;
font-family: Arial, sans-serif;
font-size: 12px;
line-height: 1.6;
text-align: center;
}
@media (max-width: 600px) {
.we-album {
padding-right: 12px;
padding-left: 12px;
}
.we-album__intro {
margin-bottom: 24px;
}
.we-album__book {
aspect-ratio: 0.95 / 1;
min-height: 280px;
border-width: 5px;
}
.we-album__caption {
padding: 18px 10px 32px;
}
.we-album__caption h3 {
font-size: 17px;
}
.we-album__caption p {
font-size: 11px;
}
.we-album__page-number {
right: 8px;
bottom: 8px;
font-size: 9px;
}
.we-album__controls {
gap: 10px;
margin-top: 20px;
}
.we-album__nav {
gap: 5px;
padding: 9px 10px;
font-size: 12px;
}
.we-album__progress {
gap: 6px;
font-size: 11px;
}
.we-album__progress i {
width: 18px;
}
}
@media (prefers-reduced-motion: reduce) {
.we-album__right.is-animating .we-album__turn-page {
transition-duration: 1ms;
}
.we-album__nav {
transition: none;
}
}
document.querySelectorAll(".we-album").forEach((album) => {
// Az albumhoz tartozó elemek keresése.
const $ = (selector) => album.querySelector(selector);
const book = $(".we-album__book");
const leftImage = $("[data-left-image]");
const rightImage = $("[data-right-image]");
const leftNumber = $("[data-left-number]");
const rightNumber = $("[data-right-number]");
const kicker = $("[data-caption-kicker]");
const title = $("[data-caption-title]");
const captionText = $("[data-caption-text]");
const rightPage = $(".we-album__right");
const turnPage = $("[data-turn-page]");
const turnFront = $("[data-turn-front]");
const turnBack = $("[data-turn-back]");
const currentLabel = $("[data-current]");
const totalLabel = $("[data-total]");
const prevButton = $("[data-prev]");
const nextButton = $("[data-next]");
const requiredElements = {
book,
leftImage,
rightImage,
leftNumber,
rightNumber,
kicker,
title,
captionText,
rightPage,
turnPage,
turnFront,
turnBack,
currentLabel,
totalLabel,
prevButton,
nextButton
};
const missingElements = Object.entries(requiredElements)
.filter(([, element]) => !element)
.map(([name]) => name);
if (missingElements.length) {
console.error(
"Fotóalbum: hiányzó HTML-elemek:",
missingElements.join(", "),
album
);
return;
}
// A fotóalbum tartalma.
const photos = [
{
image: "https://images.unsplash.com/photo-1470770841072-f978cf4d019e?auto=format&fit=crop&w=900&q=85",
alt: "Hegyek és tó",
category: "UTAZÁS",
title: "A csend pillanata",
text: "Van, amikor elég csak megállni, és körülnézni."
},
{
image: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=900&q=85",
alt: "Napfényes hegyvidéki táj",
category: "TERMÉSZET",
title: "A fény nyomában",
text: "A táj minden órában más arcát mutatja."
},
{
image: "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=900&q=85",
alt: "Magas hegycsúcsok",
category: "HEGYEK",
title: "Közelebb az éghez",
text: "Néha a legszebb út felfelé vezet."
},
{
image: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?auto=format&fit=crop&w=900&q=85",
alt: "Napfényes erdei ösvény",
category: "ERDŐ",
title: "Az erdő titka",
text: "A fák között még a csendnek is hangja van."
},
{
image: "https://images.unsplash.com/photo-1473116763249-2faaef81ccda?auto=format&fit=crop&w=900&q=85",
alt: "Zöldellő táj és hegyek",
category: "KIRÁNDULÁS",
title: "Ahol megpihen a szem",
text: "Egy pillanat, amelyet jó eltenni emlékbe."
},
{
image: "https://images.unsplash.com/photo-1501785888041-af3ef285b470?auto=format&fit=crop&w=900&q=85",
alt: "Tó a hegyek között",
category: "VIZEK",
title: "Tükör a hegyek alatt",
text: "A víz megőrzi az ég minden színét."
},
{
image: "https://images.unsplash.com/photo-1518837695005-2083093ee35b?auto=format&fit=crop&w=900&q=85",
alt: "Hullámok a tengerparton",
category: "TENGER",
title: "A hullámok meséje",
text: "A parton ülve az idő is lassabban telik."
},
{
image: "https://images.unsplash.com/photo-1470252649378-9c29740c9fa8?auto=format&fit=crop&w=900&q=85",
alt: "Aranyló napfelkelte a táj felett",
category: "NAPFÉNY",
title: "Egy új reggel",
text: "Minden nap tartogat valamit, amit érdemes észrevenni."
}
];
let spread = 0;
let isAnimating = false;
let animationTimer = null;
let animationToken = 0;
const duration = 740;
const lastSpread = photos.length - 2;
totalLabel.textContent = String(photos.length).padStart(2, "0");
function setImage(imageElement, photo) {
imageElement.src = photo.image;
imageElement.alt = photo.alt;
}
function renderSpread() {
const leftPhoto = photos[spread];
const rightPhoto = photos[spread + 1];
setImage(leftImage, leftPhoto);
setImage(rightImage, rightPhoto);
leftNumber.textContent = String(spread + 1).padStart(2, "0");
rightNumber.textContent = String(spread + 2).padStart(2, "0");
kicker.textContent =
String(spread + 1).padStart(2, "0") +
" / " +
rightPhoto.category;
title.textContent = rightPhoto.title;
captionText.textContent = rightPhoto.text;
currentLabel.textContent = String(spread + 2).padStart(2, "0");
prevButton.disabled = isAnimating || spread === 0;
nextButton.disabled =
isAnimating || spread >= lastSpread;
}
function resetTurnPage() {
turnPage.classList.remove(
"is-turning-next",
"is-turning-prev"
);
rightPage.classList.remove("is-animating");
turnPage.style.transition = "none";
turnPage.style.transform = "rotateY(0deg)";
// A böngészőnek újra kell számolnia a kezdőállapotot,
// mielőtt a következő lapfordítás elindul.
void turnPage.offsetWidth;
turnPage.style.removeProperty("transition");
turnPage.style.removeProperty("transform");
}
function finishTurn(token, nextSpread) {
if (token !== animationToken) {
return;
}
window.clearTimeout(animationTimer);
animationTimer = null;
spread = nextSpread;
isAnimating = false;
resetTurnPage();
renderSpread();
}
function turn(direction) {
if (isAnimating) {
return;
}
const nextSpread = spread + direction * 2;
if (nextSpread < 0 || nextSpread > lastSpread) {
return;
}
isAnimating = true;
renderSpread();
const currentRightPhoto = photos[spread + 1];
const nextLeftPhoto = photos[nextSpread];
const previousRightPhoto = photos[spread - 1];
if (direction > 0) {
setImage(turnFront, currentRightPhoto);
setImage(turnBack, nextLeftPhoto);
turnPage.classList.remove("is-turning-prev");
rightPage.classList.add("is-animating");
// A kezdőállapot visszaállítása az új animációhoz.
turnPage.style.transition = "none";
turnPage.style.transform = "rotateY(0deg)";
void turnPage.offsetWidth;
turnPage.style.removeProperty("transition");
turnPage.style.removeProperty("transform");
turnPage.classList.add("is-turning-next");
} else {
setImage(turnFront, currentRightPhoto);
setImage(turnBack, previousRightPhoto);
turnPage.classList.remove("is-turning-next");
rightPage.classList.add("is-animating");
turnPage.style.transition = "none";
turnPage.style.transform = "rotateY(0deg)";
void turnPage.offsetWidth;
turnPage.style.removeProperty("transition");
turnPage.style.removeProperty("transform");
turnPage.classList.add("is-turning-prev");
}
const token = ++animationToken;
// Biztonsági lezárás arra az esetre is, ha a
// transitionend esemény nem fut le.
animationTimer = window.setTimeout(() => {
finishTurn(token, nextSpread);
}, duration + 100);
}
nextButton.addEventListener("click", () => turn(1));
prevButton.addEventListener("click", () => turn(-1));
book.addEventListener("keydown", (event) => {
if (event.key === "ArrowRight") {
event.preventDefault();
turn(1);
} else if (event.key === "ArrowLeft") {
event.preventDefault();
turn(-1);
}
});
turnPage.addEventListener("transitionend", (event) => {
if (
event.target !== turnPage ||
event.propertyName !== "transform" ||
!isAnimating
) {
return;
}
const nextSpread =
turnPage.classList.contains("is-turning-next")
? spread + 2
: spread - 2;
finishTurn(animationToken, nextSpread);
});
// Induló állapot.
resetTurnPage();
renderSpread();
});