JavaScriptオブザーバーの例





オブザーバーは、特定の要素の状態を監視し、その要素の変化を登録するオブジェクトです。監視されている要素(私はほとんど「監視が組織されている」と書いています)はターゲットと呼ばれます。オブザーバーは、1つ以上の要素の状態を監視でき、場合によっては、ターゲット要素の子孫も監視できます。



JavaScriptには、主に3つのタイプのオブザーバーがあります。



  1. ResizeObserver
  2. IntersectionObserver
  3. MutationObserver


この記事では、各オブザーバーの実際の実装に焦点を当てることを提案します。



オブザーバーのサイズ変更



予定


ターゲット要素のサイズ変更を監視します。



理論


MDN

ハブレに関する私の記事



サポート








次の例では、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);


サンドボックス:





IntersectionObserver



予定


ターゲット要素と親要素またはページビューポート(ビューポート)との交差を監視します。



理論


MDN

ハブレに関する私の記事



サポート








次の例では、ページ上のすべてのセクションを監視し、現在のセクション番号(その識別子)をローカルストレージに書き込みます。これは、ユーザーがページに戻ったときに、ビューポートを中断したセクションまでスクロールするために行われます。この例ではスムーズなスクロールが実装されていることに注意してください。情報が多いページでは、すぐにスクロールすることをお勧めします。



マークアップ:
<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();


サンドボックス:





MutationObserver



予定


ターゲット要素とその子孫の属性、テキストコンテンツの変更に注意してください。おそらく、機能の観点から、これは私たちが検討しているオブザーバーの中で最も興味深いものです。



理論


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