décors mid-left au pied des colonnes avec parallax custom

This commit is contained in:
2026-06-05 16:12:30 +02:00
parent b52190eff6
commit 0f1e8bab18
3 changed files with 175 additions and 8 deletions

View File

@@ -954,6 +954,15 @@ main.main-login .login > div > div:not(.hidden) form .button:hover, main.main-lo
height: 22vh; height: 22vh;
} }
.col-decor {
display: none;
}
@media (min-width: 760px) {
.col-decor {
display: block;
}
}
/* PAGES */ /* PAGES */
.home_introduction { .home_introduction {
box-sizing: border-box; box-sizing: border-box;

View File

@@ -391,13 +391,19 @@
const asideProjectMenu = document.querySelector('#block-erabletheme-views-block-projets-block-1 .projets_list'); const asideProjectMenu = document.querySelector('#block-erabletheme-views-block-projets-block-1 .projets_list');
if (asideProjectMenu) { if (asideProjectMenu) {
for (let item of asideProjectMenu.children) { // Le titre du projet n'est plus rendu dans .projet_full depuis la
const itemTitle = item.innerText; // refonte (il a été remonté dans .page-header-outside, hors colonne
const contentPage = document.querySelector('.projet_full'); // blanche). On va chercher le titre courant à sa nouvelle place.
const contentPageTitle = contentPage.querySelector('h2')?.innerText; // Garde sur null + chaîne vide pour éviter l'exception qui plantait
// tout le behavior attach (et donc Rellax) sur /projets/<slug>.
if (itemTitle.toLowerCase() === contentPageTitle.toLowerCase()) { const headerOutside = document.querySelector('.layout-content .page-header-outside h2');
item.querySelector('a').classList.add('is-active'); const contentPageTitle = (headerOutside?.innerText || '').trim().toLowerCase();
if (contentPageTitle) {
for (let item of asideProjectMenu.children) {
const itemTitle = (item.innerText || '').trim().toLowerCase();
if (itemTitle === contentPageTitle) {
item.querySelector('a')?.classList.add('is-active');
}
} }
} }
} }
@@ -433,6 +439,133 @@
} }
} }
//
// Décors mid-left : 0, 1 ou 2 PNG empilés au pied gauche extérieur
// de chaque .fullpage interne (pages non-index). Tailles et gaps
// proportionnels à colH, bornés min/max. On n'affiche un motif
// que si la pile tient sous 50% colH (les deux) ou 30% (un seul),
// sinon la forme déborderait dans le contenu en haut.
// Position absolue calculée en px dans .layout-container ;
// bord droit du PNG calé sur bord gauche colonne via
// translateX(-100%). Parallax custom plus bas (sans Rellax).
// Visibilité responsive : _background.scss.
//
const layoutContainer = document.querySelector('.layout-container');
const colDecorState = []; // { img1, img2, col }
function clamp(min, val, max) { return Math.max(min, Math.min(val, max)); }
function colDecorMetrics(colH) {
return {
gapBottom: clamp(120, 0.08 * colH, 320),
gapBetween: clamp(60, 0.03 * colH, 180),
h2: clamp(180, 0.14 * colH, 480),
h1: clamp(220, 0.17 * colH, 580),
};
}
function createColDecorImg(idx) {
const img = document.createElement('img');
img.className = `col-decor col-decor-${idx}`;
img.setAttribute('aria-hidden', 'true');
img.setAttribute('alt', '');
img.src = `/themes/erabletheme/assets/new-bg-shapes/mid-left-${idx}.png`;
img.style.cssText = 'position:absolute;width:auto;pointer-events:none;z-index:0;transform:translateX(-100%);';
return img;
}
function setupColDecor() {
if (!layoutContainer) return;
layoutContainer.querySelectorAll('.col-decor').forEach(n => n.remove());
colDecorState.length = 0;
document.querySelectorAll('.fullpage:not(.large-container)').forEach(col => {
const img1 = createColDecorImg(1);
const img2 = createColDecorImg(2);
layoutContainer.appendChild(img1);
layoutContainer.appendChild(img2);
colDecorState.push({ img1, img2, col });
});
positionColDecor();
}
function positionColDecor() {
if (!colDecorState.length) return;
const lcRect = layoutContainer.getBoundingClientRect();
const lcTopAbs = lcRect.top + window.scrollY;
const lcLeftAbs = lcRect.left + window.scrollX;
for (const entry of colDecorState) {
const { img1, img2, col } = entry;
const r = col.getBoundingClientRect();
const colH = r.height;
const m = colDecorMetrics(colH);
const stack2H = m.gapBottom + m.h2;
const stack1H = stack2H + m.gapBetween + m.h1;
const showBoth = stack1H <= colH * 0.5;
const showOne = !showBoth && stack2H <= colH * 0.3;
img1.style.display = showBoth ? 'block' : 'none';
img2.style.display = (showBoth || showOne) ? 'block' : 'none';
entry.visible1 = showBoth;
entry.visible2 = showBoth || showOne;
if (!showBoth && !showOne) continue;
const colBottomAbs = r.bottom + window.scrollY;
const leftRel = (r.left + window.scrollX) - lcLeftAbs;
const top2 = colBottomAbs - m.gapBottom - m.h2;
img2.style.top = (top2 - lcTopAbs) + 'px';
img2.style.left = leftRel + 'px';
img2.style.height = m.h2 + 'px';
// Référence scroll : motif centré dans le viewport.
entry.scrollRef2 = top2 + m.h2 / 2 - window.innerHeight / 2;
if (showBoth) {
const top1 = top2 - m.gapBetween - m.h1;
img1.style.top = (top1 - lcTopAbs) + 'px';
img1.style.left = leftRel + 'px';
img1.style.height = m.h1 + 'px';
entry.scrollRef1 = top1 + m.h1 / 2 - window.innerHeight / 2;
}
}
// Premier rendu du parallax après (re)position.
applyColDecorParallax();
}
// Parallax custom (sans Rellax) : remontée subtile, amplitude bornée,
// proportionnelle au scroll relatif à la position d'ancrage. À 0.15
// de vitesse, pour 100px de scroll au-delà de la référence, le motif
// monte de 15px ; clamp à ±30px évite tout débordement.
const COL_DECOR_PARALLAX_SPEED = 0.15;
const COL_DECOR_PARALLAX_AMP = 30;
let colDecorRafPending = false;
function applyColDecorParallax() {
const scrollY = window.scrollY || document.body.scrollTop || 0;
for (const { img1, img2, visible1, visible2, scrollRef1, scrollRef2 } of colDecorState) {
if (visible2 && scrollRef2 != null) {
const dY = clamp(-COL_DECOR_PARALLAX_AMP,
-(scrollY - scrollRef2) * COL_DECOR_PARALLAX_SPEED,
COL_DECOR_PARALLAX_AMP);
img2.style.transform = `translate3d(0, ${dY}px, 0) translateX(-100%)`;
}
if (visible1 && scrollRef1 != null) {
const dY = clamp(-COL_DECOR_PARALLAX_AMP,
-(scrollY - scrollRef1) * COL_DECOR_PARALLAX_SPEED,
COL_DECOR_PARALLAX_AMP);
img1.style.transform = `translate3d(0, ${dY}px, 0) translateX(-100%)`;
}
}
}
function onColDecorScroll() {
if (colDecorRafPending) return;
colDecorRafPending = true;
requestAnimationFrame(() => {
colDecorRafPending = false;
applyColDecorParallax();
});
}
if (!document.body.dataset.colDecorScrollWired) {
document.body.addEventListener('scroll', onColDecorScroll, { passive: true });
document.body.dataset.colDecorScrollWired = 'true';
}
// //
// Décors latéraux parallax v2. // Décors latéraux parallax v2.
// Cloner le .decor-tile autant de fois que nécessaire pour couvrir // Cloner le .decor-tile autant de fois que nécessaire pour couvrir
@@ -472,15 +605,30 @@
rellax = new Rellax('.bgImg', { wrapper: 'body' }); rellax = new Rellax('.bgImg', { wrapper: 'body' });
} }
// Init initial : frises latérales (basées sur layout-container,
// mesure stable même sans images chargées) + Rellax. Les col-decor
// sont injectés et positionnés UNIQUEMENT au window.load — leur
// position dépend de getBoundingClientRect() de .fullpage qui n'est
// fiable qu'une fois toutes les images du contenu chargées (sinon
// Rellax cache un blockTop périmé et le motif dérive vers le bas
// sur les pages longues).
setupBackgroundTiles(); setupBackgroundTiles();
initRellax(); initRellax();
// Resize debouncé : recalcule le nombre de tiles, repart Rellax propre. window.addEventListener('load', () => {
setupBackgroundTiles();
setupColDecor();
initRellax();
});
// Resize debouncé : recalcule le nombre de tiles + col-decor,
// repart Rellax propre.
let bgResizeTimer = null; let bgResizeTimer = null;
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
clearTimeout(bgResizeTimer); clearTimeout(bgResizeTimer);
bgResizeTimer = setTimeout(() => { bgResizeTimer = setTimeout(() => {
setupBackgroundTiles(); setupBackgroundTiles();
setupColDecor();
initRellax(); initRellax();
}, 200); }, 200);
}); });

View File

@@ -63,3 +63,13 @@ $tile_height: 220vh;
.decor-right-13 { top: 92%; height: 22vh; } .decor-right-13 { top: 92%; height: 22vh; }
} }
} }
// Décors mid-left au pied gauche extérieur des .fullpage internes.
// Injection et positionnement en JS (cf. setupColDecor dans
// erabletheme.js) ; on gère uniquement la visibilité responsive ici.
.col-decor {
display: none;
@media (min-width: $breakpoint_tablet) {
display: block;
}
}