Exemples d'observateurs JavaScript





Un observateur est un objet qui surveille l'état d'un élément spécifique et en enregistre les modifications. L'élément surveillé (j'ai presque écrit "pour lequel la surveillance est organisée") s'appelle la cible. Un observateur peut surveiller l'état d'un ou plusieurs éléments, et dans certains cas également les descendants de l'élément cible.



Il existe trois principaux types d'observateurs en JavaScript:



  1. ResizeObserver
  2. IntersectionObserver
  3. MutationObserver


Dans cet article, je propose de me concentrer sur la mise en œuvre pratique de chaque observateur.



Redimensionner l'observateur



Rendez-vous


Regarder le redimensionnement de l'élément cible.



Théorie


MDN

Mon article sur Habré



Soutien






Exemple


Dans l'exemple suivant, nous observons la largeur du conteneur avec l'ID "box". Lorsque la largeur du conteneur est inférieure à 768px, nous changeons la couleur d'arrière-plan du conteneur et la couleur du texte (à l'opposé avec "filter: invert (100%)"), diminuons la taille de la police de l'en-tête et du corps du texte, et masquons les informations supplémentaires.



Le balisage ressemble à ceci:



<div id="box" class="box">
  <h1 id="title" class="title">Some Awesome Title</h1>
  <p id="text" class="text">Some Main Text</p>
  <span id="info" class="info">Some Secondary Info</span>
</div>


Modes:



* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
.box,
.title,
.text,
.info {
  transition: 0.3s;
}
.box {
  background: #ddd;
  color: #222;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
}
.title,
.info {
  margin: 1rem;
}
.title {
  font-size: 2rem;
}
.text {
  font-size: 1.25rem;
}


Scénario:



//     
const changeStyles = (elements, properties, values) =>
  elements.forEach((element, index) => {
    element.style[properties[index]] = values[index];
  });

//   ResizeObserver
const observer = new ResizeObserver((entries) => {
  //     (  )
  for (const entry of entries) {
    //    
    const width = entry.contentRect.width;

    //  
    //      768px
    //   
    if (width < 768) {
      changeStyles(
        [box, title, text, info],
        ["filter", "fontSize", "fontSize", "opacity"],
        ["invert(100%)", "1.5rem", "1rem", "0"]
      );
    } else {
      //      768px
      //  
      changeStyles(
        [box, title, text, info],
        ["filter", "fontSize", "fontSize", "opacity"],
        ["invert(0%)", "2rem", "1.25rem", "1"]
      );
    }
  }
});

//       "box"
observer.observe(box);


Bac à sable:





IntersectionObserver



Rendez-vous


Observer l'intersection de l'élément cible avec l'élément parent ou la fenêtre d'affichage de la page.



Théorie


MDN

Mon article sur Habré



Soutien






Exemple


Dans l'exemple suivant, nous surveillons toutes les sections d'une page et écrivons le numéro de la section actuelle (son identifiant) dans le stockage local. Ceci est fait pour que lorsque l'utilisateur retourne à la page, faites défiler la fenêtre jusqu'à la section où il s'est arrêté. Veuillez noter que l'exemple met en œuvre un défilement fluide: sur les pages contenant beaucoup d'informations, il est préférable de faire défiler instantanément.



