JavaScript观察者示例





观察者是监视特定元素状态并在其中记录更改的对象。被监视的元素(我几乎写了“为其组织监视”)被称为目标。观察者可以监视一个或多个元素的状态,在某些情况下还可以监视目标元素的后代。



JavaScript中主要有三种观察者:



  1. ResizeObserver
  2. 交叉口观察者
  3. 突变观察者


在本文中,我建议重点关注每个观察者的实际实现。



调整观察者大小



约定


观看目标元素的大小调整。



理论


MDN

我在Habré上的文章



支持








在下面的示例中,我们观察ID为“ box”的容器的宽度。当容器的宽度小于768px时,我们将更改容器的背景颜色和文本的颜色(与“ filter:invert(100%)”相反),减小标题和正文的字体大小,并隐藏其他信息。



标记如下所示:



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


样式:



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


脚本:



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


沙箱:





交叉口观察者



约定


观看目标元素与父元素或页面视口(视口)的交集。



理论


MDN

我在Habré上的文章



支持








在下面的示例中,我们观看页面上的所有部分并将当前部分编号(其标识符)写入本地存储。这样做是为了在用户返回页面时,将视口滚动到他停止的部分。请注意,该示例实现了平滑滚动:在具有大量信息的页面上,最好立即滚动。



标记:
<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>




样式:



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


脚本:



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


沙箱:





突变观察者



约定


注意属性,目标元素及其后代的文本内容的更改。就功能而言,也许这是我们正在考虑的观察者中最有趣的。



理论


MDN

现代JavaScript教程



支持








在下面的示例中,我们将实现一个简单的技巧,其中观察者跟踪列表上的任务数量。在功能方面,我们的观察者将类似于React.js中的“ useEffect”或Vue.js中的“ watch”。



标记:



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


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




脚本:



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


沙箱:





希望您发现自己感兴趣的东西。感谢您的关注。



All Articles