我有一个Canvas元素(在DIV中)。我希望在以下转换的转换状态更改时获得更新:
画布到页面,画布到窗口,画布到屏幕(像素)。
理想情况下,比设置动画任务和遍历每个帧上的树更方便,更有效,保存旧值并与每个节点上从子节点到根节点以及每个帧上的当前状态进行比较。
由于你希望在任何改变你的元素时有一个事件,最好是轮询每个渲染帧(我们可以用requestAnimationFrame方法挂钩这些帧)。
无论如何,这样的轮询将是确定窗口是否已在屏幕中移动所必需的,因为没有内置事件会触发。 (请注意,浏览器甚至似乎以非常低的速率更新它)。
然后基本的是存储一个保持当前Element位置的对象(它的bounding box应该足够),以及窗口的位置(Window的screenX
and和screenY
会让我们知道这一点),并检查每个帧是否确实改变了值。
需要注意的是,边界框仅与Element的所有者文档相关。也就是说,在类似StackOverflow StackSnippets iframe的情况下,如果我们滚动最顶层的文档,我们元素在其iframe中的边界rect将不会改变。为了解决这个问题,我们可以添加一个IntersectionObserver,当元素可见时,滚动父窗口时,我们会告诉我们。
function listenForMove(elem) {
if (!(elem instanceof Element))
throw new TypeError('Expected an Element');
const state = {
width: null,
height: null,
left: null,
top: null,
screenX: null,
screenY: null
};
let changed = false;
function loop() {
const rect = elem.getBoundingClientRect();
Object.keys(state).forEach(checkKeys.bind(rect));
['screenX', 'screenY'].forEach(checkKeys.bind(window));
if (changed) {
elem.dispatchEvent(new Event('move'));
}
// lower the flag
changed = false;
// check again next frame
requestAnimationFrame(loop);
function checkKeys(key) {
if (key in this) {
if (state[key] !== null && state[key] !== this[key]) {
changed = true;
}
state[key] = this[key];
}
}
}
loop();
// if inside a frame, our bounding box doesn't change
// so we use an intersection observer
// works only when the Element is visible though
var obs = new IntersectionObserver(onintersectionchange, {
root: null,
margin: '100%',
threshold: Array.from({
length: 100
}).map((_, i) => i / 100)
})
obs.observe(elem);
function onintersectionchange(entries) {
changed = true; // simply raise the flag
// `loop` will be responsible of firing the event
}
}
// initialise our poll loop
listenForMove(canvas);
canvas.addEventListener('move', e => console.log('moved'));
canvas {
border: 1px solid;
}
body {
height: 500vh;
}
canvas:hover {
transform: translateX(20px);
transition: .2s linear;
}
<canvas id="canvas"></canvas>