Balisage:
<main id="main">
  <section id="1" class="section">
    <h3 class="title">First Section Title</h3>
    <p class="text">
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsam nostrum ex delectus distinctio reprehenderit facere vitae beatae ab dolores, aliquam maiores officia mollitia unde et! Quaerat odit in minus dolor corrupti nemo nam beatae. Ex consequatur rem laborum necessitatibus omnis, soluta fuga maiores repellendus eveniet? Blanditiis quae officiis maiores vitae nobis in voluptate, dicta voluptas rerum. Et laudantium consequuntur vitae tenetur doloremque accusantium tempora quos magni repudiandae voluptatem perferendis velit reprehenderit laborum libero soluta quis id, quidem assumenda nihil obcaecati expedita, aliquam suscipit nesciunt facere. Voluptate rem perferendis ab iste? Maxime, earum quos! Modi, aut quis nihil quidem accusamus vero sunt debitis architecto soluta repellendus fugit suscipit aspernatur labore a est sit dolores in necessitatibus ea tenetur corporis. Exercitationem mollitia impedit qui nemo voluptate numquam perspiciatis repellendus repellat a odio fugit dolor ducimus labore ex veritatis pariatur aliquam enim distinctio libero doloremque saepe quaerat consectetur, ut sapiente. Laboriosam dignissimos iure praesentium modi ab perferendis at molestias maiores suscipit, expedita aut aperiam nam voluptates similique optio minus quam! Voluptas ullam sunt, a officiis accusamus adipisci sed saepe voluptatem minima maxime est assumenda cum quibusdam voluptates provident in quasi vitae. Corrupti voluptatibus laborum ipsum quia, cupiditate adipisci assumenda dolores sunt distinctio, recusandae nesciunt aliquid, explicabo ullam eligendi perspiciatis rerum architecto? Cumque numquam blanditiis, magnam delectus velit laudantium aliquid quibusdam excepturi vero nihil necessitatibus, sed officiis, molestias hic autem modi consequuntur iusto sapiente dolore. Voluptates tenetur provident eius distinctio iure rerum minima eum eaque. Ea autem, deleniti atque magnam eius modi dicta assumenda tempore ducimus molestias. Aperiam enim tenetur, hic blanditiis velit quod odio deserunt sequi quisquam dignissimos animi amet magnam excepturi dicta quidem error quis officia natus. Temporibus nobis dolores veritatis eius illo quas perspiciatis reiciendis dolorum optio, commodi, animi quos at! Amet praesentium totam ab error esse optio quo, quis iusto!
    </p>
  </section>
  <section id="2" class="section">
    <h3 class="title">Second Section Title</h3>
    <p class="text">
      ...
    </p>
  </section>
  <section id="3" class="section">
    <h3 class="title">Third Section Title</h3>
    <p class="text">
      ...
    </p>
  </section>
</main>




Modes:



* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  background: #eee;
  color: #222;
  text-align: center;
}
main {
  max-width: 768px;
  margin: auto;
}
.section {
  padding: 1rem;
}
.title {
  font-size: 1.5rem;
  margin: 1rem;
}
.text {
  font-size: 1.25rem;
}


Scénario:



//    ,   
const findLastSection = () => {
  //      
  //   -   
  const number = localStorage.getItem("numberOfSection") || 1;

  //   
  const section = document.getElementById(number);

  //        (  )
  const position = Math.round(section.offsetTop);

  //      
  scrollTo({
    top: position,
    // 
    behavior: "smooth",
  });
};

findLastSection();

//      
const createObserver = () => {
  //   IntersectionObserver
  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        //       
        if (entry.isIntersecting) {
          //      
          localStorage.setItem("numberOfSection", entry.target.id);
        }
      });
    },
    {
      //       
      // 10%
      threshold: 0.1,
    }
  );

  //   
  const sections = main.querySelectorAll("section");
  //    
  sections.forEach((section) => observer.observe(section));
};

createObserver();


Bac à sable:





MutationObserver



Rendez-vous


Surveillez les changements dans les attributs, le contenu du texte de l'élément cible et ses descendants. Peut-être, en termes de fonctionnalité, est-ce le plus intéressant des observateurs que nous considérons.



Théorie


MDN

Un didacticiel JavaScript moderne



Soutien






Exemple


Dans l'exemple suivant, nous allons implémenter une astuce simple, dans laquelle l'observateur garde une trace du nombre de tâches sur la liste. En termes de fonctionnalité, notre observateur sera similaire à "useEffect" dans React.js ou à "watch" dans Vue.js.



Balisage:



<div id="box" class="box"></div>


