diff --git a/src/scroll-timeline-base.js b/src/scroll-timeline-base.js index 2c85492..7bba378 100644 --- a/src/scroll-timeline-base.js +++ b/src/scroll-timeline-base.js @@ -763,7 +763,9 @@ function calculateInset(value, sizes) { return resolvePx(part, { percentageReference: CSS.px(sizes.containerSize), - fontSize: CSS.px(parseFloat(sizes.fontSize)) + fontSize: CSS.px(Number.parseFloat(sizes.fontSize)), + viewportWidth: CSS.px(window.innerWidth), + viewportHeight: CSS.px(window.innerHeight), }) }); @@ -793,7 +795,11 @@ export function fractionalOffset(timeline, value) { } // TODO: pass relative measurements (viewport, font-size, root font-size, etc. ) to resolvePx() to resolve relative units - const position = resolvePx(value, {percentageReference: CSS.px(sourceScrollDistance)}); + const position = resolvePx(value, { + percentageReference: CSS.px(sourceScrollDistance), + viewportWidth: CSS.px(window.innerWidth), + viewportHeight: CSS.px(window.innerHeight), + }); const fractionalOffset = position / sourceScrollDistance; return fractionalOffset; @@ -809,7 +815,9 @@ export function calculateRelativePosition(phaseRange, offset, coverRange, subjec let style = getComputedStyle(subject) const info = { percentageReference: CSS.px(phaseRange.end - phaseRange.start), - fontSize: CSS.px(parseFloat(style.fontSize)) + fontSize: CSS.px(Number.parseFloat(style.fontSize)), + viewportWidth: CSS.px(window.innerWidth), + viewportHeight: CSS.px(window.innerHeight), }; const offsetPX = resolvePx(offset, info) + phaseRange.start; diff --git a/src/simplify-calculation.js b/src/simplify-calculation.js index d0e04fe..66c59ae 100644 --- a/src/simplify-calculation.js +++ b/src/simplify-calculation.js @@ -1,4 +1,4 @@ -import {isCanonical} from "./utils"; +import {isCanonical, isViewportUnit, resolveViewportUnit} from "./utils"; /** * @typedef {{percentageReference: CSSUnitValue, fontSize?: CSSUnitValue}} Info @@ -72,6 +72,16 @@ export function simplifyCalculation(root, info = {}) { return new CSSUnitValue(resolvedValue, resolvedUnit); } + // 2. If root is a viewport-relative unit that is not expressed in its canonical unit, and there is enough information available + // to convert it to the canonical unit, do so, and return the value. + if (isViewportUnit(root.unit)) { + const resolvedViewport = resolveViewportUnit(root, info); + + if (resolvedViewport) { + return resolvedViewport; + } + } + // 2. If root is a dimension that is not expressed in its canonical unit, and there is enough information available // to convert it to the canonical unit, do so, and return the value. diff --git a/src/utils.js b/src/utils.js index 133a922..8bf1392 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,9 +1,15 @@ const canonicalUnits = new Set(["px", "deg", "s", "hz", "dppx", "number", "fr"]); +const viewportUnits = new Set(["vw", "vh", "vmin", "vmax"]); + export function isCanonical(unit) { return canonicalUnits.has(unit.toLowerCase()); } +export function isViewportUnit(unit) { + return viewportUnits.has(unit.toLowerCase()); +} + export function normalizeAxis(axis, computedStyle) { if (['x','y'].includes(axis)) return axis; @@ -77,4 +83,45 @@ export function splitIntoComponentValues(input) { } } return res; -} \ No newline at end of file +} + + +/** + * Resolve viewport-relative units to canonical units + * + * @param {CSSUnitValue} root + * @param {Info} info + * @return {CSSUnitValue|null} + */ +export function resolveViewportUnit(root, info) { + if (!(root instanceof CSSUnitValue)) return null; + + const { viewportWidth, viewportHeight } = info; + if (!viewportWidth || !viewportHeight) return null; + + switch (root.unit) { + case "vw": + return new CSSUnitValue( + (root.value * viewportWidth.value) / 100, + viewportWidth.unit + ); + + case "vh": + return new CSSUnitValue( + (root.value * viewportHeight.value) / 100, + viewportHeight.unit + ); + + case "vmin": { + const min = Math.min(viewportWidth.value, viewportHeight.value); + return new CSSUnitValue((root.value * min) / 100, viewportWidth.unit); + } + + case "vmax": { + const max = Math.max(viewportWidth.value, viewportHeight.value); + return new CSSUnitValue((root.value * max) / 100, viewportWidth.unit); + } + } + + return null; +}