Workaround issue in Safari where setting the perspective origin using a var() expression for one of the axes does not apply correctly causing the 3d parallax effect to be lost.
80 lines
2.9 KiB
JavaScript
80 lines
2.9 KiB
JavaScript
let heightOffset = 0;
|
|
let lastHeight = window.innerHeight;
|
|
function getWindowHeight(event) {
|
|
|
|
/*
|
|
When scrolling such that the browser toolbar is pushed out of view or
|
|
pulled into view on a mobile device the height of the viewport changes.
|
|
This causes the perspective to shift noticeably when you let go of your
|
|
finger after scrolling. To avoid this, I store the last change of the
|
|
window height if this was previously 0 and apply that to the calculated
|
|
height of the window when the size changes.
|
|
|
|
This causes the heightOffset to toggle between 0 and the value that
|
|
negates the perspective shift when you switch between having the browser
|
|
toolbar visible and not visible on mobile, but stay as 0 in most other
|
|
situations involving window resize.
|
|
*/
|
|
if (event?.type === 'resize') {
|
|
if (heightOffset === 0 && lastHeight !== 0) {
|
|
const newOffset = lastHeight - window.innerHeight;
|
|
// Expect the browser toolbar resize to be between 20 and 100 pixels.
|
|
if (Math.abs(newOffset) > 20 && Math.abs(newOffset) < 100) {
|
|
heightOffset = newOffset;
|
|
} else {
|
|
heightOffset = 0;
|
|
}
|
|
} else {
|
|
heightOffset = 0;
|
|
}
|
|
lastHeight = window.innerHeight;
|
|
}
|
|
return window.innerHeight + heightOffset;
|
|
}
|
|
|
|
function setViewportOffset(viewport, windowHeight) {
|
|
|
|
/*
|
|
Ignoring updates if the the bounding height of the viewport element is
|
|
not inside of the visible client area on the page to optimise for when
|
|
it not visible due to being off screen.
|
|
*/
|
|
const viewportRect = viewport.getBoundingClientRect();
|
|
if (viewportRect.top > window.innerHeight || viewportRect.bottom < 0) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
Calculate the distance from the top of the viewport to the center of the
|
|
screen. This is an accurate rendering of perspective shift as you
|
|
scroll.
|
|
|
|
If the the bounds of a viewport element is close to the top or bottom
|
|
of the page the perspective origin is adjusted so that it is still
|
|
contained inside of the bounds of the element to not look out of place.
|
|
endOffset controls how much into the bounds the perspective origin should be
|
|
pushed when the screen in scrolled all the way to the top/bottom.
|
|
*/
|
|
const documentRect = document.documentElement.getBoundingClientRect();
|
|
const endOffset = viewportRect.height * 0.25;
|
|
const origin = Math.min(
|
|
(viewportRect.bottom - documentRect.top) - endOffset,
|
|
Math.max(
|
|
windowHeight / 2,
|
|
windowHeight - (documentRect.bottom - viewportRect.top) + endOffset,
|
|
)
|
|
);
|
|
const yOffset = origin - viewportRect.top;
|
|
viewport.style.setProperty('perspective-origin', `50% ${yOffset}px`);
|
|
}
|
|
|
|
const viewports = document.querySelectorAll(".viewport");
|
|
function updateViewports(event) {
|
|
const windowHeight = getWindowHeight(event);
|
|
for (const viewport of viewports) {
|
|
setViewportOffset(viewport, windowHeight);
|
|
}
|
|
}
|
|
window.addEventListener("scroll", updateViewports, { passive: true });
|
|
window.addEventListener("resize", updateViewports, { passive: true });
|
|
updateViewports();
|