11521 sujets

JavaScript, DOM et API Web HTML5

Tout est dans la question...

Y en a t-il un qui supplante l'autre?
Font ils la même chose?
Comment en choisir un par rapport à l'autre?

J'arrive pas à trancher...
Modérateur
Salut,
getBoundingClientRect() et intersection observer, ça n'a rien à voir. Un donne la position x, y, sa largeur et hauteur et l'autre est un observer.

Que veux tu faire ?
Modérateur
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 Smiley cligne )


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">&nbsp;</div>
        <div id="_2">&nbsp;</div>
        <div id="_3">&nbsp;</div>
        <div id="_4">&nbsp;</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)
Modérateur
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)