Unverified Commit d9abd85d authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #1477 from hypothesis/observe-element-size

Convert excerpt to Preact (1/n) - Add a utility to observe element size changes
parents 73e0e81a e0db3e13
'use strict';
/**
* Watch for changes in the size (`clientWidth` and `clientHeight`) of
* an element.
*
* Returns a cleanup function which should be called to remove observers when
* updates are no longer needed.
*
* @param {Element} element - HTML element to watch
* @param {(width: number, height: number) => any} onSizeChanged -
* Callback to invoke with the `clientWidth` and `clientHeight` of the
* element when a change in its size is detected.
* @return {() => void}
*/
function observeElementSize(element, onSizeChanged) {
if (typeof ResizeObserver !== 'undefined') {
const observer = new ResizeObserver(() =>
onSizeChanged(element.clientWidth, element.clientHeight)
);
observer.observe(element);
return () => observer.disconnect();
}
// Fallback method which listens for the most common events that result in
// element size changes:
//
// - Window size change
// - Media loading and adjusting size to content
// - DOM changes
//
// This is not comprehensive but it is simple to implement and good-enough for
// our current use cases.
let prevWidth = element.clientWidth;
let prevHeight = element.clientHeight;
const check = () => {
if (
prevWidth !== element.clientWidth ||
prevHeight !== element.clientHeight
) {
prevWidth = element.clientWidth;
prevHeight = element.clientHeight;
onSizeChanged(prevWidth, prevHeight);
}
};
element.addEventListener('load', check);
window.addEventListener('resize', check);
const observer = new MutationObserver(check);
observer.observe(element, {
characterData: true,
childList: true,
subtree: true,
});
return () => {
element.removeEventListener('load', check);
window.removeEventListener('resize', check);
observer.disconnect();
};
}
module.exports = observeElementSize;
'use strict';
const observeElementSize = require('../observe-element-size');
/**
* Give MutationObserver, ResizeObserver etc. a chance to deliver their
* notifications.
*/
function waitForObservations() {
return new Promise(resolve => setTimeout(resolve, 1));
}
describe('observeElementSize', () => {
let content;
let sizeChanged;
let stopObserving;
beforeEach(() => {
sizeChanged = sinon.stub();
content = document.createElement('div');
content.innerHTML = '<p>Some test content</p>';
document.body.appendChild(content);
});
afterEach(() => {
stopObserving();
content.remove();
});
function startObserving() {
stopObserving = observeElementSize(content, sizeChanged);
}
context('when `ResizeObserver` is available', function() {
if (typeof ResizeObserver === 'undefined') {
this.skip();
}
it('notifies when the element size changes', async () => {
startObserving();
content.innerHTML = '<p>different content</p>';
await waitForObservations();
assert.called(sizeChanged);
stopObserving();
sizeChanged.reset();
content.innerHTML = '<p>other content</p>';
await waitForObservations();
assert.notCalled(sizeChanged);
});
});
context('when `ResizeObserver` is not available', () => {
let origResizeObserver;
beforeEach(() => {
origResizeObserver = window.ResizeObserver;
window.ResizeObserver = undefined;
});
afterEach(() => {
window.ResizeObserver = origResizeObserver;
});
[
{
description: 'media loads inside the element',
triggerCheck: () =>
content.dispatchEvent(new Event('load', { bubbles: true })),
},
{
description: 'the window is resized',
triggerCheck: () => window.dispatchEvent(new Event('resize')),
},
{
description: "the element's DOM structure changes",
triggerCheck: () => (content.innerHTML += '<p>more content</p>'),
},
].forEach(({ description, triggerCheck }) => {
it(`checks for changes when ${description}`, async () => {
startObserving();
// Change the content height, which is not directly observed.
content.style.minHeight = '500px';
triggerCheck();
await waitForObservations();
assert.called(sizeChanged);
sizeChanged.reset();
stopObserving();
content.style.minHeight = '200px';
triggerCheck();
await waitForObservations();
assert.notCalled(sizeChanged);
});
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment