| Autor | SHA1 | Wiadomość | Data |
|---|---|---|---|
|
|
ef705874b1 | [evo4532] 202601021003 | 1 tydzień temu |
|
|
0dbccd74de | 202512010737 | 1 miesiąc temu |
|
|
12df9f1274 | 202511261413 | 1 miesiąc temu |
|
|
69b9d98a7e | 202511260955 | 1 miesiąc temu |
|
|
3a562b6d26 | 202511260822 | 1 miesiąc temu |
|
|
3870cb21ee | 202511260802 | 1 miesiąc temu |
|
|
e5b744ca74 | 202511260751 | 1 miesiąc temu |
|
|
ddaccfed78 | 202511232102 | 1 miesiąc temu |
|
|
72c551a82d | 202511232100 | 1 miesiąc temu |
|
|
525939e44b | 202511221144 | 1 miesiąc temu |
| @ -0,0 +1,220 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="fr"> | |||||
| <head> | |||||
| <title>template ZONES</title> | |||||
| <meta name="description" content="PROTO"> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||||
| <meta charset="utf-8"> | |||||
| <link rel="preload" as="font" type="../font/woff2" href="fonts/Fonte-Texte.woff2" crossorigin /> | |||||
| <link rel="preload" as="font" type="../font/woff2" href="fonts/Fonte-Titre.woff2" crossorigin /> | |||||
| <link rel="stylesheet" type="text/css" href="styles.css"> | |||||
| </head> | |||||
| <body> | |||||
| <section id="header">HEADER</section> | |||||
| <section id="navbar">NAVBAR</section> | |||||
| <section id="synthese">SYNTHESE</section> | |||||
| <section id="principale">PRINCIPALE</section> | |||||
| <section id="footer">FOOTER</section> | |||||
| </body> | |||||
| <template id="zone"> | |||||
| <div id="div_before_zone"></div> | |||||
| <div id="div_zone">zone</div> | |||||
| <div id="div_after_zone"></div> | |||||
| </template> | |||||
| <script src="log.js"></script> | |||||
| <script src="scroll.js"></script> | |||||
| <script src="zones.js"></script> | |||||
| <script> | |||||
| logMsgAddActiveSection('intersectionObserver'); | |||||
| logMsgAddActiveSection('htmlElement'); | |||||
| logMsgAddActiveSection('etatSection'); | |||||
| </script> | |||||
| <script> | |||||
| function addClassToElement(id,className){ | |||||
| const element=document.querySelector(id); | |||||
| if (element) element.classList.add(className); | |||||
| } | |||||
| function removeClassToElement(id,className){ | |||||
| const element=document.querySelector(id); | |||||
| if (element) element.classList.remove(className); | |||||
| } | |||||
| function initZoneElements(zone) { | |||||
| const element=document.querySelector('#div_'+zone); | |||||
| if (element) { | |||||
| element.innerHTML=zone; | |||||
| addClassToElement('#div_before_'+zone,'div_before_section'); | |||||
| addClassToElement('#div_'+zone,'div_section'); | |||||
| addClassToElement('#div_after_'+zone,'div_after_section'); | |||||
| } | |||||
| } | |||||
| function initZone(zone) { | |||||
| const sectionZone = document.querySelector('#'+zone); | |||||
| if (sectionZone) { | |||||
| const template = document.querySelector('#zone'); | |||||
| if (template) { | |||||
| const contenu = document.importNode(template.content, true); | |||||
| if (contenu) { | |||||
| sectionZone.innerHTML=''; | |||||
| contenu.childNodes.forEach((element) => {if (element.id) element.id=element.id.replace('zone',zone)}); | |||||
| sectionZone.appendChild(contenu); | |||||
| initZoneElements(zone); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| initZone('header'); | |||||
| initZone('navbar'); | |||||
| initZone('synthese'); | |||||
| initZone('principale'); | |||||
| initZone('footer'); | |||||
| addClassToElement('#div_header','header'); | |||||
| addClassToElement('#div_navbar','navbar'); | |||||
| addClassToElement('#div_synthese','synthese'); | |||||
| addClassToElement('#div_principale','principale'); | |||||
| addClassToElement('#div_footer','footer'); | |||||
| function etatSectionLog(message) { | |||||
| logMsg('etatSection', message); | |||||
| } | |||||
| const section_header=document.querySelector('#div_header'); | |||||
| const section_navbar=document.querySelector('#div_navbar'); | |||||
| const section_synthese=document.querySelector('#div_synthese'); | |||||
| const after_section_header=document.querySelector('#div_after_header'); | |||||
| const after_section_synthese=document.querySelector('#div_after_synthese'); | |||||
| function toggleElement(element) { | |||||
| if (element.style.display === "block") { | |||||
| element.style.display = "none"; | |||||
| } else { | |||||
| element.style.display = "block"; | |||||
| } | |||||
| } | |||||
| // ---- | |||||
| // ---- La page est une machine à états | |||||
| // ---- | |||||
| let currentEtatSection = 1; | |||||
| let maxEtat = 4; | |||||
| function etatSections(etat) { | |||||
| if (currentEtatSection == etat) return; | |||||
| currentEtatSection=etat; | |||||
| etatSectionLog('Etat courant '+currentEtatSection); | |||||
| switch (currentEtatSection) { | |||||
| case 1 : | |||||
| removeClassToElement('#div_navbar','navbar_fixed'); | |||||
| removeClassToElement('#div_before_synthese','div_before_synthese_when_navbar_fixed'); | |||||
| section_header.style.display = "block"; | |||||
| scrollToElement('#div_before_header'); | |||||
| break; | |||||
| case 2 : | |||||
| addClassToElement('#div_navbar','navbar_fixed'); | |||||
| addClassToElement('#div_before_synthese','div_before_synthese_when_navbar_fixed'); | |||||
| scrollToElement('#div_before_navbar'); | |||||
| section_synthese.style.display = "block"; | |||||
| break; | |||||
| case 3 : | |||||
| addClassToElement('#div_navbar','navbar_fixed'); | |||||
| addClassToElement('#div_before_synthese','div_before_synthese_when_navbar_fixed'); | |||||
| section_synthese.style.display = "none"; | |||||
| scrollToElement('#div_before_navbar'); | |||||
| break; | |||||
| default : | |||||
| break; | |||||
| } | |||||
| } | |||||
| function changeEtatSectionUp() { | |||||
| if (currentEtatSection != maxEtat) | |||||
| etatSections(currentEtatSection+1); | |||||
| } | |||||
| function changeEtatSectionDown(){ | |||||
| if (currentEtatSection != 1) | |||||
| etatSections(currentEtatSection-1); | |||||
| } | |||||
| after_section_header.addEventListener("click", function() { | |||||
| etatSectionLog('toggle section_header '); | |||||
| if (currentEtatSection!=1) etatSections(1); | |||||
| else etatSections(2); | |||||
| }); | |||||
| after_section_synthese.addEventListener("click", function() { | |||||
| etatSectionLog('toggle section_synthese '); | |||||
| if (currentEtatSection!=2) etatSections(2); | |||||
| else etatSections(2); | |||||
| }); | |||||
| function changeEtatSection(_visible) { | |||||
| if (!_visible) changeEtatSectionUp(); | |||||
| if (_visible) changeEtatSectionDown(); | |||||
| return true; | |||||
| } | |||||
| function beforeSection(_visible) { | |||||
| if (!_visible) changeEtatSectionUp(); | |||||
| } | |||||
| function afterSection(_visible) { | |||||
| if (_visible) changeEtatSectionDown(); | |||||
| } | |||||
| function beforeNavbarVisibility(_visible) { | |||||
| etatSectionLog('TRIGGER '+arguments.callee.name); | |||||
| beforeSection(_visible); | |||||
| } | |||||
| function afterNavbarVisibility(_visible) { | |||||
| etatSectionLog('TRIGGER '+arguments.callee.name); | |||||
| afterSection(_visible); | |||||
| } | |||||
| function beforeHeaderVisibility(_visible) { | |||||
| etatSectionLog('TRIGGER '+arguments.callee.name); | |||||
| beforeSection(_visible); | |||||
| } | |||||
| function afterHeaderVisibility(_visible) { | |||||
| etatSectionLog('TRIGGER '+arguments.callee.name); | |||||
| afterSection(_visible); | |||||
| } | |||||
| addIntersectionObserverEntry("div_before_header", beforeHeaderVisibility); | |||||
| addIntersectionObserverEntry("div_after_header", afterHeaderVisibility); | |||||
| addIntersectionObserverEntry("div_before_navbar", beforeNavbarVisibility); | |||||
| addIntersectionObserverEntry("div_after_navbar", afterNavbarVisibility); | |||||
| /* | |||||
| let yprev = window.pageYOffset; | |||||
| const scrollHandler = () => { | |||||
| const y = window.pageYOffset; | |||||
| const ymax = document.documentElement.scrollHeight - window.innerHeight; | |||||
| if (y > yprev) { | |||||
| changeEtatSectionUp(); | |||||
| } else { | |||||
| changeEtatSectionDown(); | |||||
| } | |||||
| yprev = Math.min(Math.max(y, 0), ymax); | |||||
| }; | |||||
| window.addEventListener('scroll', scrollThrottleCallback(scrollHandler, 1000)); | |||||
| */ | |||||
| </script> | |||||
| </html> | |||||
| @ -0,0 +1,14 @@ | |||||
| const logMsgActivesSections = new Set(); | |||||
| function logMsgAddActiveSection(sectionName) { | |||||
| logMsgActivesSections.add(sectionName); | |||||
| } | |||||
| function logMsgRemoveActiveSection(sectionName) { | |||||
| logMsgActivesSections.delete(sectionName); | |||||
| } | |||||
| function logMsg(section, message){ | |||||
| if (logMsgActivesSections.has(section)) | |||||
| console.log(section, ' : ', message) | |||||
| } | |||||
| @ -0,0 +1,182 @@ | |||||
| const scrollThrottleCallback = (callback, timeout) => { | |||||
| let wait = false; | |||||
| return () => { | |||||
| if (wait) return; | |||||
| callback.call(); | |||||
| wait = true; | |||||
| setTimeout(() => { wait = false; }, timeout); | |||||
| }; | |||||
| }; | |||||
| function getAbsolutePosition(element) { | |||||
| const rect = element.getBoundingClientRect(); | |||||
| return { | |||||
| top: rect.top + window.scrollY, // Add vertical scroll | |||||
| left: rect.left + window.scrollX, // Add horizontal scroll | |||||
| bottom: rect.bottom + window.scrollY, | |||||
| right: rect.right + window.scrollX | |||||
| }; | |||||
| } | |||||
| function getPositionRelativeToParent(element) { | |||||
| const parent = element.parentElement; | |||||
| const parentRect = parent.getBoundingClientRect(); | |||||
| const elementRect = element.getBoundingClientRect(); | |||||
| return { | |||||
| top: elementRect.top - parentRect.top, | |||||
| left: elementRect.left - parentRect.left, | |||||
| bottom: elementRect.bottom - parentRect.top, | |||||
| right: elementRect.right - parentRect.left | |||||
| }; | |||||
| } | |||||
| function smoothScrollToElement(element, offset = 0) { | |||||
| const elementPosition = getAbsolutePosition(element); | |||||
| const targetScroll = elementPosition.top - offset; | |||||
| // Smooth scroll with animation | |||||
| window.scrollTo({ | |||||
| top: targetScroll, | |||||
| behavior: 'smooth' | |||||
| }); | |||||
| } | |||||
| // Enhanced version with progress callback | |||||
| function scrollToElementWithProgress(element, offset = 0, onProgress) { | |||||
| const start = window.scrollY; | |||||
| const elementPosition = getAbsolutePosition(element); | |||||
| const target = elementPosition.top - offset; | |||||
| const distance = target - start; | |||||
| const duration = 1000; // ms | |||||
| const startTime = performance.now(); | |||||
| function animate(currentTime) { | |||||
| const elapsed = currentTime - startTime; | |||||
| const progress = Math.min(elapsed / duration, 1); | |||||
| // Easing function for smooth animation | |||||
| const easeProgress = 1 - Math.pow(1 - progress, 3); | |||||
| const currentPosition = start + (distance * easeProgress); | |||||
| window.scrollTo(0, currentPosition); | |||||
| if (onProgress) { | |||||
| onProgress(progress); | |||||
| } | |||||
| if (progress < 1) { | |||||
| requestAnimationFrame(animate); | |||||
| } | |||||
| } | |||||
| requestAnimationFrame(animate); | |||||
| } | |||||
| function scrollToElement(elementSelector) { | |||||
| const element = document.querySelector(elementSelector); | |||||
| if (element) { | |||||
| const methode=1; | |||||
| switch (methode) { | |||||
| case 1 : | |||||
| element.scrollIntoView(); | |||||
| break; | |||||
| case 2 : | |||||
| smoothScrollToElement(element); | |||||
| break; | |||||
| case 3 : | |||||
| scrollToElementWithProgress(element, 0, (progress) => { | |||||
| logMsg('htmlElement',`Scroll progress: ${Math.round(progress * 100)}%`); | |||||
| }); | |||||
| break; | |||||
| } | |||||
| } | |||||
| logMsg('htmlElement','scrollTo '+elementSelector); | |||||
| } | |||||
| function getAbsolutePosition(element) { | |||||
| const rect = element.getBoundingClientRect(); | |||||
| return { | |||||
| top: rect.top + window.scrollY, // Add vertical scroll | |||||
| left: rect.left + window.scrollX, // Add horizontal scroll | |||||
| bottom: rect.bottom + window.scrollY, | |||||
| right: rect.right + window.scrollX | |||||
| }; | |||||
| } | |||||
| function getPositionRelativeToParent(element) { | |||||
| const parent = element.parentElement; | |||||
| const parentRect = parent.getBoundingClientRect(); | |||||
| const elementRect = element.getBoundingClientRect(); | |||||
| return { | |||||
| top: elementRect.top - parentRect.top, | |||||
| left: elementRect.left - parentRect.left, | |||||
| bottom: elementRect.bottom - parentRect.top, | |||||
| right: elementRect.right - parentRect.left | |||||
| }; | |||||
| } | |||||
| function smoothScrollToElement(element, offset = 0) { | |||||
| const elementPosition = getAbsolutePosition(element); | |||||
| const targetScroll = elementPosition.top - offset; | |||||
| // Smooth scroll with animation | |||||
| window.scrollTo({ | |||||
| top: targetScroll, | |||||
| behavior: 'smooth' | |||||
| }); | |||||
| } | |||||
| // Enhanced version with progress callback | |||||
| function scrollToElementWithProgress(element, offset = 0, onProgress) { | |||||
| const start = window.scrollY; | |||||
| const elementPosition = getAbsolutePosition(element); | |||||
| const target = elementPosition.top - offset; | |||||
| const distance = target - start; | |||||
| const duration = 1000; // ms | |||||
| const startTime = performance.now(); | |||||
| function animate(currentTime) { | |||||
| const elapsed = currentTime - startTime; | |||||
| const progress = Math.min(elapsed / duration, 1); | |||||
| // Easing function for smooth animation | |||||
| const easeProgress = 1 - Math.pow(1 - progress, 3); | |||||
| const currentPosition = start + (distance * easeProgress); | |||||
| window.scrollTo(0, currentPosition); | |||||
| if (onProgress) { | |||||
| onProgress(progress); | |||||
| } | |||||
| if (progress < 1) { | |||||
| requestAnimationFrame(animate); | |||||
| } | |||||
| } | |||||
| requestAnimationFrame(animate); | |||||
| } | |||||
| function scrollToElement(elementSelector) { | |||||
| const element = document.querySelector(elementSelector); | |||||
| if (element) { | |||||
| const methode=1; | |||||
| switch (methode) { | |||||
| case 1 : | |||||
| element.scrollIntoView(); | |||||
| break; | |||||
| case 2 : | |||||
| smoothScrollToElement(element); | |||||
| break; | |||||
| case 3 : | |||||
| scrollToElementWithProgress(element, 0, (progress) => { | |||||
| logMsg('htmlElement',`Scroll progress: ${Math.round(progress * 100)}%`); | |||||
| }); | |||||
| break; | |||||
| } | |||||
| } | |||||
| logMsg('htmlElement','scrollTo '+elementSelector); | |||||
| } | |||||
| @ -0,0 +1,119 @@ | |||||
| :root { | |||||
| --dark-mode: 1; | |||||
| --r-sombre: 0; | |||||
| --g-sombre: 0; | |||||
| --b-sombre: 0; | |||||
| --r-clair: 245; | |||||
| --g-clair: 245; | |||||
| --b-clair: 240; | |||||
| --rgb-sombre: var(--r-sombre), var(--g-sombre), var(--b-sombre); | |||||
| --rgb-clair: var(--r-clair), var(--g-clair), var(--b-clair); | |||||
| --couleur-sombre: rgb(var(--rgb-sombre)); | |||||
| --couleur-clair: rgb(var(--rgb-clair)); | |||||
| --couleur-texte: black; | |||||
| --couleur-fond: var(--couleur-clair); | |||||
| --marker-before-height: 5px; | |||||
| --marker-after-height: 12px; | |||||
| --section-min-height: 80px; | |||||
| --section-header-height: 120px; | |||||
| --section-navbar-height: var(--section-min-height); | |||||
| --section-synthese-height: 120px; | |||||
| --section-principale-height: 400px; | |||||
| --section-footer-height: 100vh; | |||||
| } | |||||
| @font-face { | |||||
| font-family: 'Fonte-Texte'; | |||||
| font-display: fallback; | |||||
| /* Penser à bien héberger la font sur le même domaine que mon site */ | |||||
| src: url(../fonts/Fonte-Texte.woff2) format('woff2'); | |||||
| /* Réutiliser ici exactement la même liste unicode que ci-dessus */ | |||||
| unicode-range: U+20-5F, U+61-7A, U+7C, U+A0, U+A7, U+A9, U+AB, U+B2-B3, U+BB, U+C0, U+C2, U+C6-CB, | |||||
| U+CE-CF, U+D4, U+D9, U+DB-DC, U+E0, U+E2, U+E6-EB, U+EE-EF, U+F4, U+F9, U+FB-FC, U+FF, | |||||
| U+152-153, U+178, U+2B3, U+2E2, U+1D48-1D49, U+2010-2011, U+2013-2014, U+2019, U+201C-201D, | |||||
| U+2020-2021, U+2026, U+202F-2030, U+20AC, U+2212; | |||||
| } | |||||
| @font-face { | |||||
| font-family: 'Fonte-Titre'; | |||||
| font-display: fallback; | |||||
| /* Penser à bien héberger la font sur le même domaine que mon site */ | |||||
| src: url(../fonts/Fonte-Titre.woff2) format('woff2'); | |||||
| /* Réutiliser ici exactement la même liste unicode que ci-dessus */ | |||||
| unicode-range: U+20-5F, U+61-7A, U+7C, U+A0, U+A7, U+A9, U+AB, U+B2-B3, U+BB, U+C0, U+C2, U+C6-CB, | |||||
| U+CE-CF, U+D4, U+D9, U+DB-DC, U+E0, U+E2, U+E6-EB, U+EE-EF, U+F4, U+F9, U+FB-FC, U+FF, | |||||
| U+152-153, U+178, U+2B3, U+2E2, U+1D48-1D49, U+2010-2011, U+2013-2014, U+2019, U+201C-201D, | |||||
| U+2020-2021, U+2026, U+202F-2030, U+20AC, U+2212; | |||||
| } | |||||
| html { | |||||
| font-family: Fonte-Texte, monospace; | |||||
| width: 95%; | |||||
| padding-left: 0; | |||||
| padding-right: 0; | |||||
| margin: 0 auto; | |||||
| background: var(--couleur-fond); | |||||
| color: var(--couleur-texte); | |||||
| } | |||||
| body { | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| min-height:1000px; | |||||
| } | |||||
| .div_before_section { | |||||
| color: var(--couleur-texte); | |||||
| background-color: black; | |||||
| height: var(--marker-before-height); | |||||
| } | |||||
| .div_section { | |||||
| min-height: var(--section-min-height); | |||||
| } | |||||
| .div_after_section { | |||||
| color: var(--couleur-texte); | |||||
| background-color: red; | |||||
| height: var(--marker-after-height); | |||||
| } | |||||
| .header { | |||||
| height: var(--section-header-height); | |||||
| background-color: aqua; | |||||
| } | |||||
| .navbar { | |||||
| position: relative; | |||||
| height: var(--section-navbar-height); | |||||
| background-color: yellow; | |||||
| } | |||||
| .navbar_fixed { | |||||
| position: fixed; | |||||
| top: 0; | |||||
| width: 70%; | |||||
| } | |||||
| .div_before_synthese_when_navbar_fixed { | |||||
| height: calc(var(--section-navbar-height) - var(--marker-after-height) - var(--marker-before-height)); | |||||
| } | |||||
| .synthese { | |||||
| background-color: pink; | |||||
| height: var(--section-synthese-height); | |||||
| } | |||||
| .principale { | |||||
| height: var(--section-principale-height); | |||||
| } | |||||
| .footer { | |||||
| background-color: greenyellow; | |||||
| height: var(--section-footer-height); | |||||
| } | |||||
| @ -0,0 +1,54 @@ | |||||
| function intersectionObserverLog(message) { | |||||
| logMsg('intersectionObserver', message); | |||||
| } | |||||
| function intersectionObserverLogVisibility(_id, _visible) { | |||||
| if (_visible) { | |||||
| intersectionObserverLog(_id + ' is Visible'); | |||||
| } else { | |||||
| intersectionObserverLog(_id + ' is NOT Visible'); | |||||
| } | |||||
| } | |||||
| const intersectionObserverRegistry = new Object(); | |||||
| function setIntersectionObserverRegistry(parametre, valeur) | |||||
| { | |||||
| intersectionObserverRegistry[parametre] = valeur; | |||||
| } | |||||
| function getIntersectionObserverRegistry(parametre) | |||||
| { | |||||
| return intersectionObserverRegistry[parametre]; | |||||
| } | |||||
| function entryVisiblity(_id, _visible) { | |||||
| let entry=getIntersectionObserverRegistry(_id); | |||||
| if (entry) return entry(_visible); | |||||
| return intersectionObserverLogVisibility(_id, _visible) | |||||
| } | |||||
| // Create a function that will handle any intersection between some elements and the viewport. | |||||
| const handleIntersectionObserverEntries = function (entries) { | |||||
| entries.forEach(entry => { | |||||
| intersectionObserverLog(entry.target.id+' ratio '+entry.intersectionRatio); | |||||
| if (typeof entryVisiblity === 'function') entryVisiblity(entry.target.id, entry.isIntersecting); | |||||
| }); | |||||
| } | |||||
| const intersectionObserver = new IntersectionObserver(handleIntersectionObserverEntries,{ | |||||
| // Dès qu'il y a ne serait-ce qu'un pixel au dessus de la ligne rouge | |||||
| // => donc threshold = 1 | |||||
| threshold: 0, | |||||
| // On met la hauteur en pixel entre la fin de l'image et la ligne rouge | |||||
| // C'est négatif parce qu'on entre à l'intérieur de l'image. Si le nombre | |||||
| // était positif, alors la ligne rouge se retrouverait sous l'image | |||||
| rootMargin: '-1px 0px' | |||||
| //rootMargin: '2px 0px' | |||||
| }); | |||||
| function addIntersectionObserverEntry(_id, _func) | |||||
| { | |||||
| let item = document.querySelector('#'+_id); | |||||
| if ((item) && (intersectionObserver)) { | |||||
| setIntersectionObserverRegistry(_id, _func); | |||||
| intersectionObserver.observe(item); | |||||
| logMsg('intersectionObserver', 'Add entry for '+ _id); | |||||
| } | |||||
| } | |||||