Skip to content

Commit 6c1e2c3

Browse files
Add range attributes to animation
1 parent 61320c7 commit 6c1e2c3

File tree

3 files changed

+82
-42
lines changed

3 files changed

+82
-42
lines changed

src/proxy-animation.js

Lines changed: 71 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,10 @@ function fractionalStartDelay(details) {
818818
if (!(details.timeline instanceof ViewTimeline))
819819
return 0;
820820

821-
const startTime = details.animationRange.start;
821+
let startTime = details.animationRange.start;
822+
if (startTime === 'normal') {
823+
startTime = {rangeName: 'cover', offset: CSS.percent(0)};
824+
}
822825
return relativePosition(details.timeline, startTime.rangeName, startTime.offset);
823826
}
824827

@@ -827,7 +830,10 @@ function fractionalEndDelay(details) {
827830
if (!(details.timeline instanceof ViewTimeline))
828831
return 0;
829832

830-
const endTime = details.animationRange.end;
833+
let endTime = details.animationRange.end;
834+
if (endTime === 'normal') {
835+
endTime = {rangeName: 'cover', offset: CSS.percent(100)};
836+
}
831837
return 1 - relativePosition(details.timeline, endTime.rangeName, endTime.offset);
832838
}
833839

@@ -1327,6 +1333,49 @@ export class ProxyAnimation {
13271333
// 4. Otherwise
13281334
return 'running';
13291335
}
1336+
1337+
get rangeStart() {
1338+
return proxyAnimations.get(this).animationRange.start ?? 'normal';
1339+
}
1340+
1341+
set rangeStart(value) {
1342+
const details = proxyAnimations.get(this);
1343+
if (!details.timeline) {
1344+
return details.animation.rangeStart = value;
1345+
}
1346+
1347+
if (details.timeline instanceof ViewTimeline) {
1348+
const animationRange = details.animationRange;
1349+
animationRange.start = parseTimelineRangeOffset(value, 'start');
1350+
1351+
// Additional polyfill step to ensure that the native animation has the
1352+
// correct value for current time.
1353+
autoAlignStartTime(details);
1354+
syncCurrentTime(details);
1355+
}
1356+
}
1357+
1358+
get rangeEnd() {
1359+
return proxyAnimations.get(this).animationRange.end ?? 'normal';
1360+
}
1361+
1362+
set rangeEnd(value) {
1363+
const details = proxyAnimations.get(this);
1364+
if (!details.timeline) {
1365+
return details.animation.rangeEnd = value;
1366+
}
1367+
1368+
if (details.timeline instanceof ViewTimeline) {
1369+
const animationRange = details.animationRange;
1370+
animationRange.end = parseTimelineRangeOffset(value, 'end');
1371+
1372+
// Additional polyfill step to ensure that the native animation has the
1373+
// correct value for current time.
1374+
autoAlignStartTime(details);
1375+
syncCurrentTime(details);
1376+
}
1377+
}
1378+
13301379
get replaceState() {
13311380
// TODO: Fix me. Replace state is not a boolean.
13321381
return proxyAnimations.get(this).animation.pending;
@@ -1707,30 +1756,36 @@ export class ProxyAnimation {
17071756

17081757
// Parses an individual TimelineRangeOffset
17091758
// TODO: Support all formatting options
1710-
function parseTimelineRangeOffset(value, defaultValue) {
1711-
if(!value) return defaultValue;
1759+
function parseTimelineRangeOffset(value, position) {
1760+
if(!value || value === 'normal') return 'normal';
17121761

17131762
// Extract parts from the passed in value.
1714-
let { rangeName, offset } = defaultValue;
1763+
let rangeName = 'cover'
1764+
let offset = position === 'start' ? CSS.percent(0) : CSS.percent(100)
17151765

17161766
// Author passed in something like `{ rangeName: 'cover', offset: CSS.percent(100) }`
17171767
if (value instanceof Object) {
1718-
if (value.rangeName != undefined) {
1768+
if (value.rangeName !== undefined) {
17191769
rangeName = value.rangeName;
1720-
};
1770+
}
17211771

17221772
if (value.offset !== undefined) {
17231773
offset = value.offset;
17241774
}
17251775
}
17261776
// Author passed in something like `"cover 100%"`
17271777
else {
1728-
const parts = value.split(' ');
1729-
1730-
rangeName = parts[0];
1778+
const parts = value.split(new RegExp(`(${ANIMATION_RANGE_NAMES.join('|')})`)).map(part => part.trim()).filter(Boolean);
17311779

1732-
if (parts.length == 2) {
1733-
offset = parts[1];
1780+
if (parts.length === 1) {
1781+
if (ANIMATION_RANGE_NAMES.includes(parts[0])) {
1782+
rangeName = parts[0];
1783+
} else {
1784+
offset = CSSNumericValue.parse(parts[0]);
1785+
}
1786+
} else if (parts.length === 2) {
1787+
rangeName = parts[0];
1788+
offset = CSSNumericValue.parse(parts[1]);
17341789
}
17351790
}
17361791

@@ -1739,35 +1794,14 @@ function parseTimelineRangeOffset(value, defaultValue) {
17391794
throw TypeError("Invalid range name");
17401795
}
17411796

1742-
// Validate and process offset
1743-
// TODO: support more than % and px. Don’t forget about calc() along with that.
1744-
if (!(offset instanceof Object)) {
1745-
if (!offset.endsWith('%') && !offset.endsWith('px')) {
1746-
throw TypeError("Invalid range offset. Only % and px are supported (for now)");
1747-
}
1748-
1749-
const parsedValue = parseFloat(offset);
1750-
1751-
if (offset.endsWith('%')) {
1752-
offset = CSS.percent(parsedValue);
1753-
} else if (offset.endsWith('px')) {
1754-
offset = CSS.px(parsedValue);
1755-
}
1756-
1757-
}
1758-
17591797
return { rangeName, offset };
17601798
}
17611799

1762-
function defaultAnimationRangeStart() { return { rangeName: 'cover', offset: CSS.percent(0) }; }
1763-
1764-
function defaultAnimationRangeEnd() { return { rangeName: 'cover', offset: CSS.percent(100) }; }
1765-
17661800
// Parses a given animation-range value (string)
17671801
function parseAnimationRange(value) {
17681802
const animationRange = {
1769-
start: defaultAnimationRangeStart(),
1770-
end: defaultAnimationRangeEnd()
1803+
start: 'normal',
1804+
end: 'normal'
17711805
};
17721806

17731807
if (!value)
@@ -1824,8 +1858,8 @@ export function animate(keyframes, options) {
18241858
const details = proxyAnimations.get(proxyAnimation);
18251859

18261860
details.animationRange = {
1827-
start: parseTimelineRangeOffset(options.rangeStart, defaultAnimationRangeStart()),
1828-
end: parseTimelineRangeOffset(options.rangeEnd, defaultAnimationRangeEnd()),
1861+
start: parseTimelineRangeOffset(options.rangeStart, 'start'),
1862+
end: parseTimelineRangeOffset(options.rangeEnd, 'end'),
18291863
};
18301864
}
18311865
proxyAnimation.play();

src/scroll-timeline-base.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -769,15 +769,20 @@ function calculateInset(value, sizes) {
769769
export function relativePosition(timeline, phase, offset) {
770770
const phaseRange = range(timeline, phase);
771771
const coverRange = range(timeline, 'cover');
772-
return calculateRelativePosition(phaseRange, offset, coverRange);
772+
return calculateRelativePosition(phaseRange, offset, coverRange, timeline.subject);
773773
}
774774

775775

776-
export function calculateRelativePosition(phaseRange, offset, coverRange) {
776+
777+
export function calculateRelativePosition(phaseRange, offset, coverRange, subject) {
777778
if (!phaseRange || !coverRange)
778779
return 0;
779780

780-
const info = {percentageReference: new CSSUnitValue(phaseRange.end - phaseRange.start, "px")};
781+
let style = getComputedStyle(subject)
782+
const info = {
783+
percentageReference: CSS.px(phaseRange.end - phaseRange.start),
784+
fontSize: CSS.px(parseFloat(style.fontSize))
785+
};
781786
const simplifiedRangeOffset = simplifyCalculation(offset, info);
782787
if (!(simplifiedRangeOffset instanceof CSSUnitValue) || simplifiedRangeOffset.unit !== 'px') {
783788
throw new Error(`Unsupported offset '${simplifiedRangeOffset.toString()}'`)

test/expected.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,7 @@ FAIL /scroll-animations/view-timelines/sticky/view-timeline-sticky-offscreen-6.h
924924
FAIL /scroll-animations/view-timelines/sticky/view-timeline-sticky-offscreen-7.html View timeline target > viewport, bottom-sticky and top-sticky during contain.
925925
FAIL /scroll-animations/view-timelines/svg-graphics-element-001.html View timeline attached to SVG graphics element
926926
FAIL /scroll-animations/view-timelines/svg-graphics-element-002.html View timeline attached to SVG graphics element
927+
FAIL /scroll-animations/view-timelines/svg-graphics-element-003.html View timeline attached to SVG graphics element
927928
FAIL /scroll-animations/view-timelines/timeline-offset-in-keyframe.html Timeline offsets in programmatic keyframes
928929
FAIL /scroll-animations/view-timelines/timeline-offset-in-keyframe.html String offsets in programmatic keyframes
929930
PASS /scroll-animations/view-timelines/timeline-offset-in-keyframe.html Invalid timeline offset in programmatic keyframe throws
@@ -946,7 +947,7 @@ PASS /scroll-animations/view-timelines/view-timeline-range.html View timeline wi
946947
PASS /scroll-animations/view-timelines/view-timeline-range.html View timeline with range and inferred name or offset.
947948
PASS /scroll-animations/view-timelines/view-timeline-range.html View timeline with range as <name> <px> pair.
948949
PASS /scroll-animations/view-timelines/view-timeline-range.html View timeline with range as <name> <percent+px> pair.
949-
FAIL /scroll-animations/view-timelines/view-timeline-range.html View timeline with range as strings.
950+
PASS /scroll-animations/view-timelines/view-timeline-range.html View timeline with range as strings.
950951
PASS /scroll-animations/view-timelines/view-timeline-root-source.html Test view-timeline with document scrolling element.
951952
PASS /scroll-animations/view-timelines/view-timeline-snapport.html Default ViewTimeline is not affected by scroll-padding
952953
PASS /scroll-animations/view-timelines/view-timeline-source.tentative.html Default source for a View timeline is the nearest scroll ancestor to the subject
@@ -956,4 +957,4 @@ FAIL /scroll-animations/view-timelines/view-timeline-sticky-block.html View time
956957
FAIL /scroll-animations/view-timelines/view-timeline-sticky-inline.html View timeline with sticky target, block axis.
957958
FAIL /scroll-animations/view-timelines/view-timeline-subject-size-changes.html View timeline with subject size change after the creation of the animation
958959
FAIL /scroll-animations/view-timelines/zero-intrinsic-iteration-duration.tentative.html Intrinsic iteration duration is non-negative
959-
Passed 431 of 958 tests.
960+
Passed 432 of 959 tests.

0 commit comments

Comments
 (0)