forked from mirrors/gecko-dev
MozReview-Commit-ID: F6xUXCgdRE4 --HG-- extra : rebase_source : 65de1b0aba412d9044b5196115f74276caa058f2
677 lines
25 KiB
JavaScript
677 lines
25 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
const EventEmitter = require("devtools/shared/event-emitter");
|
|
const {createNode, createSVGNode, TimeScale, getFormattedAnimationTitle} =
|
|
require("devtools/client/inspector/animation-old/utils");
|
|
const {SummaryGraphHelper, getPreferredKeyframesProgressThreshold,
|
|
getPreferredProgressThreshold} =
|
|
require("devtools/client/inspector/animation-old/graph-helper");
|
|
|
|
const { LocalizationHelper } = require("devtools/shared/l10n");
|
|
const L10N =
|
|
new LocalizationHelper("devtools/client/locales/animationinspector.properties");
|
|
|
|
// Show max 10 iterations for infinite animations
|
|
// to give users a clue that the animation does repeat.
|
|
const MAX_INFINITE_ANIMATIONS_ITERATIONS = 10;
|
|
|
|
// Minimum opacity for semitransparent fill color for keyframes's easing graph.
|
|
const MIN_KEYFRAMES_EASING_OPACITY = .5;
|
|
|
|
/**
|
|
* UI component responsible for displaying a single animation timeline, which
|
|
* basically looks like a rectangle that shows the delay and iterations.
|
|
*/
|
|
function AnimationTimeBlock() {
|
|
EventEmitter.decorate(this);
|
|
this.onClick = this.onClick.bind(this);
|
|
}
|
|
|
|
exports.AnimationTimeBlock = AnimationTimeBlock;
|
|
|
|
AnimationTimeBlock.prototype = {
|
|
init: function(containerEl) {
|
|
this.containerEl = containerEl;
|
|
this.containerEl.addEventListener("click", this.onClick);
|
|
},
|
|
|
|
destroy: function() {
|
|
this.containerEl.removeEventListener("click", this.onClick);
|
|
this.unrender();
|
|
this.containerEl = null;
|
|
this.animation = null;
|
|
},
|
|
|
|
unrender: function() {
|
|
while (this.containerEl.firstChild) {
|
|
this.containerEl.firstChild.remove();
|
|
}
|
|
},
|
|
|
|
render: function(animation, tracks) {
|
|
this.unrender();
|
|
|
|
this.animation = animation;
|
|
|
|
// Animation summary graph element.
|
|
const summaryEl = createSVGNode({
|
|
parent: this.containerEl,
|
|
nodeType: "svg",
|
|
attributes: {
|
|
"class": "summary",
|
|
"preserveAspectRatio": "none"
|
|
}
|
|
});
|
|
this.updateSummaryGraphViewBox(summaryEl);
|
|
|
|
const {state} = this.animation;
|
|
// Total displayed duration
|
|
const totalDisplayedDuration = this.getTotalDisplayedDuration();
|
|
// Minimum segment duration is the duration of one pixel.
|
|
const minSegmentDuration = totalDisplayedDuration / this.containerEl.clientWidth;
|
|
// Minimum progress threshold for effect timing.
|
|
const minEffectProgressThreshold = getPreferredProgressThreshold(state.easing);
|
|
|
|
// Render summary graph.
|
|
// The summary graph is constructed from keyframes's easing and effect timing.
|
|
const graphHelper = new SummaryGraphHelper(this.win, state, minSegmentDuration);
|
|
renderKeyframesEasingGraph(summaryEl, state, totalDisplayedDuration,
|
|
minEffectProgressThreshold, tracks, graphHelper);
|
|
if (state.easing !== "linear") {
|
|
renderEffectEasingGraph(summaryEl, state, totalDisplayedDuration,
|
|
minEffectProgressThreshold, graphHelper);
|
|
}
|
|
graphHelper.destroy();
|
|
|
|
// The animation name is displayed over the animation.
|
|
const nameEl = createNode({
|
|
parent: this.containerEl,
|
|
attributes: {
|
|
"class": "name",
|
|
"title": this.getTooltipText(state)
|
|
}
|
|
});
|
|
|
|
createSVGNode({
|
|
parent: createSVGNode({
|
|
parent: nameEl,
|
|
nodeType: "svg",
|
|
}),
|
|
nodeType: "text",
|
|
attributes: {
|
|
"y": "50%",
|
|
"x": "100%",
|
|
},
|
|
textContent: state.name
|
|
});
|
|
|
|
// Delay.
|
|
if (state.delay) {
|
|
// Negative delays need to start at 0.
|
|
const delayEl = createNode({
|
|
parent: this.containerEl,
|
|
attributes: {
|
|
"class": "delay"
|
|
+ (state.delay < 0 ? " negative" : " positive")
|
|
+ (state.fill === "both" ||
|
|
state.fill === "backwards" ? " fill" : "")
|
|
}
|
|
});
|
|
this.updateDelayBounds(delayEl);
|
|
}
|
|
|
|
// endDelay
|
|
if (state.iterationCount && state.endDelay) {
|
|
const endDelayEl = createNode({
|
|
parent: this.containerEl,
|
|
attributes: {
|
|
"class": "end-delay"
|
|
+ (state.endDelay < 0 ? " negative" : " positive")
|
|
+ (state.fill === "both" ||
|
|
state.fill === "forwards" ? " fill" : "")
|
|
}
|
|
});
|
|
this.updateEndDelayBounds(endDelayEl);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Update animation and updating its DOM accordingly.
|
|
* Unlike 'render' method, this method does not generate any elements, but update
|
|
* the bounds of DOM.
|
|
* @param {Object} animation
|
|
*/
|
|
update: function(animation) {
|
|
this.animation = animation;
|
|
this.updateSummaryGraphViewBox(this.containerEl.querySelector(".summary"));
|
|
const delayEl = this.containerEl.querySelector(".delay");
|
|
if (delayEl) {
|
|
this.updateDelayBounds(delayEl);
|
|
}
|
|
const endDelayEl = this.containerEl.querySelector(".end-delay");
|
|
if (endDelayEl) {
|
|
this.updateEndDelayBounds(endDelayEl);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Update viewBox and style of SVG element for summary graph to fit to latest
|
|
* TimeScale.
|
|
* @param {Element} summaryEl - SVG element for summary graph.
|
|
*/
|
|
updateSummaryGraphViewBox: function(summaryEl) {
|
|
const {x, delayW} = TimeScale.getAnimationDimensions(this.animation);
|
|
const totalDisplayedDuration = this.getTotalDisplayedDuration();
|
|
const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight;
|
|
const {state} = this.animation;
|
|
summaryEl.setAttribute("viewBox",
|
|
`${state.delay < 0 ? state.delay : 0} ` +
|
|
`-${1 + strokeHeightForViewBox} ` +
|
|
`${totalDisplayedDuration} ` +
|
|
`${1 + strokeHeightForViewBox * 2}`);
|
|
summaryEl.setAttribute("style", `left: ${ x - (state.delay > 0 ? delayW : 0) }%`);
|
|
},
|
|
|
|
/**
|
|
* Update bounds of element which represents delay to fit to latest TimeScale.
|
|
* @param {Element} delayEl - which represents delay.
|
|
*/
|
|
updateDelayBounds: function(delayEl) {
|
|
const {delayX, delayW} = TimeScale.getAnimationDimensions(this.animation);
|
|
delayEl.style.left = `${ delayX }%`;
|
|
delayEl.style.width = `${ delayW }%`;
|
|
},
|
|
|
|
/**
|
|
* Update bounds of element which represents endDelay to fit to latest TimeScale.
|
|
* @param {Element} endDelayEl - which represents endDelay.
|
|
*/
|
|
updateEndDelayBounds: function(endDelayEl) {
|
|
const {endDelayX, endDelayW} = TimeScale.getAnimationDimensions(this.animation);
|
|
endDelayEl.style.left = `${ endDelayX }%`;
|
|
endDelayEl.style.width = `${ endDelayW }%`;
|
|
},
|
|
|
|
getTotalDisplayedDuration: function() {
|
|
return this.animation.state.playbackRate * TimeScale.getDuration();
|
|
},
|
|
|
|
getTooltipText: function(state) {
|
|
const getTime = time => L10N.getFormatStr("player.timeLabel",
|
|
L10N.numberWithDecimals(time / 1000, 2));
|
|
|
|
let text = "";
|
|
|
|
// Adding the name.
|
|
text += getFormattedAnimationTitle({state});
|
|
text += "\n";
|
|
|
|
// Adding the delay.
|
|
if (state.delay) {
|
|
text += L10N.getStr("player.animationDelayLabel") + " ";
|
|
text += getTime(state.delay);
|
|
text += "\n";
|
|
}
|
|
|
|
// Adding the duration.
|
|
text += L10N.getStr("player.animationDurationLabel") + " ";
|
|
text += getTime(state.duration);
|
|
text += "\n";
|
|
|
|
// Adding the endDelay.
|
|
if (state.endDelay) {
|
|
text += L10N.getStr("player.animationEndDelayLabel") + " ";
|
|
text += getTime(state.endDelay);
|
|
text += "\n";
|
|
}
|
|
|
|
// Adding the iteration count (the infinite symbol, or an integer).
|
|
if (state.iterationCount !== 1) {
|
|
text += L10N.getStr("player.animationIterationCountLabel") + " ";
|
|
text += state.iterationCount ||
|
|
L10N.getStr("player.infiniteIterationCountText");
|
|
text += "\n";
|
|
}
|
|
|
|
// Adding the iteration start.
|
|
if (state.iterationStart !== 0) {
|
|
const iterationStartTime = state.iterationStart * state.duration / 1000;
|
|
text += L10N.getFormatStr("player.animationIterationStartLabel",
|
|
state.iterationStart,
|
|
L10N.numberWithDecimals(iterationStartTime, 2));
|
|
text += "\n";
|
|
}
|
|
|
|
// Adding the easing if it is not "linear".
|
|
if (state.easing && state.easing !== "linear") {
|
|
text += L10N.getStr("player.animationOverallEasingLabel") + " ";
|
|
text += state.easing;
|
|
text += "\n";
|
|
}
|
|
|
|
// Adding the fill mode.
|
|
if (state.fill) {
|
|
text += L10N.getStr("player.animationFillLabel") + " ";
|
|
text += state.fill;
|
|
text += "\n";
|
|
}
|
|
|
|
// Adding the direction mode if it is not "normal".
|
|
if (state.direction && state.direction !== "normal") {
|
|
text += L10N.getStr("player.animationDirectionLabel") + " ";
|
|
text += state.direction;
|
|
text += "\n";
|
|
}
|
|
|
|
// Adding the playback rate if it's different than 1.
|
|
if (state.playbackRate !== 1) {
|
|
text += L10N.getStr("player.animationRateLabel") + " ";
|
|
text += state.playbackRate;
|
|
text += "\n";
|
|
}
|
|
|
|
// Adding the animation-timing-function
|
|
// if it is not "ease" which is default value for CSS Animations.
|
|
if (state.animationTimingFunction && state.animationTimingFunction !== "ease") {
|
|
text += L10N.getStr("player.animationTimingFunctionLabel") + " ";
|
|
text += state.animationTimingFunction;
|
|
text += "\n";
|
|
}
|
|
|
|
// Adding a note that the animation is running on the compositor thread if
|
|
// needed.
|
|
if (state.propertyState) {
|
|
if (state.propertyState
|
|
.every(propState => propState.runningOnCompositor)) {
|
|
text += L10N.getStr("player.allPropertiesOnCompositorTooltip");
|
|
} else if (state.propertyState
|
|
.some(propState => propState.runningOnCompositor)) {
|
|
text += L10N.getStr("player.somePropertiesOnCompositorTooltip");
|
|
}
|
|
} else if (state.isRunningOnCompositor) {
|
|
text += L10N.getStr("player.runningOnCompositorTooltip");
|
|
}
|
|
|
|
return text;
|
|
},
|
|
|
|
onClick: function(e) {
|
|
e.stopPropagation();
|
|
this.emit("selected", this.animation);
|
|
},
|
|
|
|
get win() {
|
|
return this.containerEl.ownerDocument.defaultView;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Render keyframes's easing graph.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {float} totalDisplayedDuration - Displayable total duration.
|
|
* @param {float} minEffectProgressThreshold - Minimum progress threshold for effect.
|
|
* @param {Object} tracks - The value of AnimationsTimeline.getTracks().
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderKeyframesEasingGraph(parentEl, state, totalDisplayedDuration,
|
|
minEffectProgressThreshold, tracks, graphHelper) {
|
|
const keyframesList = getOffsetAndEasingOnlyKeyframesList(tracks);
|
|
const keyframeEasingOpacity = Math.max(1 / keyframesList.length,
|
|
MIN_KEYFRAMES_EASING_OPACITY);
|
|
for (const keyframes of keyframesList) {
|
|
const minProgressTreshold =
|
|
Math.min(minEffectProgressThreshold,
|
|
getPreferredKeyframesProgressThreshold(keyframes));
|
|
graphHelper.setMinProgressThreshold(minProgressTreshold);
|
|
graphHelper.setKeyframes(keyframes);
|
|
graphHelper.setClosePathNeeded(true);
|
|
const element = renderGraph(parentEl, state, totalDisplayedDuration,
|
|
"keyframes-easing", graphHelper);
|
|
element.style.opacity = keyframeEasingOpacity;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render effect easing graph.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {float} totalDisplayedDuration - Displayable total duration.
|
|
* @param {float} minEffectProgressThreshold - Minimum progress threshold for effect.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderEffectEasingGraph(parentEl, state, totalDisplayedDuration,
|
|
minEffectProgressThreshold, graphHelper) {
|
|
graphHelper.setMinProgressThreshold(minEffectProgressThreshold);
|
|
graphHelper.setKeyframes(null);
|
|
graphHelper.setClosePathNeeded(false);
|
|
renderGraph(parentEl, state, totalDisplayedDuration, "effect-easing", graphHelper);
|
|
}
|
|
|
|
/**
|
|
* Render a graph of given parameters.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {float} totalDisplayedDuration - Displayable total duration.
|
|
* @param {String} className - Class name for graph element.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderGraph(parentEl, state, totalDisplayedDuration, className, graphHelper) {
|
|
const graphEl = createSVGNode({
|
|
parent: parentEl,
|
|
nodeType: "g",
|
|
attributes: {
|
|
"class": className,
|
|
}
|
|
});
|
|
|
|
// Starting time of main iteration.
|
|
let mainIterationStartTime = 0;
|
|
let iterationStart = state.iterationStart;
|
|
let iterationCount = state.iterationCount ? state.iterationCount : Infinity;
|
|
|
|
graphHelper.setFillMode(state.fill);
|
|
graphHelper.setOriginalBehavior(true);
|
|
|
|
// Append delay.
|
|
if (state.delay > 0) {
|
|
renderDelay(graphEl, state, graphHelper);
|
|
mainIterationStartTime = state.delay;
|
|
} else {
|
|
const negativeDelayCount = -state.delay / state.duration;
|
|
// Move to forward the starting point for negative delay.
|
|
iterationStart += negativeDelayCount;
|
|
// Consume iteration count by negative delay.
|
|
if (iterationCount !== Infinity) {
|
|
iterationCount -= negativeDelayCount;
|
|
}
|
|
}
|
|
|
|
// Append 1st section of iterations,
|
|
// This section is only useful in cases where iterationStart has decimals.
|
|
// e.g.
|
|
// if { iterationStart: 0.25, iterations: 3 }, firstSectionCount is 0.75.
|
|
const firstSectionCount =
|
|
iterationStart % 1 === 0
|
|
? 0 : Math.min(iterationCount, 1) - iterationStart % 1;
|
|
if (firstSectionCount) {
|
|
renderFirstIteration(graphEl, state, mainIterationStartTime,
|
|
firstSectionCount, graphHelper);
|
|
}
|
|
|
|
if (iterationCount === Infinity) {
|
|
// If the animation repeats infinitely,
|
|
// we fill the remaining area with iteration paths.
|
|
renderInfinity(graphEl, state, mainIterationStartTime,
|
|
firstSectionCount, totalDisplayedDuration, graphHelper);
|
|
} else {
|
|
// Otherwise, we show remaining iterations, endDelay and fill.
|
|
|
|
// Append forwards fill-mode.
|
|
if (state.fill === "both" || state.fill === "forwards") {
|
|
renderForwardsFill(graphEl, state, mainIterationStartTime,
|
|
iterationCount, totalDisplayedDuration, graphHelper);
|
|
}
|
|
|
|
// Append middle section of iterations.
|
|
// e.g.
|
|
// if { iterationStart: 0.25, iterations: 3 }, middleSectionCount is 2.
|
|
const middleSectionCount =
|
|
Math.floor(iterationCount - firstSectionCount);
|
|
renderMiddleIterations(graphEl, state, mainIterationStartTime,
|
|
firstSectionCount, middleSectionCount, graphHelper);
|
|
|
|
// Append last section of iterations, if there is remaining iteration.
|
|
// e.g.
|
|
// if { iterationStart: 0.25, iterations: 3 }, lastSectionCount is 0.25.
|
|
const lastSectionCount =
|
|
iterationCount - middleSectionCount - firstSectionCount;
|
|
if (lastSectionCount) {
|
|
renderLastIteration(graphEl, state, mainIterationStartTime,
|
|
firstSectionCount, middleSectionCount,
|
|
lastSectionCount, graphHelper);
|
|
}
|
|
|
|
// Append endDelay.
|
|
if (state.endDelay > 0) {
|
|
renderEndDelay(graphEl, state,
|
|
mainIterationStartTime, iterationCount, graphHelper);
|
|
}
|
|
}
|
|
|
|
// Append negative delay (which overlap the animation).
|
|
if (state.delay < 0) {
|
|
graphHelper.setFillMode("both");
|
|
graphHelper.setOriginalBehavior(false);
|
|
renderNegativeDelayHiddenProgress(graphEl, state, graphHelper);
|
|
}
|
|
// Append negative endDelay (which overlap the animation).
|
|
if (state.iterationCount && state.endDelay < 0) {
|
|
graphHelper.setFillMode("both");
|
|
graphHelper.setOriginalBehavior(false);
|
|
renderNegativeEndDelayHiddenProgress(graphEl, state, graphHelper);
|
|
}
|
|
|
|
return graphEl;
|
|
}
|
|
|
|
/**
|
|
* Render delay section.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderDelay(parentEl, state, graphHelper) {
|
|
const startSegment = graphHelper.getSegment(0);
|
|
const endSegment = { x: state.delay, y: startSegment.y };
|
|
graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "delay-path");
|
|
}
|
|
|
|
/**
|
|
* Render first iteration section.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {Number} mainIterationStartTime - Starting time of main iteration.
|
|
* @param {Number} firstSectionCount - Iteration count of first section.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderFirstIteration(parentEl, state, mainIterationStartTime,
|
|
firstSectionCount, graphHelper) {
|
|
const startTime = mainIterationStartTime;
|
|
const endTime = startTime + firstSectionCount * state.duration;
|
|
const segments = graphHelper.createPathSegments(startTime, endTime);
|
|
graphHelper.appendShapePath(parentEl, segments, "iteration-path");
|
|
}
|
|
|
|
/**
|
|
* Render middle iterations section.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {Number} mainIterationStartTime - Starting time of main iteration.
|
|
* @param {Number} firstSectionCount - Iteration count of first section.
|
|
* @param {Number} middleSectionCount - Iteration count of middle section.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderMiddleIterations(parentEl, state, mainIterationStartTime,
|
|
firstSectionCount, middleSectionCount,
|
|
graphHelper) {
|
|
const offset = mainIterationStartTime + firstSectionCount * state.duration;
|
|
for (let i = 0; i < middleSectionCount; i++) {
|
|
// Get the path segments of each iteration.
|
|
const startTime = offset + i * state.duration;
|
|
const endTime = startTime + state.duration;
|
|
const segments = graphHelper.createPathSegments(startTime, endTime);
|
|
graphHelper.appendShapePath(parentEl, segments, "iteration-path");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render last iteration section.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {Number} mainIterationStartTime - Starting time of main iteration.
|
|
* @param {Number} firstSectionCount - Iteration count of first section.
|
|
* @param {Number} middleSectionCount - Iteration count of middle section.
|
|
* @param {Number} lastSectionCount - Iteration count of last section.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderLastIteration(parentEl, state, mainIterationStartTime,
|
|
firstSectionCount, middleSectionCount,
|
|
lastSectionCount, graphHelper) {
|
|
const startTime = mainIterationStartTime +
|
|
(firstSectionCount + middleSectionCount) * state.duration;
|
|
const endTime = startTime + lastSectionCount * state.duration;
|
|
const segments = graphHelper.createPathSegments(startTime, endTime);
|
|
graphHelper.appendShapePath(parentEl, segments, "iteration-path");
|
|
}
|
|
|
|
/**
|
|
* Render Infinity iterations.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {Number} mainIterationStartTime - Starting time of main iteration.
|
|
* @param {Number} firstSectionCount - Iteration count of first section.
|
|
* @param {Number} totalDuration - Displayed max duration.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderInfinity(parentEl, state, mainIterationStartTime,
|
|
firstSectionCount, totalDuration, graphHelper) {
|
|
// Calculate the number of iterations to display,
|
|
// with a maximum of MAX_INFINITE_ANIMATIONS_ITERATIONS
|
|
let uncappedInfinityIterationCount =
|
|
(totalDuration - firstSectionCount * state.duration) / state.duration;
|
|
// If there is a small floating point error resulting in, e.g. 1.0000001
|
|
// ceil will give us 2 so round first.
|
|
uncappedInfinityIterationCount =
|
|
parseFloat(uncappedInfinityIterationCount.toPrecision(6));
|
|
const infinityIterationCount =
|
|
Math.min(MAX_INFINITE_ANIMATIONS_ITERATIONS,
|
|
Math.ceil(uncappedInfinityIterationCount));
|
|
|
|
// Append first full iteration path.
|
|
const firstStartTime =
|
|
mainIterationStartTime + firstSectionCount * state.duration;
|
|
const firstEndTime = firstStartTime + state.duration;
|
|
const firstSegments =
|
|
graphHelper.createPathSegments(firstStartTime, firstEndTime);
|
|
graphHelper.appendShapePath(parentEl, firstSegments, "iteration-path infinity");
|
|
|
|
// Append other iterations. We can copy first segments.
|
|
const isAlternate = state.direction.match(/alternate/);
|
|
for (let i = 1; i < infinityIterationCount; i++) {
|
|
const startTime = firstStartTime + i * state.duration;
|
|
let segments;
|
|
if (isAlternate && i % 2) {
|
|
// Copy as reverse.
|
|
segments = firstSegments.map(segment => {
|
|
return { x: firstEndTime - segment.x + startTime, y: segment.y };
|
|
});
|
|
} else {
|
|
// Copy as is.
|
|
segments = firstSegments.map(segment => {
|
|
return { x: segment.x - firstStartTime + startTime, y: segment.y };
|
|
});
|
|
}
|
|
graphHelper.appendShapePath(parentEl, segments, "iteration-path infinity copied");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render endDelay section.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {Number} mainIterationStartTime - Starting time of main iteration.
|
|
* @param {Number} iterationCount - Whole iteration count.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderEndDelay(parentEl, state,
|
|
mainIterationStartTime, iterationCount, graphHelper) {
|
|
const startTime = mainIterationStartTime + iterationCount * state.duration;
|
|
const startSegment = graphHelper.getSegment(startTime);
|
|
const endSegment = { x: startTime + state.endDelay, y: startSegment.y };
|
|
graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "enddelay-path");
|
|
}
|
|
|
|
/**
|
|
* Render forwards fill section.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {Number} mainIterationStartTime - Starting time of main iteration.
|
|
* @param {Number} iterationCount - Whole iteration count.
|
|
* @param {Number} totalDuration - Displayed max duration.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderForwardsFill(parentEl, state, mainIterationStartTime,
|
|
iterationCount, totalDuration, graphHelper) {
|
|
const startTime = mainIterationStartTime + iterationCount * state.duration +
|
|
(state.endDelay > 0 ? state.endDelay : 0);
|
|
const startSegment = graphHelper.getSegment(startTime);
|
|
const endSegment = { x: totalDuration, y: startSegment.y };
|
|
graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "fill-forwards-path");
|
|
}
|
|
|
|
/**
|
|
* Render hidden progress of negative delay.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderNegativeDelayHiddenProgress(parentEl, state, graphHelper) {
|
|
const startTime = state.delay;
|
|
const endTime = 0;
|
|
const segments =
|
|
graphHelper.createPathSegments(startTime, endTime);
|
|
graphHelper.appendShapePath(parentEl, segments, "delay-path negative");
|
|
}
|
|
|
|
/**
|
|
* Render hidden progress of negative endDelay.
|
|
* @param {Element} parentEl - Parent element of this appended path element.
|
|
* @param {Object} state - State of animation.
|
|
* @param {Object} graphHelper - SummaryGraphHelper.
|
|
*/
|
|
function renderNegativeEndDelayHiddenProgress(parentEl, state, graphHelper) {
|
|
const endTime = state.delay + state.iterationCount * state.duration;
|
|
const startTime = endTime + state.endDelay;
|
|
const segments = graphHelper.createPathSegments(startTime, endTime);
|
|
graphHelper.appendShapePath(parentEl, segments, "enddelay-path negative");
|
|
}
|
|
|
|
/**
|
|
* Create new keyframes object which has only offset and easing.
|
|
* Also, the returned value has no duplication.
|
|
* @param {Object} tracks - The value of AnimationsTimeline.getTracks().
|
|
* @return {Array} keyframes list.
|
|
*/
|
|
function getOffsetAndEasingOnlyKeyframesList(tracks) {
|
|
return Object.keys(tracks).reduce((result, name) => {
|
|
const track = tracks[name];
|
|
const exists = result.find(keyframes => {
|
|
if (track.length !== keyframes.length) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < track.length; i++) {
|
|
const keyframe1 = track[i];
|
|
const keyframe2 = keyframes[i];
|
|
if (keyframe1.offset !== keyframe2.offset ||
|
|
keyframe1.easing !== keyframe2.easing) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
if (!exists) {
|
|
const keyframes = track.map(keyframe => {
|
|
return { offset: keyframe.offset, easing: keyframe.easing };
|
|
});
|
|
result.push(keyframes);
|
|
}
|
|
return result;
|
|
}, []);
|
|
}
|