Ejemplos de observadores de JavaScript





Un observador es un objeto que monitorea el estado de un elemento específico y registra los cambios que ocurren en él. El elemento que se está monitoreando (casi escribí "para el cual se organiza la vigilancia") se llama objetivo. Un observador puede monitorear el estado de uno o más elementos y, en algunos casos, también los descendientes del elemento objetivo.



Hay tres tipos principales de observadores en JavaScript:



  1. ResizeObserver
  2. IntersectionObserver
  3. MutationObserver


En este artículo, propongo centrarme en la implementación práctica de cada observador.



Cambiar el tamaño del observador



Cita


Viendo el cambio de tamaño del elemento de destino.



Teoría


MDN

Mi artículo sobre Habré



Apoyo






Ejemplo


En el siguiente ejemplo, observamos el ancho del contenedor con el ID "box". Cuando el ancho del contenedor es menor a 768px, cambiamos el color de fondo del contenedor y el color del texto (al contrario con "filter: invert (100%)"), reducimos el tamaño de fuente del encabezado y el cuerpo del texto, y también ocultamos información adicional.



El marcado se ve así:



<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>


Estilos:



* {
  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;
}


Guión:



//     
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);


Salvadera:





IntersectionObserver



Cita


Observar la intersección del elemento de destino con el elemento principal o la ventana gráfica de la página.



Teoría


MDN

Mi artículo sobre Habré



Apoyo






Ejemplo


En el siguiente ejemplo, observamos todas las secciones de la página y escribimos el número de sección actual (su identificador) en el almacenamiento local. Esto se hace para que cuando el usuario regrese a la página, desplace la ventana gráfica hasta la sección donde la dejó. Tenga en cuenta que el ejemplo implementa un desplazamiento suave: en páginas con mucha información, es mejor desplazarse instantáneamente.



Margen:
<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>




Estilos:



* {
  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;
}


Guión:



//    ,   
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();


Salvadera:





MutationObserver



Cita


Esté atento a los cambios en los atributos, el contenido del texto del elemento de destino y sus descendientes. Quizás, en términos de funcionalidad, este sea el más interesante de los observadores que estamos considerando.



Teoría


MDN

Un tutorial de JavaScript moderno



Apoyo






Ejemplo


En el siguiente ejemplo, implementaremos un truco simple, en el que el observador realiza un seguimiento del número de tareas en la lista. En términos de funcionalidad, nuestro observador será similar a "useEffect" en React.js o "watch" en Vue.js.



Margen:



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


Estilos:
@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;
}




Guión:



// 
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;
    }
  });
})();


Salvadera:





Espero que hayas encontrado algo interesante para ti. Gracias por su atención.



All Articles