Modes:
@import url("https://fonts.googleapis.com/css2?family=Stylish&display=swap");

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: stylish;
  font-size: 1rem;
  color: #222;
}

.box {
  max-width: 512px;
  margin: auto;
  text-align: center;
}

.counter {
  font-size: 2.25rem;
  margin: 0.75rem;
}

.form {
  display: flex;
  margin-bottom: 0.25rem;
}

.input {
  flex-grow: 1;
  border: none;
  border-radius: 4px;
  box-shadow: 0 0 1px inset #222;
  text-align: center;
  font-size: 1.15rem;
  margin: 0.5rem 0.25rem;
}

.input:focus {
  outline-color: #5bc0de;
}

.btn {
  border: none;
  outline: none;
  background: #337ab7;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.5);
  color: #eee;
  margin: 0.5rem 0.25rem;
  cursor: pointer;
  user-select: none;
  width: 92px;
  text-shadow: 0 0 1px rgba(0, 0, 0, 0.5);
}

.btn:active {
  box-shadow: 0 0 1px rgba(0, 0, 0, 0.5) inset;
}

.btn.danger {
  background: #d9534f;
}

.list {
  list-style: none;
}

.item {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
}

.item + .item {
  border-top: 1px dashed rgba(0, 0, 0, 0.5);
}

.text {
  flex: 1;
  font-size: 1.15rem;
  margin: 0.5rem;
  padding: 0.5rem;
  background: #eee;
  border-radius: 4px;
}




Scénario:



// 
const todos = [
  {
    id: "1",
    text: "Learn HTML",
  },
  {
    id: "2",
    text: "Learn CSS",
  },
  {
    id: "3",
    text: "Learn JavaScript",
  },
  {
    id: "4",
    text: "Stay alive",
  },
];

//   
const Item = (todo) => `
<li
  class="item"
  id="${todo.id}"
>
  <span class="text"}">
    ${todo.text}
  </span>
  <button
    class="btn danger"
    data-type="delete"
  >
    Delete
  </button>
</li>
`;

//  
const Template = `
<form id="form" class="form">
    <input
      type="text"
      class="input"
      id="input"
    >
    <button
      class="btn"
      data-type="add"
    >
      Add
    </button>
</form>
<ul id="list" class="list">
    ${todos.reduce(
      (html, todo) =>
        (html += `
          ${Item(todo)}
        `),
      ""
    )}
</ul>
`;

//    IIFE
(() => {
  //       "box"
  box.innerHTML = `
  <h1 id="counter" class="counter">
    ${todos.length} todo(s) left
  </h1>
  ${Template}
`;

  //      
  input.focus();

  //   MutationObserver
  const observer = new MutationObserver(() => {
    //     
    const count = todos.length;

    //    ,   ,   ,    
    counter.textContent = `
    ${count > 0 ? `${count} todo(s) left` : "There are no todos"}
  `;
  });

  //        
  observer.observe(list, {
    childList: true,
  });

  //      
  const addTodo = () => {
    if (!input.value.trim()) return;

    const todo = {
      id: Date.now().toString().slice(-4),
      text: input.value,
    };

    list.insertAdjacentHTML("beforeend", Item(todo));

    todos.push(todo);

    input.value = "";
    input.focus();
  };

  //     
  const deleteTodo = (item) => {
    const index = todos.findIndex((todo) => todo.id === item.id);

    item.remove();

    todos.splice(index, 1);
  };

  //     
  form.onsubmit = (e) => e.preventDefault();

  //   
  box.addEventListener("click", (e) => {
    if (e.target.tagName !== "BUTTON") return;

    //      
    const { type } = e.target.dataset;
    const item = e.target.parentElement;

    //          
    switch (type) {
      case "add":
        addTodo();
        break;
      default:
        deleteTodo(item);
        break;
    }
  });
})();


Bac à sable:





J'espère que vous avez trouvé quelque chose d'intéressant pour vous-même. Merci de votre attention.



All Articles