Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support anonymous scroll timeline in animation shorthand #90

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions demo/basic/anonymous-scroll-timeline-animation-shorthand.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<!DOCTYPE HTML>
<title>Anonymous Scroll Progress Timeline</title>

<style>
@keyframes move {
to { transform: translateX(350px) translateY(2000px); }
}

@keyframes colorChange {
from { background: red; }
to { background: blue; }
}

@keyframes widthChange {
from { width: 100px; }
to { width: 120px; }
}

#box_one {
width: 100px;
height: 100px;
background-color: green;
animation: linear colorChange both scroll(root), linear widthChange 1s both,
move linear scroll(vertical nearest);
}

.spacer {
width: 100%;
height: 2000px;
}

#container {
width: 500px;
height: 500px;
background-color:beige;
overflow: auto;
}

body {
height: 1000px;
}
</style>

<body>

<div id="container">
<div id="box_one"></div>
<div class="spacer"></div>
</div>

<script src="../../dist/scroll-timeline.js"></script>
</body>
2 changes: 1 addition & 1 deletion dist/scroll-timeline.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/scroll-timeline.js.map

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
<a href="demo/view-timeline-css/">demo/view-timeline-css</a>
</li>
<li>
<a href="demo/basic/anonymous-scroll-progress-timeline.html">demo/basic/anonymous-scroll-progress-timeline.html</a>
<a href="demo/basic/anonymous-scroll-timeline.html">demo/basic/anonymous-scroll-timeline.html</a>
</li>
<li>
<a href="demo/basic/anonymous-scroll-timeline-animation-shorthand.html">demo/basic/anonymous-scroll-timeline-animation-shorthand.html</a>
</li>
</ul>
</body>
Expand Down
86 changes: 52 additions & 34 deletions src/scroll-timeline-css-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const RegexMatcher = {
ANIMATION_NAME: /animation-name\s*:([^;}]+)/,
ANIMATION: /animation\s*:([^;}]+)/,
SOURCE_ELEMENT: /selector\(#([^)]+)\)/,
ANONYMOUS_SCROLL: /scroll\(([^)]*)\)/,
};

// Used for ANIMATION_TIMELINE, ANIMATION_NAME and ANIMATION regex
Expand Down Expand Up @@ -242,6 +243,7 @@ export class StyleParser {

let timelineNames = [];
let animationNames = [];
let shouldReplacePart = false;

if (hasAnimationTimeline)
timelineNames = this.extractScrollTimelineNames(rule.block.contents);
Expand All @@ -257,50 +259,54 @@ export class StyleParser {
if (hasAnimation) {
this.extractMatches(rule.block.contents, RegexMatcher.ANIMATION)
.forEach(shorthand => {
const r = this.extractTimelineName(shorthand);

if(r.timelineName)
timelineNames.push(r.timelineName);

const animationName = this.extractAnimationName(shorthand);
const timelineName = this.extractTimelineName(shorthand);
if (animationName) animationNames.push(animationName);
if (timelineName) {
timelineNames.push(timelineName);
// Remove timeline name from animation shorthand
// so the native implementation works with the rest of the properties
// Retain length of original name though, to play nice with multiple
// animations that might have been applied
rule.block.contents = rule.block.contents.replace(
timelineName,
" ".repeat(timelineName.length)
);
this.replacePart(
rule.block.startIndex,
rule.block.endIndex,
rule.block.contents,
p
);
}
// Save this animation only if there is a scroll timeline.
if (animationName && (r.timelineName || hasAnimationTimeline))
animationNames.push(animationName);

// If there is no duration, animationstart will not happen,
// and polyfill will not work which is based on animationstart.
// Add 1s as duration to fix this.
if(timelineName || hasAnimationTimeline) {
if(r.timelineName || hasAnimationTimeline) {
mehdi-kazemi marked this conversation as resolved.
Show resolved Hide resolved
if(!this.hasDuration(shorthand)) {
// TODO: Should keep track of whether duration is artificial or not,
// so that we can later track that we need to update timing to
// properly see duration as 'auto' for the polyfill.
rule.block.contents = rule.block.contents.replace(
"animation:",
"animation: 1s "
);
this.replacePart(
rule.block.startIndex,
rule.block.endIndex,
rule.block.contents,
p
shorthand, " 1s " + shorthand
);
shouldReplacePart = true;
}
}

if(r.toBeReplaced) {
// Remove timeline name from animation shorthand
// so the native implementation works with the rest of the properties
// Retain length of original name though, to play nice with multiple
// animations that might have been applied
rule.block.contents = rule.block.contents.replace(
r.toBeReplaced,
" ".repeat(r.toBeReplaced.length)
);
shouldReplacePart = true;
}
});
}

if(shouldReplacePart) {
this.replacePart(
rule.block.startIndex,
rule.block.endIndex,
rule.block.contents,
p
);
}

this.saveRelationInList(rule, timelineNames, animationNames);
}

Expand Down Expand Up @@ -401,13 +407,11 @@ export class StyleParser {
}

parseAnonymousTimeline(part) {
const openIndex = part.indexOf("(");
const closeIndex = part.indexOf(")");

if(!part.startsWith("scroll") || openIndex == -1 || closeIndex == -1)
const anonymousMatch = RegexMatcher.ANONYMOUS_SCROLL.exec(part);
if(!anonymousMatch)
return null;

const value = part.substring(openIndex+1, closeIndex);
const value = anonymousMatch[VALUES_CAPTURE_INDEX];
const options = {};
value.split(" ").forEach(token => {
if(TIMELINE_AXIS_TYPES.includes(token)) {
Expand All @@ -425,7 +429,20 @@ export class StyleParser {
}

extractTimelineName(shorthand) {
return this.findMatchingEntryInContainer(shorthand, this.scrollTimelineOptions);
let timelineName = null;
let toBeReplaced = null; // either timelineName or anonymousTimeline

const anonymousMatch = RegexMatcher.ANONYMOUS_SCROLL.exec(shorthand);
if(!anonymousMatch) {
timelineName = this.findMatchingEntryInContainer(shorthand, this.scrollTimelineOptions);
toBeReplaced = timelineName;
} else {
const anonymousTimeline = anonymousMatch[WHOLE_MATCH_INDEX];
timelineName = this.saveAnonymousTimelineName(anonymousTimeline);
toBeReplaced = anonymousTimeline;
}

return { timelineName, toBeReplaced };
}

findMatchingEntryInContainer(shorthand, container) {
Expand Down Expand Up @@ -589,6 +606,7 @@ export class StyleParser {
// If we are pointing past the end of the affected section, we need to
// recalculate the string pointer. Pointing to something inside the section
// that’s being replaced is undefined behavior. Sue me.

if (p.index >= end) {
const delta = p.index - end;
p.index = start + replacement.length + delta;
Expand Down
4 changes: 4 additions & 0 deletions src/scroll-timeline-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ function isDescendant(child, parent) {

function createScrollTimeline(anim, animationName, target) {
const animOptions = parser.getAnimationTimelineOptions(animationName, target);

if(!animOptions)
return null;

const timelineName = animOptions['animation-timeline'];
if(!timelineName) return null;

Expand Down