Add clamping of perspective origin in Viewport
If there are elements deep into the viewport near the top or bottom they will end up obscured by the edge of the viewport when scrolling. This may be undesirable in some cases. Implement a clamping of the allowed range of the perspective origin to give better control over the perspective and what it obscures.
This commit is contained in:
parent
8a600420b5
commit
937d3d3cfb
2 changed files with 60 additions and 2 deletions
|
@ -4,9 +4,17 @@
|
||||||
export default function Viewport(
|
export default function Viewport(
|
||||||
props: {
|
props: {
|
||||||
children: unknown,
|
children: unknown,
|
||||||
|
clampTop?: number,
|
||||||
|
clampBottom?: number,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
return <div class="viewport in3d">
|
const viewportProps: any = {};
|
||||||
|
if (props.clampTop !== undefined) viewportProps["data-clamp-top"] = String(props.clampTop);
|
||||||
|
if (props.clampBottom !== undefined) viewportProps["data-clamp-bottom"] = String(props.clampBottom);
|
||||||
|
return <div
|
||||||
|
class="viewport in3d"
|
||||||
|
{...viewportProps}
|
||||||
|
>
|
||||||
{ props.children }
|
{ props.children }
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,23 @@ function getWindowHeight(event) {
|
||||||
return window.innerHeight + heightOffset;
|
return window.innerHeight + heightOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns x rescaled such that the values in the range a-b are mapped to the range c-d.
|
||||||
|
*/
|
||||||
|
function rescaleRange(x, a, b, c, d){
|
||||||
|
return c + (x - a) * (d - c) / (b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
If value is not undefined return it parsed as a float, otherwise returns undefined.
|
||||||
|
*/
|
||||||
|
function maybeFloat(value) {
|
||||||
|
if (value === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return Number.parseFloat(value);
|
||||||
|
}
|
||||||
|
|
||||||
function setViewportOffset(viewport, windowHeight) {
|
function setViewportOffset(viewport, windowHeight) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -57,13 +74,46 @@ function setViewportOffset(viewport, windowHeight) {
|
||||||
*/
|
*/
|
||||||
const documentRect = document.documentElement.getBoundingClientRect();
|
const documentRect = document.documentElement.getBoundingClientRect();
|
||||||
const endOffset = viewportRect.height * 0.25;
|
const endOffset = viewportRect.height * 0.25;
|
||||||
const origin = Math.min(
|
let origin = Math.min(
|
||||||
(viewportRect.bottom - documentRect.top) - endOffset,
|
(viewportRect.bottom - documentRect.top) - endOffset,
|
||||||
Math.max(
|
Math.max(
|
||||||
windowHeight / 2,
|
windowHeight / 2,
|
||||||
windowHeight - (documentRect.bottom - viewportRect.top) + endOffset,
|
windowHeight - (documentRect.bottom - viewportRect.top) + endOffset,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
In some cases it's undesirable to let the perspective origin move too far
|
||||||
|
away in one or both directions as it causes elements inside the 3d view to
|
||||||
|
become obscured by the top/bottom of the viewport. To aid with this
|
||||||
|
optionally clamp how far the perspective origin can move relative to the top
|
||||||
|
and bottom.
|
||||||
|
|
||||||
|
This is done by calculating how far the origin can go un-clamped while the
|
||||||
|
viewport is visible on the screen and then rescaling the range of the
|
||||||
|
perspective origin from it's un-clamped range to its clamped range.
|
||||||
|
*/
|
||||||
|
const clampTop = maybeFloat(viewport.dataset.clampTop);
|
||||||
|
const clampBottom = maybeFloat(viewport.dataset.clampBottom);
|
||||||
|
if (clampBottom !== undefined || clampTop !== undefined) {
|
||||||
|
const rootFontSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||||
|
const topOrigin = Math.min(
|
||||||
|
viewportRect.bottom - endOffset,
|
||||||
|
Math.max(viewportRect.top, documentRect.top + windowHeight) - windowHeight / 2,
|
||||||
|
)
|
||||||
|
const topClampPosition = rootFontSize * (clampTop ?? -Infinity) + viewportRect.top;
|
||||||
|
const topLimit = Math.max(topOrigin, topClampPosition);
|
||||||
|
|
||||||
|
const bottomOrigin = Math.max(
|
||||||
|
viewportRect.top + endOffset,
|
||||||
|
Math.min(viewportRect.bottom, documentRect.bottom - windowHeight) + windowHeight / 2,
|
||||||
|
)
|
||||||
|
const bottomClampPosition = rootFontSize * (clampBottom ?? +Infinity) + viewportRect.bottom;
|
||||||
|
const bottomLimit = Math.min(bottomOrigin, bottomClampPosition);
|
||||||
|
|
||||||
|
origin = rescaleRange(origin, topOrigin, bottomOrigin, topLimit, bottomLimit);
|
||||||
|
}
|
||||||
|
|
||||||
const yOffset = origin - viewportRect.top;
|
const yOffset = origin - viewportRect.top;
|
||||||
viewport.style.setProperty('perspective-origin', `50% ${yOffset}px`);
|
viewport.style.setProperty('perspective-origin', `50% ${yOffset}px`);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue