зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1383974
- Part 1: Display easing in keyframes. r=pbro
MozReview-Commit-ID: 8pIUMAurfS3 --HG-- extra : rebase_source : d7acb339c012bd67cb1a68975e6934afa995eb5c
This commit is contained in:
Родитель
f23bf7a153
Коммит
054392da3b
|
@ -462,7 +462,7 @@ function renderGraph(parentEl, state, totalDisplayedDuration, className, graphHe
|
|||
function renderDelay(parentEl, state, graphHelper) {
|
||||
const startSegment = graphHelper.getSegment(0);
|
||||
const endSegment = { x: state.delay, y: startSegment.y };
|
||||
graphHelper.appendPathElement(parentEl, [startSegment, endSegment], "delay-path");
|
||||
graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "delay-path");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -478,7 +478,7 @@ function renderFirstIteration(parentEl, state, mainIterationStartTime,
|
|||
const startTime = mainIterationStartTime;
|
||||
const endTime = startTime + firstSectionCount * state.duration;
|
||||
const segments = graphHelper.createPathSegments(startTime, endTime);
|
||||
graphHelper.appendPathElement(parentEl, segments, "iteration-path");
|
||||
graphHelper.appendShapePath(parentEl, segments, "iteration-path");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -499,7 +499,7 @@ function renderMiddleIterations(parentEl, state, mainIterationStartTime,
|
|||
const startTime = offset + i * state.duration;
|
||||
const endTime = startTime + state.duration;
|
||||
const segments = graphHelper.createPathSegments(startTime, endTime);
|
||||
graphHelper.appendPathElement(parentEl, segments, "iteration-path");
|
||||
graphHelper.appendShapePath(parentEl, segments, "iteration-path");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -520,7 +520,7 @@ function renderLastIteration(parentEl, state, mainIterationStartTime,
|
|||
(firstSectionCount + middleSectionCount) * state.duration;
|
||||
const endTime = startTime + lastSectionCount * state.duration;
|
||||
const segments = graphHelper.createPathSegments(startTime, endTime);
|
||||
graphHelper.appendPathElement(parentEl, segments, "iteration-path");
|
||||
graphHelper.appendShapePath(parentEl, segments, "iteration-path");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -552,7 +552,7 @@ function renderInfinity(parentEl, state, mainIterationStartTime,
|
|||
const firstEndTime = firstStartTime + state.duration;
|
||||
const firstSegments =
|
||||
graphHelper.createPathSegments(firstStartTime, firstEndTime);
|
||||
graphHelper.appendPathElement(parentEl, firstSegments, "iteration-path infinity");
|
||||
graphHelper.appendShapePath(parentEl, firstSegments, "iteration-path infinity");
|
||||
|
||||
// Append other iterations. We can copy first segments.
|
||||
const isAlternate = state.direction.match(/alternate/);
|
||||
|
@ -570,7 +570,7 @@ function renderInfinity(parentEl, state, mainIterationStartTime,
|
|||
return { x: segment.x - firstStartTime + startTime, y: segment.y };
|
||||
});
|
||||
}
|
||||
graphHelper.appendPathElement(parentEl, segments, "iteration-path infinity copied");
|
||||
graphHelper.appendShapePath(parentEl, segments, "iteration-path infinity copied");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -587,7 +587,7 @@ function renderEndDelay(parentEl, state,
|
|||
const startTime = mainIterationStartTime + iterationCount * state.duration;
|
||||
const startSegment = graphHelper.getSegment(startTime);
|
||||
const endSegment = { x: startTime + state.endDelay, y: startSegment.y };
|
||||
graphHelper.appendPathElement(parentEl, [startSegment, endSegment], "enddelay-path");
|
||||
graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "enddelay-path");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -605,8 +605,7 @@ function renderForwardsFill(parentEl, state, mainIterationStartTime,
|
|||
(state.endDelay > 0 ? state.endDelay : 0);
|
||||
const startSegment = graphHelper.getSegment(startTime);
|
||||
const endSegment = { x: totalDuration, y: startSegment.y };
|
||||
graphHelper.appendPathElement(parentEl, [startSegment, endSegment],
|
||||
"fill-forwards-path");
|
||||
graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "fill-forwards-path");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -620,7 +619,7 @@ function renderNegativeDelayHiddenProgress(parentEl, state, graphHelper) {
|
|||
const endTime = 0;
|
||||
const segments =
|
||||
graphHelper.createPathSegments(startTime, endTime);
|
||||
graphHelper.appendPathElement(parentEl, segments, "delay-path negative");
|
||||
graphHelper.appendShapePath(parentEl, segments, "delay-path negative");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -633,7 +632,7 @@ 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.appendPathElement(parentEl, segments, "enddelay-path negative");
|
||||
graphHelper.appendShapePath(parentEl, segments, "enddelay-path negative");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
const {createNode, createSVGNode} =
|
||||
require("devtools/client/animationinspector/utils");
|
||||
const {ProgressGraphHelper, getPreferredKeyframesProgressThreshold} =
|
||||
require("devtools/client/animationinspector/graph-helper.js");
|
||||
require("devtools/client/animationinspector/graph-helper.js");
|
||||
|
||||
// Counter for linearGradient ID.
|
||||
let LINEAR_GRADIENT_ID_COUNTER = 0;
|
||||
|
@ -55,22 +55,14 @@ Keyframes.prototype = {
|
|||
// so we use animation.state.duration as total duration.
|
||||
const totalDuration = animation.state.duration;
|
||||
|
||||
// Calculate stroke height in viewBox to display stroke of path.
|
||||
const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight;
|
||||
// Minimum segment duration is the duration of one pixel.
|
||||
const minSegmentDuration =
|
||||
totalDuration / this.containerEl.clientWidth;
|
||||
|
||||
// Set viewBox.
|
||||
graphEl.setAttribute("viewBox",
|
||||
`0 -${ 1 + strokeHeightForViewBox }
|
||||
${ totalDuration }
|
||||
${ 1 + strokeHeightForViewBox * 2 }`);
|
||||
|
||||
// Create graph helper to render the animation property graph.
|
||||
const win = this.containerEl.ownerGlobal;
|
||||
const graphHelper =
|
||||
new ProgressGraphHelper(this.containerEl.ownerDocument.defaultView,
|
||||
propertyName, animationType, keyframes, totalDuration);
|
||||
new ProgressGraphHelper(win, propertyName, animationType, keyframes, totalDuration);
|
||||
|
||||
renderPropertyGraph(graphEl, totalDuration, minSegmentDuration,
|
||||
getPreferredKeyframesProgressThreshold(keyframes), graphHelper);
|
||||
|
@ -78,6 +70,19 @@ Keyframes.prototype = {
|
|||
// Destroy ProgressGraphHelper resources.
|
||||
graphHelper.destroy();
|
||||
|
||||
// Set viewBox which includes invisible stroke width.
|
||||
// At first, calculate invisible stroke width from maximum width.
|
||||
// The reason why divide by 2 is that half of stroke width will be invisible
|
||||
// if we use 0 or 1 for y axis.
|
||||
const maxStrokeWidth =
|
||||
win.getComputedStyle(graphEl.querySelector(".keyframes svg .hint")).strokeWidth;
|
||||
const invisibleStrokeWidthInViewBox =
|
||||
maxStrokeWidth / 2 / this.containerEl.clientHeight;
|
||||
graphEl.setAttribute("viewBox",
|
||||
`0 -${ 1 + invisibleStrokeWidthInViewBox }
|
||||
${ totalDuration }
|
||||
${ 1 + invisibleStrokeWidthInViewBox * 2 }`);
|
||||
|
||||
// Append elements to display keyframe values.
|
||||
this.keyframesEl.classList.add(animation.state.type);
|
||||
for (let frame of this.keyframes) {
|
||||
|
@ -110,7 +115,8 @@ function renderPropertyGraph(parentEl, duration, minSegmentDuration,
|
|||
|
||||
const graphType = graphHelper.getGraphType();
|
||||
if (graphType !== "color") {
|
||||
graphHelper.appendPathElement(parentEl, segments, graphType);
|
||||
graphHelper.appendShapePath(parentEl, segments, graphType);
|
||||
renderEasingHint(parentEl, segments, graphHelper);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -118,7 +124,7 @@ function renderPropertyGraph(parentEl, duration, minSegmentDuration,
|
|||
segments.forEach(segment => {
|
||||
segment.y = 1;
|
||||
});
|
||||
const path = graphHelper.appendPathElement(parentEl, segments, graphType);
|
||||
const path = graphHelper.appendShapePath(parentEl, segments, graphType);
|
||||
const defEl = createSVGNode({
|
||||
parent: parentEl,
|
||||
nodeType: "def"
|
||||
|
@ -142,4 +148,105 @@ function renderPropertyGraph(parentEl, duration, minSegmentDuration,
|
|||
});
|
||||
});
|
||||
path.style.fill = `url(#${ id })`;
|
||||
|
||||
renderEasingHintForColor(parentEl, graphHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the easing hint.
|
||||
* This method renders an emphasized path over the easing path for a keyframe.
|
||||
* It appears when hovering over the easing.
|
||||
* It also renders a tooltip that appears when hovering.
|
||||
* @param {Element} parentEl - Parent element of this appended path element.
|
||||
* @param {Array} path segments - [{x: {Number} time, y: {Number} progress}, ...]
|
||||
* @param {ProgressGraphHelper} graphHelper - The object of ProgressGraphHelper.
|
||||
*/
|
||||
function renderEasingHint(parentEl, segments, helper) {
|
||||
const keyframes = helper.getKeyframes();
|
||||
const duration = helper.getDuration();
|
||||
|
||||
// Split segments for each keyframe.
|
||||
for (let i = 0, indexOfSegments = 0; i < keyframes.length - 1; i++) {
|
||||
const startKeyframe = keyframes[i];
|
||||
const startTime = startKeyframe.offset * duration;
|
||||
const endKeyframe = keyframes[i + 1];
|
||||
const endTime = endKeyframe.offset * duration;
|
||||
|
||||
const keyframeSegments = [];
|
||||
for (; indexOfSegments < segments.length; indexOfSegments++) {
|
||||
const segment = segments[indexOfSegments];
|
||||
if (segment.x < startTime) {
|
||||
// If previous easings were linear, we need to increment the indexOfSegments.
|
||||
continue;
|
||||
}
|
||||
if (segment.x > endTime) {
|
||||
indexOfSegments -= 1;
|
||||
break;
|
||||
}
|
||||
keyframeSegments.push(segment);
|
||||
}
|
||||
|
||||
// If keyframeSegments does not have segment which is at startTime,
|
||||
// get and set the segment.
|
||||
if (keyframeSegments[0].x !== startTime) {
|
||||
keyframeSegments.unshift(helper.getSegment(startTime));
|
||||
}
|
||||
// Also, endTime.
|
||||
if (keyframeSegments[keyframeSegments.length - 1].x !== endTime) {
|
||||
keyframeSegments.push(helper.getSegment(endTime));
|
||||
}
|
||||
|
||||
// Append easing hint as text and emphasis path.
|
||||
const gEl = createSVGNode({
|
||||
parent: parentEl,
|
||||
nodeType: "g"
|
||||
});
|
||||
createSVGNode({
|
||||
parent: gEl,
|
||||
nodeType: "title",
|
||||
textContent: startKeyframe.easing
|
||||
});
|
||||
helper.appendLinePath(gEl, keyframeSegments, `${helper.getGraphType()} hint`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render easing hint for properties that are represented by color.
|
||||
* This method render as text only.
|
||||
* @param {Element} parentEl - Parent element of this appended path element.
|
||||
* @param {ProgressGraphHelper} graphHelper - The object of ProgressGraphHalper.
|
||||
*/
|
||||
function renderEasingHintForColor(parentEl, helper) {
|
||||
const keyframes = helper.getKeyframes();
|
||||
const duration = helper.getDuration();
|
||||
|
||||
// Split segments for each keyframe.
|
||||
for (let i = 0; i < keyframes.length - 1; i++) {
|
||||
const startKeyframe = keyframes[i];
|
||||
const startTime = startKeyframe.offset * duration;
|
||||
const endKeyframe = keyframes[i + 1];
|
||||
const endTime = endKeyframe.offset * duration;
|
||||
|
||||
// Append easing hint.
|
||||
const gEl = createSVGNode({
|
||||
parent: parentEl,
|
||||
nodeType: "g"
|
||||
});
|
||||
createSVGNode({
|
||||
parent: gEl,
|
||||
nodeType: "title",
|
||||
textContent: startKeyframe.easing
|
||||
});
|
||||
createSVGNode({
|
||||
parent: gEl,
|
||||
nodeType: "rect",
|
||||
attributes: {
|
||||
x: startTime,
|
||||
y: -1,
|
||||
width: endTime - startTime,
|
||||
height: 1,
|
||||
class: "hint",
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,6 +85,22 @@ ProgressGraphHelper.prototype = {
|
|||
this.win = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return animation duration.
|
||||
* @return {Number} duration
|
||||
*/
|
||||
getDuration: function () {
|
||||
return this.animation.effect.timing.duration;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return animation's keyframe.
|
||||
* @return {Object} keyframe
|
||||
*/
|
||||
getKeyframes: function () {
|
||||
return this.keyframes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return graph type.
|
||||
* @return {String} if property is 'opacity' or 'transform', return that value.
|
||||
|
@ -248,14 +264,27 @@ ProgressGraphHelper.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Append path element.
|
||||
* Append path element as shape. Also, this method appends two segment
|
||||
* that are {start x, 0} and {end x, 0} to make shape.
|
||||
* @param {Element} parentEl - Parent element of this appended path element.
|
||||
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
|
||||
* @param {String} cls - Class name.
|
||||
* @return {Element} path element.
|
||||
*/
|
||||
appendPathElement: function (parentEl, pathSegments, cls) {
|
||||
return appendPathElement(parentEl, pathSegments, cls);
|
||||
appendShapePath: function (parentEl, pathSegments, cls) {
|
||||
return appendShapePath(parentEl, pathSegments, cls);
|
||||
},
|
||||
|
||||
/**
|
||||
* Append path element as line.
|
||||
* @param {Element} parentEl - Parent element of this appended path element.
|
||||
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
|
||||
* @param {String} cls - Class name.
|
||||
* @return {Element} path element.
|
||||
*/
|
||||
appendLinePath: function (parentEl, pathSegments, cls) {
|
||||
const isClosePathNeeded = false;
|
||||
return appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -271,7 +300,7 @@ exports.ProgressGraphHelper = ProgressGraphHelper;
|
|||
* setFillMode:
|
||||
* Animation fill-mode (e.g. "none", "backwards", "forwards" or "both")
|
||||
* setClosePathNeeded:
|
||||
* If true, appendPathElement make the last segment of <path> element to
|
||||
* If true, appendShapePath make the last segment of <path> element to
|
||||
* "close" segment("Z").
|
||||
* Therefore, if don't need under-line of graph, please set false.
|
||||
* setOriginalBehavior:
|
||||
|
@ -360,7 +389,7 @@ SummaryGraphHelper.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Set true if need to close path in appendPathElement.
|
||||
* Set true if need to close path in appendShapePath.
|
||||
* @param {bool} isClosePathNeeded - true: close, false: open.
|
||||
*/
|
||||
setClosePathNeeded: function (isClosePathNeeded) {
|
||||
|
@ -411,14 +440,15 @@ SummaryGraphHelper.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Append path element.
|
||||
* Append path element as shape. Also, this method appends two segment
|
||||
* that are {start x, 0} and {end x, 0} to make shape.
|
||||
* @param {Element} parentEl - Parent element of this appended path element.
|
||||
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
|
||||
* @param {String} cls - Class name.
|
||||
* @return {Element} path element.
|
||||
*/
|
||||
appendPathElement: function (parentEl, pathSegments, cls) {
|
||||
return appendPathElement(parentEl, pathSegments, cls, this.isClosePathNeeded);
|
||||
appendShapePath: function (parentEl, pathSegments, cls) {
|
||||
return appendShapePath(parentEl, pathSegments, cls, this.isClosePathNeeded);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -498,40 +528,50 @@ function createPathSegments(startTime, endTime, minSegmentDuration,
|
|||
}
|
||||
|
||||
/**
|
||||
* Append path element.
|
||||
* Append path element as shape. Also, this method appends two segment
|
||||
* that are {start x, 0} and {end x, 0} to make shape.
|
||||
* But does not affect given pathSegments.
|
||||
* @param {Element} parentEl - Parent element of this appended path element.
|
||||
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
|
||||
* @param {String} cls - Class name.
|
||||
* @param {bool} isClosePathNeeded - Set true if need to close the path. (default true)
|
||||
* @return {Element} path element.
|
||||
*/
|
||||
function appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded = true) {
|
||||
function appendShapePath(parentEl, pathSegments, cls, isClosePathNeeded = true) {
|
||||
const segments = [
|
||||
{ x: pathSegments[0].x, y: 0 },
|
||||
...pathSegments,
|
||||
{ x: pathSegments[pathSegments.length - 1].x, y: 0 }
|
||||
];
|
||||
return appendPathElement(parentEl, segments, cls, isClosePathNeeded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append path element.
|
||||
* @param {Element} parentEl - Parent element of this appended path element.
|
||||
* @param {Array} pathSegments - Path segments. Please see createPathSegments.
|
||||
* @param {String} cls - Class name.
|
||||
* @param {bool} isClosePathNeeded - Set true if need to close the path.
|
||||
* @return {Element} path element.
|
||||
*/
|
||||
function appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded) {
|
||||
// Create path string.
|
||||
let path = `M${ pathSegments[0].x },0`;
|
||||
for (let i = 0; i < pathSegments.length; i++) {
|
||||
const pathSegment = pathSegments[i];
|
||||
if (!pathSegment.easing || pathSegment.easing === "linear") {
|
||||
path += createLinePathString(pathSegment);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 1 === pathSegments.length) {
|
||||
// We already create steps or cubic-bezier path string in previous.
|
||||
break;
|
||||
}
|
||||
|
||||
const nextPathSegment = pathSegments[i + 1];
|
||||
let createPathFunction;
|
||||
if (pathSegment.easing.startsWith("steps")) {
|
||||
createPathFunction = createStepsPathString;
|
||||
} else if (pathSegment.easing.startsWith("frames")) {
|
||||
createPathFunction = createFramesPathString;
|
||||
let currentSegment = pathSegments[0];
|
||||
let path = `M${ currentSegment.x },${ currentSegment.y }`;
|
||||
for (let i = 1; i < pathSegments.length; i++) {
|
||||
const currentEasing = currentSegment.easing ? currentSegment.easing : "linear";
|
||||
const nextSegment = pathSegments[i];
|
||||
if (currentEasing === "linear") {
|
||||
path += createLinePathString(nextSegment);
|
||||
} else if (currentEasing.startsWith("steps")) {
|
||||
path += createStepsPathString(currentSegment, nextSegment);
|
||||
} else if (currentEasing.startsWith("frames")) {
|
||||
path += createFramesPathString(currentSegment, nextSegment);
|
||||
} else {
|
||||
createPathFunction = createCubicBezierPathString;
|
||||
path += createCubicBezierPathString(currentSegment, nextSegment);
|
||||
}
|
||||
path += createPathFunction(pathSegment, nextPathSegment);
|
||||
currentSegment = nextSegment;
|
||||
}
|
||||
path += ` L${ pathSegments[pathSegments.length - 1].x },0`;
|
||||
if (isClosePathNeeded) {
|
||||
path += " Z";
|
||||
}
|
||||
|
|
|
@ -697,6 +697,24 @@ body {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.keyframes svg .hint {
|
||||
stroke-opacity: 0;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 5;
|
||||
}
|
||||
|
||||
.keyframes svg path.hint {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.keyframes svg path.hint:hover {
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
.keyframes svg rect.hint {
|
||||
fill-opacity: .1;
|
||||
}
|
||||
|
||||
.animation-detail {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
|
Загрузка…
Ссылка в новой задаче