Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save bendytree/3b20473b15d6813f9cf5de838208df98 to your computer and use it in GitHub Desktop.
Save bendytree/3b20473b15d6813f9cf5de838208df98 to your computer and use it in GitHub Desktop.
const log = (...args) => {}; //console.log(...args);
const isIOS = (function () {
return ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(navigator.platform)
|| (navigator.userAgent.includes('Mac') && 'ontouchend' in document);
})();
class ScrollLocker {
orig = {};
isLocked = false;
updateLock () {
this.orig.scrollY = window.scrollY;
}
enforce () {
if (window.scrollY !== this.orig.scrollY) {
window.scrollTo({ behavior: 'instant', top: this.orig.scrollY });
}
}
lock() {
if (this.isLocked) return;
this.isLocked = true;
this.orig.scrollY = window.scrollY;
this.orig.overflow = document.body.style.overflow;
this.orig.position = document.body.style.position;
this.orig.top = document.body.style.top;
this.orig.width = document.body.style.width;
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.top = `-${this.orig.scrollY}px`;
document.body.style.width = `100%`;
}
unlock() {
if (!this.isLocked) return;
this.isLocked = false;
document.body.style.overflow = this.orig.overflow;
document.body.style.position = this.orig.position;
document.body.style.top = this.orig.top;
document.body.style.width = this.orig.width;
window.scrollTo(0, this.orig.scrollY);
}
}
export const fixFullscreenDialogOnMobileSafari = (dialog:HTMLDivElement) => {
log(`isIos: ${isIOS}`);
if (!isIOS) return;
const computed = dialog.computedStyleMap();
const yPadding = (parseInt(computed.get('top')?.value) || 0)
+ (parseInt(computed.get('bottom')?.value) || 0)
+ (parseInt(computed.get('padding-top')?.value) || 0)
+ (parseInt(computed.get('padding-bottom')?.value) || 0);
const scrollLocker = new ScrollLocker();
const origHeightStyle = dialog.style.height;
const origBottomStyle = dialog.style.bottom;
const oldViewport = document.querySelector('meta[name="viewport"]');
const newViewport = document.createElement('meta');
newViewport.setAttribute('name', `viewport`);
newViewport.setAttribute('content', `width=device-width, minimum-scale=1, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui`);
const start = () => {
log(`open!`);
if (oldViewport) oldViewport.replaceWith(newViewport);
else document.head.appendChild(newViewport);
let isFocused = false;
const startYOffset = window.scrollY;
scrollLocker.lock();
const onFocusIn = (event) => {
log(`onFocusIn`, window.visualViewport.height);
scrollLocker.updateLock();
dialog.style.bottom = 'auto';
isFocused = true;
setTimeout(() => {
log(`onFocusIn.timeout`, { height: window.visualViewport.height, focused: isFocused });
event.target.scrollIntoView({ block: 'center', behavior: 'instant' });
}, 750);
};
const onFocusOut = (event) => {
log(`onFocusIn.onFocusOut`);
dialog.style.bottom = origBottomStyle;
dialog.style.height = origHeightStyle;
isFocused = false;
};
const onVisualViewportResize = event => {
if (!isFocused) return;
const newHeight = window.visualViewport.height - yPadding;
log(`onVisualViewportResize`, { height: window.visualViewport.height, newHeight });
dialog.style.height = `${newHeight}px`;
};
const onScroll = event => {
log(`onScroll`, { height: window.visualViewport.height, focused: isFocused });
if (!isFocused) return;
scrollLocker.enforce();
};
dialog.addEventListener('focusin', onFocusIn);
dialog.addEventListener('focusout', onFocusOut);
window.visualViewport.addEventListener('resize', onVisualViewportResize);
window.addEventListener('scroll', onScroll);
if (document.activeElement && dialog.contains(document.activeElement)) {
onFocusIn({ target: document.activeElement });
}
return {
close() {
log(`close!`);
isFocused = false;
scrollLocker.unlock();
dialog.removeEventListener('focusin', onFocusIn);
dialog.removeEventListener('focusout', onFocusOut);
window.visualViewport.removeEventListener('resize', onVisualViewportResize);
window.removeEventListener('scroll', onScroll);
if (oldViewport) newViewport.replaceWith(oldViewport);
else newViewport.remove();
window.scrollTo(0, startYOffset);
},
};
};
let modal = null;
setInterval(() => {
const wasOpen = !!modal;
const c = dialog.computedStyleMap();
const isOpen = !!dialog.isConnected && c.get('display').value !== 'none' && c.get('opacity').value > 0;
if (isOpen === wasOpen) return;
if (modal) {
log(`stop!`);
modal.close();
modal = null;
}else{
log(`start!`);
modal = start();
}
}, 250);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment