11521 sujets
JavaScript, DOM et API Web HTML5
Comme je te l'ai indiqué, ça n'a rien à voir.
Un observer comme son nom l'indique : observe. Son rôle est de mettre à jour les éléments observés. getBoundingClientRect indique les propriétés d'un élément.
Lors d'un scroll et de l'utilisation de intersectionoberver, tu observeras les observables et en fonction de la propriété du scroll et des propriétés de l'observable, tu feras une action.
Pour te donner une idée, je viens de faire un Observer en vanilla. J'ai fait simple. Ça devrait être mieux élaborer. Il faudrait faire un Mediator. Mais c'est pour l'exemple et ne pas trop t'embrouiller). Je t'invite à déboguer. Si tu débogues, tu comprendras la différence. Le principe d'un observer (c'est un design pattern de comportement) est simple :
- il y a un sujet
- il y a des abonnés
Quand le sujet envoie une information, les abonnés sont au courant des données envoyées par le sujet. (Comme la parution d'un journal avec des abonnés )
le html :
le js :
Modifié par niuxe (25 Sep 2024 - 01:08)
Un observer comme son nom l'indique : observe. Son rôle est de mettre à jour les éléments observés. getBoundingClientRect indique les propriétés d'un élément.
Lors d'un scroll et de l'utilisation de intersectionoberver, tu observeras les observables et en fonction de la propriété du scroll et des propriétés de l'observable, tu feras une action.
Pour te donner une idée, je viens de faire un Observer en vanilla. J'ai fait simple. Ça devrait être mieux élaborer. Il faudrait faire un Mediator. Mais c'est pour l'exemple et ne pas trop t'embrouiller). Je t'invite à déboguer. Si tu débogues, tu comprendras la différence. Le principe d'un observer (c'est un design pattern de comportement) est simple :
- il y a un sujet
- il y a des abonnés
Quand le sujet envoie une information, les abonnés sont au courant des données envoyées par le sujet. (Comme la parution d'un journal avec des abonnés )
le html :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.8.1/dist/css/foundation-float.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.8.1/dist/css/foundation-prototype.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.8.1/dist/css/foundation-rtl.min.css" crossorigin="anonymous">
<style>
html, body{
min-height: 100vh;
scroll-behavior: smooth;
}
div{
height: 100vh;
background: #2ecc71;
}
#_2{
background: #3498db;
}
#_3{
background: #9b59b6;
}
#_4{
background: #e74c3c;
}
nav{
position: fixed;
top: 10px;
left: 10px;
padding: 25px;
background: #fff;
border: 1px solid #ccc;
border-radius: 5px;
}
nav .button{
margin: 0;
}
</style>
</head>
<body>
<main class="grid-container">
<nav>
<a href="#_1" class="button">div 1</a>
<a href="#_2" class="button">div 2</a>
<a href="#_3" class="button">div 3</a>
<a href="#_4" class="button">div 4</a>
</nav>
<div id="_1"> </div>
<div id="_2"> </div>
<div id="_3"> </div>
<div id="_4"> </div>
</main>
<script src="app.js"></script>
</body>
</html>
le js :
class Observer{
constructor(){
this._observers = []
}
update(scroll_y){
this._observers.forEach(o =>{
o.update(scroll_y)
})
}
add(el){
this._observers = [...this._observers, el]
}
remove(el){
this._observers = this._observers.filter(o => el !== o)
}
}
class Element{
constructor(el){
this._el = el
this._link = document.querySelector(`nav a[href='#${el.id}']`)
}
update(scroll_y){
let props = this._el.getBoundingClientRect(),
offsetTop = this._el.offsetTop,
cls = 'alert',
method = offsetTop - 5 < scroll_y && offsetTop + props.height - 5 > scroll_y ? 'add': 'remove'
this._link.classList[method](cls)
}
}
(()=>{
let observer = new Observer()
document.querySelectorAll('div').forEach(el => observer.add(new Element(el)))
for(let event of ['DOMContentLoaded', 'scroll']){
window.addEventListener(event, e => observer.update(window.scrollY))
}
})()
Modifié par niuxe (25 Sep 2024 - 01:08)
niuxe a écrit :
Comme je te l'ai indiqué, ça n'a rien à voir.
Oui et en même temps, de mémoire : avant que l'API Intersection Observer n'existe, les dev's ne disposaient que de getBoundingClientRect() et maltraitaient avec plus ou moins d'adresse cette fonction native pour faire des détections d'éléments HTML dans une page web (axe y) ***. Ça posait des problèmes de performance et de scope, problèmes qui prenaient une dimension exponentielle en regard du nombre d'éléments à observer.
Avec Intersection Observer on peut observer un élément HTML puis arrêter de le suivre de manière bien plus propre que ce qui pouvait se faire auparavant. Par exemple :
// Utilisez l'Intersection Observer pour observer l'élément et ajouter la classe d'animation lorsqu'il entre dans le viewport.
document.addEventListener('DOMContentLoaded', function () {
const targetElement = document.getElementById('targetElement');
const observerOptions = {
root: null, // null signifie que l'observation se fait par rapport au viewport
rootMargin: '0px',
threshold: 0.5 // 50% de l'élément doit être visible pour déclencher l'observation
};
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target); // arrêter d'observer une fois l'animation déclenchée
}
});
}, observerOptions);
observer.observe(targetElement);
});
Et maintenant voici un exemple amusant car j'utilise les deux fonctionnalités susnommées dans un même script, chacune des deux résolvant un problème spécifique, les curieux irons voir : https://github.com/Scriptura/scriptura.github.io/blob/master/scripts/development/more/svgAnimation.js
___
*** Dans le même ordre d'idée les devs se servaient de getBoundingClientRect() pour calculer la position d'un curseur par rapport au côté gauche de la page (x). Heureusement maintenant nous disposons nativement de l'élément HTML5 range et nous n'avons plus qu'à faire quelque-chose comme `input.value` pour récupérer la valeur.
Modifié par Olivier C (25 Sep 2024 - 01:00)
Olivier C a écrit :
Oui et en même temps, de mémoire : avant que l'API Intersection Observer n'existe, les dev's ne disposaient que de getBoundingClientRect() et maltraitaient avec plus ou moins d'adresse cette fonction native pour faire des détections d'éléments HTML dans une page web (axe y) ***. Ça posait des problèmes de performance et de scope, problèmes qui prenaient une dimension exponentielle en regard du nombre d'éléments à observer.
Je ne suis pas tout à fait d'accord avec toi (mais tout dépend du code final afin d'éviter les fuites mémoires). Si par hasard tu as des exemples
Il est à noter que mon code précédent n'est pas à utiliser en prod. Il va exploser les perfs si le nombre d'observable est conséquent. Le code ci dessous, c'est la même chose. On peut vite exploser les perfs. La base est là, mais faut améliorer ça.
Pour avoir un résultat similaire avec un intersectionObserver (le html est plus haut) ¹ :
(()=>{
let optionObserver = {
rootMargin: "-50% 0px"
},
observer = new IntersectionObserver(entries =>{
for(let entry of entries){
if(entry.isIntersecting){
document.querySelectorAll('nav a').forEach(a => a.classList.remove('alert'))
document.querySelector(`nav a[href='#${entry.target.id}']`).classList.add('alert')
}
}
}, optionObserver)
document.querySelectorAll('div').forEach(el => observer.observe(el))
})()
________
¹ Personnellement, je ne suis pas fan de IntersectionObserver. Tu as moins de contrôle sur les observés puisque le contexte est généralisé. Pour un scrollspy ou un scroll infinie, c'est suffisant. Mais pour quelque chose de plus précis sur chaque élément, ça va être vite un enfer à gérer. Je préfère mutationObserver comme observer natif en JS. En outre, il est pour un autre cas d'utilisation.
Modifié par niuxe (25 Sep 2024 - 02:32)
niuxe a écrit :
Je ne suis pas tout à fait d'accord avec toi (mais tout dépend du code final afin d'éviter les fuites mémoires). Si par hasard tu as des exemples
Ma considération sur les perfs n'est pas due à des tests, c'est de la littérature ; j'ai retrouvé la ref' d'où je tire cette conclusion : https://developer.mozilla.org/fr/docs/Web/API/Intersection_Observer_API
niuxe a écrit :
Il est à noter que mon code précédent n'est pas à utiliser en prod. Il va exploser les perfs si le nombre d'observable est conséquent.
Justement, le code proposé dans mon premier post est censé gérer ce problème ; notamment grâce à cette ligne :
observer.unobserve(entry.target)
Pour exemple de démo, voici une page web exposant des SVG animés de ci de là et qui exploite cette solution : https://scriptura.github.io/page/components.html
Pour voir tout le contexte le script est ici : Github, sinon voici l'extrait qui nous intéresse :
/**
* Fonction principale pour gérer la visibilité des SVG et déclencher les animations.
* Appelée par l'IntersectionObserver lorsque les éléments SVG deviennent visibles ou invisibles.
*
* @param {IntersectionObserverEntry[]} entries - Les entrées de l'observateur contenant les éléments SVG.
* @param {IntersectionObserver} observer - L'instance de l'IntersectionObserver utilisée pour observer les SVG.
*/
async function handleSvgVisibility(entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const svg = entry.target
const paths = svg.querySelectorAll('path')
if (!svg.classList.contains('active')) {
manageSvgClasses(svg, 'activate')
const initialAttributesList = [...paths].map(setSvgAnimationAttributes)
function handleAnimationEnd() {
restoreSvgAttributes(initialAttributesList)
manageSvgClasses(svg, 'deactivate')
svg.removeEventListener('animationend', handleAnimationEnd)
observer.unobserve(svg) // Arrêter d'observer cet élément une fois l'animation terminée
}
svg.addEventListener('animationend', handleAnimationEnd)
}
}
})
}
Modifié par Olivier C (26 Sep 2024 - 13:46)