Skip to content

Instantly share code, notes, and snippets.

@mr47
Created January 24, 2022 14:28
Show Gist options
  • Select an option

  • Save mr47/4158caaca9607f5e39665a59996d5a0a to your computer and use it in GitHub Desktop.

Select an option

Save mr47/4158caaca9607f5e39665a59996d5a0a to your computer and use it in GitHub Desktop.
export default function createDetectElementResize(nonce, hostWindow) {
// Check `document` and `window` in case of server-side rendering
let _window;
if (typeof hostWindow !== 'undefined') {
_window = hostWindow;
} else if (typeof window !== 'undefined') {
_window = window;
} else if (typeof self !== 'undefined') {
_window = self;
} else {
_window = global;
}
let attachEvent =
typeof _window.document !== 'undefined' && _window.document.attachEvent;
if (!attachEvent) {
let requestFrame = (function() {
let raf =
_window.requestAnimationFrame ||
_window.mozRequestAnimationFrame ||
_window.webkitRequestAnimationFrame ||
function(fn) {
return _window.setTimeout(fn, 20);
};
return function(fn) {
return raf(fn);
};
})();
let cancelFrame = (function() {
let cancel =
_window.cancelAnimationFrame ||
_window.mozCancelAnimationFrame ||
_window.webkitCancelAnimationFrame ||
_window.clearTimeout;
return function(id) {
return cancel(id);
};
})();
let resetTriggers = function(element) {
let triggers = element.__resizeTriggers__,
expand = triggers.firstElementChild,
contract = triggers.lastElementChild,
expandChild = expand.firstElementChild;
contract.scrollLeft = contract.scrollWidth;
contract.scrollTop = contract.scrollHeight;
expandChild.style.width = expand.offsetWidth + 1 + 'px';
expandChild.style.height = expand.offsetHeight + 1 + 'px';
expand.scrollLeft = expand.scrollWidth;
expand.scrollTop = expand.scrollHeight;
};
let checkTriggers = function(element) {
return (
element.offsetWidth != element.__resizeLast__.width ||
element.offsetHeight != element.__resizeLast__.height
);
};
let scrollListener = function(e) {
// Don't measure (which forces) reflow for scrolls that happen inside of children!
if (
e.target.className &&
typeof e.target.className.indexOf === 'function' &&
e.target.className.indexOf('contract-trigger') < 0 &&
e.target.className.indexOf('expand-trigger') < 0
) {
return;
}
let element = this;
resetTriggers(this);
if (this.__resizeRAF__) {
cancelFrame(this.__resizeRAF__);
}
this.__resizeRAF__ = requestFrame(function() {
if (checkTriggers(element)) {
element.__resizeLast__.width = element.offsetWidth;
element.__resizeLast__.height = element.offsetHeight;
element.__resizeListeners__.forEach(function(fn) {
fn.call(element, e);
});
}
});
};
/* Detect CSS Animations support to detect element display/re-attach */
let animation = false,
keyframeprefix = '',
animationstartevent = 'animationstart',
domPrefixes = 'Webkit Moz O ms'.split(' '),
startEvents = 'webkitAnimationStart animationstart oAnimationStart MSAnimationStart'.split(
' ',
),
pfx = '';
{
let elm = _window.document.createElement('fakeelement');
if (elm.style.animationName !== undefined) {
animation = true;
}
if (animation === false) {
for (let i = 0; i < domPrefixes.length; i++) {
if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
pfx = domPrefixes[i];
keyframeprefix = '-' + pfx.toLowerCase() + '-';
animationstartevent = startEvents[i];
animation = true;
break;
}
}
}
}
let animationName = 'resizeanim';
let animationKeyframes =
'@' +
keyframeprefix +
'keyframes ' +
animationName +
' { from { opacity: 0; } to { opacity: 0; } } ';
let animationStyle =
keyframeprefix + 'animation: 1ms ' + animationName + '; ';
}
let createStyles = function(doc) {
if (!doc.getElementById('detectElementResize')) {
//opacity:0 works around a chrome bug https://code.google.com/p/chromium/issues/detail?id=286360
let css =
(animationKeyframes ? animationKeyframes : '') +
'.resize-triggers { ' +
(animationStyle ? animationStyle : '') +
'visibility: hidden; opacity: 0; } ' +
'.resize-triggers, .resize-triggers > div, .contract-trigger:before { content: " "; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; z-index: -1; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }',
head = doc.head || doc.getElementsByTagName('head')[0],
style = doc.createElement('style');
style.id = 'detectElementResize';
style.type = 'text/css';
if (nonce != null) {
style.setAttribute('nonce', nonce);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(doc.createTextNode(css));
}
head.appendChild(style);
}
};
let addResizeListener = function(element, fn) {
if (attachEvent) {
element.attachEvent('onresize', fn);
} else {
if (!element.__resizeTriggers__) {
let doc = element.ownerDocument;
let elementStyle = _window.getComputedStyle(element);
if (elementStyle && elementStyle.position == 'static') {
element.style.position = 'relative';
}
createStyles(doc);
element.__resizeLast__ = {};
element.__resizeListeners__ = [];
(element.__resizeTriggers__ = doc.createElement('div')).className =
'resize-triggers';
let expandTrigger = doc.createElement('div');
expandTrigger.className = 'expand-trigger';
expandTrigger.appendChild(doc.createElement('div'));
let contractTrigger = doc.createElement('div');
contractTrigger.className = 'contract-trigger';
element.__resizeTriggers__.appendChild(expandTrigger);
element.__resizeTriggers__.appendChild(contractTrigger);
element.appendChild(element.__resizeTriggers__);
resetTriggers(element);
element.addEventListener('scroll', scrollListener, true);
/* Listen for a css animation to detect element display/re-attach */
if (animationstartevent) {
element.__resizeTriggers__.__animationListener__ = function animationListener(
e,
) {
if (e.animationName == animationName) {
resetTriggers(element);
}
};
element.__resizeTriggers__.addEventListener(
animationstartevent,
element.__resizeTriggers__.__animationListener__,
);
}
}
element.__resizeListeners__.push(fn);
}
};
let removeResizeListener = function(element, fn) {
if (attachEvent) {
element.detachEvent('onresize', fn);
} else {
element.__resizeListeners__.splice(
element.__resizeListeners__.indexOf(fn),
1,
);
if (!element.__resizeListeners__.length) {
element.removeEventListener('scroll', scrollListener, true);
if (element.__resizeTriggers__.__animationListener__) {
element.__resizeTriggers__.removeEventListener(
animationstartevent,
element.__resizeTriggers__.__animationListener__,
);
element.__resizeTriggers__.__animationListener__ = null;
}
try {
element.__resizeTriggers__ = !element.removeChild(
element.__resizeTriggers__,
);
} catch (e) {
// Preact compat; see developit/preact-compat/issues/228
}
}
}
};
return {
addResizeListener,
removeResizeListener,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment