admin管理员组

文章数量:1201791

Is there any way to force an update/run of an IntersectionObserver instance? The callback will be executed by default, when the viewport has changed. But I'm looking for a way to to execute it when other events happen, like a change of elements.

An Example:

On initialization everything works as expected. But when you change the position of the #red element, nothing happens.

// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');

// observer callback
let callback = entries => {
  entries.forEach(entry => {
    let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
    console.log("#" + entry.target.id + " is " + isInside + " inside #container");
  });
};

// start observer
let options = {root: document.querySelector('#container')};
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);

// button action
document.querySelector('button').addEventListener('click', () => {
  red.style.right = red.style.right == "" ? "0px" : "";
});
#container {
  width: 100px;
  height: 100px;
  background: blue;
  position: relative;
}

#green, #red {
  width: 50px;
  height: 50px;
  background: green;
  position: absolute;
}

#red {
  background: red;
  right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
  <div id="green"></div>
  <div id="red"></div>
</div>

Is there any way to force an update/run of an IntersectionObserver instance? The callback will be executed by default, when the viewport has changed. But I'm looking for a way to to execute it when other events happen, like a change of elements.

An Example:

On initialization everything works as expected. But when you change the position of the #red element, nothing happens.

// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');

// observer callback
let callback = entries => {
  entries.forEach(entry => {
    let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
    console.log("#" + entry.target.id + " is " + isInside + " inside #container");
  });
};

// start observer
let options = {root: document.querySelector('#container')};
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);

// button action
document.querySelector('button').addEventListener('click', () => {
  red.style.right = red.style.right == "" ? "0px" : "";
});
#container {
  width: 100px;
  height: 100px;
  background: blue;
  position: relative;
}

#green, #red {
  width: 50px;
  height: 50px;
  background: green;
  position: absolute;
}

#red {
  background: red;
  right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
  <div id="green"></div>
  <div id="red"></div>
</div>

Is there any way to make this working? Only thing that would work is to unobserve the element and start observing it again. This may be work for an single element, but not if the Observer has hundreds of elements to watch.

// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');

// observer callback
let callback = entries => {
  entries.forEach(entry => {
    let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
    console.log("#" + entry.target.id + " is " + isInside + " inside #container");
  });
};

// start observer
let options = {root: document.querySelector('#container')};
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);

// button action
document.querySelector('button').addEventListener('click', () => {
  red.style.right = red.style.right == "" ? "0px" : "";
  observer.unobserve(red);
  observer.observe(red);
});
#container {
  width: 100px;
  height: 100px;
  background: blue;
  position: relative;
}

#green, #red {
  width: 50px;
  height: 50px;
  background: green;
  position: absolute;
}

#red {
  background: red;
  right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
  <div id="green"></div>
  <div id="red"></div>
</div>

Share Improve this question asked Jul 18, 2018 at 13:07 eisbehreisbehr 12.5k7 gold badges41 silver badges65 bronze badges
Add a comment  | 

5 Answers 5

Reset to default 1

I don't think it is possible to force the intersection observer to update without calling unobserve/observe on the node, but you can do this for all observed nodes by saving them in a set:

class IntersectionObserverManager {
    constructor(observer) {
        this._observer = observer;
        this._observedNodes = new Set();
    }
    observe(node) {
        this._observedNodes.add(node);
        this._observer.observe(node);
    }
    unobserve(node) {
        this._observedNodes.remove(node);
        this._observer.unobserve(node);
    }
    disconnect() {
        this._observedNodes.clear();
        this._observer.disconnect();
    }
    refresh() {
        for (let node of this._observedNodes) {
            this._observer.unobserve(node);
            this._observer.observe(node);
        }
    }
}

Edit: use a Set instead of a WeakSet since they are iterable so there is no need to check if the element is being observed for each element in the body. Be carefull to call unobseve in order to avoid memory problems.

You just need to set threshold: 1.0 for your Intersection observer. This is a tricky parameter to comprehend. Threshold defines the percentage of the intersection at which the Observer should trigger the callback.

The default value is 0 which means callback will be triggered either when the very first or very last pixel of an element intersects a border of the capturing frame. Your element never completely leaves the capturing frame. This is why callback is never called.

If we set the threshold to 1 we tell the observer to trigger our callback when the element is 100% within the frame. It means the callback will be triggered on change in this state of 100% inclusiveness. I hope that sounds understandable :)

// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');

// observer callback
let callback = entries => {
  entries.forEach(entry => {
    let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
    console.log("#" + entry.target.id + " is " + isInside + " inside #container");
  });
};

// start observer
let options = {root: document.querySelector('#container'), threshold: 1.0 };
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);

// button action
document.querySelector('button').addEventListener('click', () => {
  red.style.right = red.style.right == "" ? "0px" : "";
});
#container {
  width: 100px;
  height: 100px;
  background: blue;
  position: relative;
}

#green, #red {
  width: 50px;
  height: 50px;
  background: green;
  position: absolute;
}

#red {
  background: red;
  right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
  <div id="green"></div>
  <div id="red"></div>
</div>

A disconnect did the job for me.

let observer = null

function observe() {
    if (observer) observer.disconnect()
    observer = new IntersectionObserver(intersectionCallback)
    document.querySelectorAll('.element_to_observe').forEach(o => {
        if (o) observer.observe(o)
    })
}

observe() // \
observe() //  > All visible elements are triggered at each call
observe() // /

disconnect seems to destroy the observer and a new observe does not duplicate the events, so it's ok for me.

I may not get the question right, what I understand is you want to trigger the IntersectionObserver, so your callback get called. Why don't you call it directly?

document.querySelector('button').addEventListener('click', () => {
  red.style.right = red.style.right == "" ? "0px" : "";
  callback(red);
});

One way to do it is to use MutationObserver. If a mutation happens (in this case style change) call observe/unobserve on the element that has been changed. This way you don't have to do it for all elements.

Here is an example:

// observer callback
let callback = entries => {
  entries.forEach(entry => {
    let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
    console.log("#" + entry.target.id + " is " + isInside + " inside #container");
  });
};

// start observer
let options = {
  root: document.querySelector('#container')
};
let observer = new IntersectionObserver(callback, options);

const boxes = document.querySelectorAll('#container > div');
boxes.forEach(box => {
  observer.observe(box);
});

// button action
document.querySelector('button').addEventListener('click', () => {
  red.style.right = red.style.right == "" ? "0px" : "";
});



// Mutation observer
const targetNode = document.querySelector('#container');

// Options for the observer (which mutations to observe). We only need style changes so we set attributes to true. Also, we are observing the children of container so subtree is true
const config = {
  attributes: true,
  childList: false,
  subtree: true,
  attributeFilter: ["style"]
};

// Callback function to execute when mutations are observed
const mutationCallback = (mutationsList, mutationObserver) => {
  for (let mutation of mutationsList) {
    if (mutation.type === 'attributes') {
      console.log('The ' + mutation.attributeName + ' attribute was modified.');
      observer.unobserve(mutation.target);
      observer.observe(mutation.target);
    }
  }
};

// Create an observer instance linked to the callback function
const mutationObserver = new MutationObserver(mutationCallback);

// Start observing the target node for configured mutations
mutationObserver.observe(targetNode, config);
#container {
  width: 300px;
  height: 300px;
  background: lightblue;
  position: relative;
}

#green,
#red {
  width: 100px;
  height: 100px;
  background: green;
  position: absolute;
}

#red {
  background: purple;
  right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
  <div id="green"></div>
  <div id="red"></div>
</div>

本文标签: javascriptForce IntersectionObserver updateStack Overflow