Mr. Demetrius Michael
Mr. Demetrius Michael

Reputation: 2406

How to efficiently return all element ids in viewport?

I'm unsure how to get all element ids that are in view, without building an array of all the element ids and checking individually if each of those ids are visible.

Is there a more efficient way in doing this (that ideally has a +80% browser compatibility)?

I want to build a simple dwell time on content script. Also I wanted to use this to pause videos (think "GIFs" encoded as mp4s) that are out of view. I think modern browsers do this already, but not entirely sure.

I don't have any code yet, because I don't know where to begin from a high level.

I was going to attach observers and trigger call backs using Intersection Observer API. But that'll be pretty fragile and less elegant than I would like since I'll need to trigger this on scroll for dynamically loaded websites.

Upvotes: 3

Views: 1817

Answers (3)

jmcgriz
jmcgriz

Reputation: 3368

This is sort of a backwards approach, but you could store the offsets of the content blocks ahead of time (you'll need to update this for new content that enters the page or on window resize), and then just filter that array on scroll rather than re-querying the dom and re-calculating the offset every time.

This needs refinement as it was thrown together in a few minutes, but hopefully it gives the general idea of the approach.

const contentBlocks = [] // { elem, offset }

//for infinite scroll, fire this for each new element added to the page
function storeElem(elem){
	contentBlocks.push({
  	elem,
    offset: elem.getBoundingClientRect().top + window.scrollY
  })
}

//should be in a window.load statement
let i = 0
Array.prototype.slice.call(document.querySelectorAll('p'), 0).forEach(elem => {
	++i
	elem.id = "id" + i
	storeElem(elem)
})

window.addEventListener('scroll', e => {
	const min = window.scrollY, max = window.scrollY + window.innerHeight
	const visibleElements = contentBlocks.filter(t => {
  	return t.offset > min && t.offset < max
  })
  
  console.log(visibleElements.map(t => t.elem.id))
})
span { display: block; }
<p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p>
<p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p><p>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
<span>Content!</span>
</p>

Upvotes: 0

Bhuvanesh kumar
Bhuvanesh kumar

Reputation: 1

Let's say this is your HTML code, DIVs with IDs:

<div id="id1"></div>
<div id="id2"></div>
<div id="id3"></div>

You can use this simple piece of Javascript to find all elements with IDs, and loop through the list:

var ele = document.querySelectorAll('[id]');
ele.forEach(element => {
  console.log(element.id)
});

Upvotes: 0

James Kraus
James Kraus

Reputation: 3478

For this you'll probably want to use a combination of Document.querySelectorAll (nearly universal support) and IntersectionObserver (non-ie support). You can use querySelectorAll to get a list of all elements with IDs in the viewport:

const elemsWithIds = document.querySelectorAll("*[id]");

And IntersectionObserver to see when something enters/leaves the viewport:

const observer = new IntersectionObserver(elems => {
  // Run code here to detect when a collection of elements has entered/left
  // the viewport. This will be fired the moment you call `observer.observe`,
  // so you can get an initial tally of which elements are in view.
});


elemsWithIds.forEach(elem => observer.observe(elem));

Here, I use the two APIs to log which elements are entering a leaving the view to console:

// Construct some elements to observe
for (let i = 0; i < 200; i++) {
  const elem = document.createElement("li");
  elem.setAttribute("id", `item-${i}`);
  elem.innerHTML = `I'm element ${i}`;
  document.getElementById("myList").appendChild(elem);
}

// Create our observer
const observer = new IntersectionObserver(entries => {
  console.log("Items in view have changed:")
  
  entries.forEach(({ target, isIntersecting }) => {
    console.log(`I'm ${isIntersecting ? 'in view' : 'out of view'}: ${target.getAttribute("id")}`, target);
  });
});

// Observe all elements with IDs
const elemsWithIds = document.querySelectorAll("*[id]");
elemsWithIds.forEach(elem => observer.observe(elem));
<ul id="myList"></ul>	

If you only need the initial tally and do not need live updates on what's in view, you can modify this code to cancel the observation by calling IntersectionObserver.disconnect()

Upvotes: 4

Related Questions