Lullaby
Lullaby

Reputation: 21

Trigger a custom Event when element in viewport - JavaScript

I need to link these two codes so that when the "isElementInViewport" function returns true to trigger the "inviewport" event:

let inViewportEvent = document.createEvent("Event");
inViewportEvent.initEvent("inviewport", true, true);

document.addEventListener("inviewport", (e) => {
    console.log(`${e.target} in viewport!`)
}, false);

document.dispatchEvent(inViewportEvent);

and this function to detect if a element are in viewport:

const isElementInViewport = el => {
    let rect = el.getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
}

Upvotes: 1

Views: 2084

Answers (1)

Nalin Ranjan
Nalin Ranjan

Reputation: 1782

We must use CustomEvent constructor to create a new custom event, and then use dispatchEvent to trigger it...

const inViewportCustomEvent = new CustomEvent('inviewport', {
  detail: { 
    /* put the data to be carried here*/
  }
});
document.dispatchEvent(inViewportCustomEvent);

Illustration

const isElementInViewport = (el) => {

  let rect = el.getBoundingClientRect();
  // console.log(rect);
  const isIn = (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  );
  console.log('[isElementInViewport]', isIn, {
    top: rect.top,
    right: rect.right,
    left: rect.left,
    bottom: rect.bottom,
    innerHeight: window.innerHeight,
    clientHeight: document.documentElement.clientHeight,
    innerWidth: window.innerWidth,
    clientWidth: document.documentElement.clientWidth
  });
  return isIn;
};

const observer_callback = (entries, observer) => {
  entries.filter(entry => isElementInViewport(entry.target))
    .forEach(entry => {
      console.log("We got something");
      const inViewportCustomEvent = new CustomEvent('inviewport', {
        detail: {
          target: entry
          /* put the data to be carried here*/
        }
      });
      document.dispatchEvent(inViewportCustomEvent);
    });
};

function create_observer() {
  let options = {
    root: document,
    rootMargin: "5px"
  };

  return new IntersectionObserver(observer_callback, options);
}

const outermost = document.querySelector("#outermost");

const fill_counter = document.querySelector("#fill_counter");

const filler = document.querySelector("#filler");

const fill_clearer = document.querySelector("#fill_clearer");

const highlighted_index = document.querySelector("#highlighted_index");

function fill_squares(num_squares) {

  outermost.append(...new Array(num_squares).fill(0).map((_, index) => create_square(index)));

  function create_square(index) {
    const divEle = document.createElement("div");
    divEle.setAttribute("data-index", index);
    divEle.classList.add("square");

    divEle.onclick = (event) => {
      event.target.classList.toggle("square");
      event.target.classList.toggle("highlighted_square");
    }
    return divEle;
  }
}

function highlight_square(index) {
  const selected_square = outermost.querySelector(`[data-index="${index}"]`);
  if (selected_square) {
    if (!selected_square.classList.contains("highlighted_square")) {
      selected_square.classList.toggle("square");
      selected_square.classList.toggle("highlighted_square");
    }
  }
}

function highlight_squares(indexes) {
  if (indexes && indexes.length) {
    indexes.forEach(index => highlight_square(index));
  }
}

function clear_children(parentElement) {
  var e = parentElement;
  var first = e.firstElementChild;
  while (first) {
    first.remove();
    first = e.firstElementChild;
  }
}

resetter.onclick = (event) => {
  fill_counter.value = outermost.getAttribute("data-fill-count");
  highlighted_index.value = +outermost.getAttribute("data-highlight-index");
  reset();
}

filler.onclick = (event) => {
  reset();
}

highlighter.onclick = (event) => {
  const highlightedIndex = +highlighted_index.value < +event.target.min ? +event.target.value : +highlighted_index.value;
  if (!isNaN(highlightedIndex)) {
    highlight_square(highlightedIndex);
  }
}


fill_counter.onchange = (event) => {
  let current_value = +event.target.value;

  if (!isNaN(current_value)) {
    if (current_value < +event.target.min) {
      event.target.value = +event.target.min;
    }
  }
}

highlighted_index.onchange = (event) => {
  let current_value = +event.target.value;

  if (!isNaN(current_value)) {
    if (current_value < +event.target.min) {
      event.target.value = +event.target.min;
    }
    if (+event.target.value === +event.target.min) {
      event.target.value = +event.target.min;
    }
  }
}

function reset() {
  clear_children(outermost);
  fill_squares(+fill_counter.value);
}

function setup_listener() {
  document.addEventListener("inviewport", (e) => {
    console.log(`${e.detail.target} in viewport!`)
  }, false);
}

reset();
highlight_squares([19, 50, 35]);
setup_listener();
const observer = create_observer();
outermost.querySelectorAll('.highlighted_square').forEach(square => observer.observe(square));
.input_region {
  margin: 5px;
}

.input_region label {
  display: inline-block;
}

.action_input {
  padding: 10px;
}

.highlighted_square {
  background: yellow
}

.num_input {
  height: 30px;
  width: 50px;
  padding: 5px;
}

.square {
  background: magenta
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr));
  grid-auto-rows: 1fr;
}

.grid::before {
  content: '';
  width: 0;
  padding-bottom: 100%;
  grid-row: 1 / 1;
  grid-column: 1 / 1;
}

.grid>*:first-child {
  grid-row: 1 / 1;
  grid-column: 1 / 1;
}


/* Just to make the grid visible */

.grid>* {
  // background: rgba(0, 0, 0, 0.1);
  border: 1px white solid;
}
<section>
  <div class="input_region">
    <input class="num_input" id="fill_counter" type="number" value="1000" min="10" />

    <button id="filler" class="action_input">FILL</button>
  </div>

  <div class="input_region">
    <input class="num_input" id="highlighted_index" type="number" value="10" min="0" />
    <button id="highlighter" class="action_input">HIGHLIGHT</button>
  </div>

  <div class="input_region">
    <button class="action_input" id="resetter">RESET</button>
  </div>
</section>

<section>
  <div id="outermost" class="grid" data-fill-count="1000" data-highlight-index="100"></div>
</section>

Upvotes: 1

Related Questions