【问题标题】:Force IntersectionObserver update强制 IntersectionObserver 更新
【发布时间】:2018-12-26 10:08:12
【问题描述】:

有没有办法强制更新/运行IntersectionObserver 实例?默认情况下,当视口发生更改时,将执行回调。但我正在寻找一种在其他事件发生时执行它的方法,比如元素的变化。

一个例子:

在初始化时,一切都按预期工作。但是当你改变#red 元素的位置时,什么都没有发生。

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

有什么办法可以让它工作吗?唯一可行的方法是unobserve 元素并再次启动observing。这可能适用于单个元素,但如果观察者有数百个元素要观察,则不行。

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

【问题讨论】:

    标签: javascript intersection-observer


    【解决方案1】:

    我认为在不调用节点上的 unobserve/observe 的情况下强制交叉观察者进行更新是不可能的,但您可以通过将所有观察到的节点保存在一个集合中来执行此操作:

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

    编辑:使用Set 而不是WeakSet,因为它们是iterable,因此无需检查是否针对正文中的每个元素都在观察该元素。请小心拨打unobseve,以免出现内存问题。

    【讨论】:

    • 这与我在示例中发布的内容基本相同。唯一不同的是,它包含在一个类中。但是,如果我们谈论成百上千个元素,如问题中所述,这是不切实际的。它很慢而且要手动。因此,感谢您的时间,但这不是我的解决方案。 ;)
    • 不一样,如果你移动了一个容器,它也会更新孩子
    • 嗯,是的。但它仍然只有 unobserveobserve 单个元素。您只需放一个不错的课程并循环播放,但基本上是这样。并且通过检查孩子,它甚至更慢。 ;)
    • @eisbehr 这真的那么慢吗?理论上(我自己没有尝试过),缓慢的部分是重新计算 CSSOM 中的所有框,但这只能执行一次,并且无论如何都必须发生。之后,它只是一个简单的边界检查过滤器,应该不会花时间。
    • @Kaiido 你是对的,事实上在n 元素上调用observe/unobserve 不会触发观察者回调n 次。由于它是在同一个call stack 中完成的,并且观察者队列绑定到event loop,因此对于位置可能会更改的每个元素作为其条目,只会进行一次调用。
    【解决方案2】:

    一种方法是使用 MutationObserver。如果发生突变(在这种情况下是样式更改),请在已更改的元素上调用观察/取消观察。这样您就不必对所有元素都这样做。

    这是一个例子:

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

    【讨论】:

    • "这样你就不必对所有元素都这样做。"是的,他们仍然需要重新观察 IntersectionObserver 的所有目标,通过附加元素,您可能会更改页面中所有元素的位置,而不仅仅是已附加的元素。并且 OP 可以控制他们何时添加该元素,因此 MutationObserver 没有任何作用,因为他们已经可以访问这个确切的时刻。
    • 在这个 OP 中没有提到附加元素。提供的示例是样式更改。这可以使用attributeFilter: ["style"] 轻松过滤
    • 添加或取消隐藏,或添加边距或其他任何更改布局的内容完全相同,他们需要检查所有节点,并且他们负责此更改因此也可以访问发生这种情况的那一刻 -> MutationObserver 是无用的。
    【解决方案3】:

    我的问题可能不正确,我的理解是你想触发 IntersectionObserver,所以你的回调被调用。为什么不直接调用呢?

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

    【讨论】:

    • 他们希望 IntersectionObserver 更新其内部交叉点列表。例如在 OP 的 sn-p 中,绿色块始终“完全”在蓝色内部,红色块仅在用户单击按钮后。但是要知道,IntersectionObserver 的内部检查需要再次触发,因为通常只有滚动或第一次观察才会触发此内部检查。
    • @Kaiido 是对的,这完全不是我们所要求的。 ;) 感谢您的宝贵时间。
    【解决方案4】:

    您只需为您的 Intersection 观察者设置threshold: 1.0。这是一个难以理解的参数。 Threshold 定义了 Observer 应该触发回调的交叉点的百分比。

    默认值为 0,这意味着当元素的第一个或最后一个像素与捕获帧的边框相交时,将触发回调。您的元素永远不会完全离开捕获帧。这就是为什么从不调用回调的原因。

    如果我们将阈值设置为 1,我们会告诉观察者在元素 100% 在帧内时触发我们的回调。这意味着回调将在这种 100% 包容的状态发生变化时触发。我希望这听起来可以理解:)

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-26
      • 2018-05-08
      • 2019-02-09
      • 2011-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多