зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1210796 - Part 2: Visualize each properties. r=pbro
MozReview-Commit-ID: Hjb1QyOMNZR --HG-- extra : rebase_source : 8ee1a7249453ed701f40c5f0a8fc0118cee8dd39
This commit is contained in:
Родитель
088af889f0
Коммит
9a2f64b9e1
|
@ -100,6 +100,8 @@ var getServerTraits = Task.async(function* (target) {
|
|||
method: "getProperties" },
|
||||
{ name: "hasSetWalkerActor", actor: "animations",
|
||||
method: "setWalkerActor" },
|
||||
{ name: "hasGetAnimationTypes", actor: "animationplayer",
|
||||
method: "getAnimationTypes" },
|
||||
];
|
||||
|
||||
let traits = {};
|
||||
|
|
|
@ -33,7 +33,8 @@ exports.AnimationDetails = AnimationDetails;
|
|||
AnimationDetails.prototype = {
|
||||
// These are part of frame objects but are not animated properties. This
|
||||
// array is used to skip them.
|
||||
NON_PROPERTIES: ["easing", "composite", "computedOffset", "offset"],
|
||||
NON_PROPERTIES: ["easing", "composite", "computedOffset",
|
||||
"offset", "simulateComputeValuesFailure"],
|
||||
|
||||
init: function (containerEl) {
|
||||
this.containerEl = containerEl;
|
||||
|
@ -108,8 +109,9 @@ AnimationDetails.prototype = {
|
|||
tracks[name] = [];
|
||||
}
|
||||
|
||||
for (let {value, offset} of values) {
|
||||
tracks[name].push({value, offset});
|
||||
for (let {value, offset, easing, distance} of values) {
|
||||
distance = distance ? distance : 0;
|
||||
tracks[name].push({value, offset, easing, distance});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -120,13 +122,18 @@ AnimationDetails.prototype = {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!tracks[name]) {
|
||||
tracks[name] = [];
|
||||
// We have to change to CSS property name
|
||||
// since GetKeyframes returns JS property name.
|
||||
const propertyCSSName = getCssPropertyName(name);
|
||||
if (!tracks[propertyCSSName]) {
|
||||
tracks[propertyCSSName] = [];
|
||||
}
|
||||
|
||||
tracks[name].push({
|
||||
tracks[propertyCSSName].push({
|
||||
value: frame[name],
|
||||
offset: frame.computedOffset
|
||||
offset: frame.computedOffset,
|
||||
easing: frame.easing,
|
||||
distance: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -135,6 +142,26 @@ AnimationDetails.prototype = {
|
|||
return tracks;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get animation types of given CSS property names.
|
||||
* @param {Array} CSS property names.
|
||||
* e.g. ["background-color", "opacity", ...]
|
||||
* @return {Object} Animation type mapped with CSS property name.
|
||||
* e.g. { "background-color": "color", }
|
||||
* "opacity": "float", ... }
|
||||
*/
|
||||
getAnimationTypes: Task.async(function* (propertyNames) {
|
||||
if (this.serverTraits.hasGetAnimationTypes) {
|
||||
return yield this.animation.getAnimationTypes(propertyNames);
|
||||
}
|
||||
// Set animation type 'none' since does not support getAnimationTypes.
|
||||
const animationTypes = {};
|
||||
propertyNames.forEach(propertyName => {
|
||||
animationTypes[propertyName] = "none";
|
||||
});
|
||||
return Promise.resolve(animationTypes);
|
||||
}),
|
||||
|
||||
render: Task.async(function* (animation) {
|
||||
this.unrender();
|
||||
|
||||
|
@ -152,8 +179,8 @@ AnimationDetails.prototype = {
|
|||
// Build an element for each animated property track.
|
||||
this.tracks = yield this.getTracks(animation, this.serverTraits);
|
||||
|
||||
// Useful for tests to know when the keyframes have been retrieved.
|
||||
this.emit("keyframes-retrieved");
|
||||
// Get animation type for each CSS properties.
|
||||
const animationTypes = yield this.getAnimationTypes(Object.keys(this.tracks));
|
||||
|
||||
for (let propertyName in this.tracks) {
|
||||
let line = createNode({
|
||||
|
@ -196,12 +223,16 @@ AnimationDetails.prototype = {
|
|||
keyframesComponent.render({
|
||||
keyframes: this.tracks[propertyName],
|
||||
propertyName: propertyName,
|
||||
animation: animation
|
||||
animation: animation,
|
||||
animationType: animationTypes[propertyName]
|
||||
});
|
||||
keyframesComponent.on("frame-selected", this.onFrameSelected);
|
||||
|
||||
this.keyframeComponents.push(keyframesComponent);
|
||||
}
|
||||
|
||||
// Useful for tests to know when rendering of all animation detail UIs
|
||||
// have been completed.
|
||||
this.emit("animation-detail-rendering-completed");
|
||||
}),
|
||||
|
||||
onFrameSelected: function (e, args) {
|
||||
|
|
|
@ -7,10 +7,17 @@
|
|||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {createNode} = require("devtools/client/animationinspector/utils");
|
||||
const {createNode, createSVGNode} =
|
||||
require("devtools/client/animationinspector/utils");
|
||||
const {ProgressGraphHelper, appendPathElement, DEFAULT_MIN_PROGRESS_THRESHOLD} =
|
||||
require("devtools/client/animationinspector/graph-helper.js");
|
||||
|
||||
// Counter for linearGradient ID.
|
||||
let LINEAR_GRADIENT_ID_COUNTER = 0;
|
||||
|
||||
/**
|
||||
* UI component responsible for displaying a list of keyframes.
|
||||
* Also, shows a graphical graph for the animation progress of one iteration.
|
||||
*/
|
||||
function Keyframes() {
|
||||
EventEmitter.decorate(this);
|
||||
|
@ -37,7 +44,7 @@ Keyframes.prototype = {
|
|||
this.containerEl = this.keyframesEl = this.animation = null;
|
||||
},
|
||||
|
||||
render: function ({keyframes, propertyName, animation}) {
|
||||
render: function ({keyframes, propertyName, animation, animationType}) {
|
||||
this.keyframes = keyframes;
|
||||
this.propertyName = propertyName;
|
||||
this.animation = animation;
|
||||
|
@ -47,6 +54,43 @@ Keyframes.prototype = {
|
|||
? 0
|
||||
: 1 - animation.state.iterationStart % 1;
|
||||
|
||||
// Create graph element.
|
||||
const graphEl = createSVGNode({
|
||||
parent: this.keyframesEl,
|
||||
nodeType: "svg",
|
||||
attributes: {
|
||||
"preserveAspectRatio": "none"
|
||||
}
|
||||
});
|
||||
|
||||
// This visual is only one iteration,
|
||||
// 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 graphHelper =
|
||||
new ProgressGraphHelper(this.containerEl.ownerDocument.defaultView,
|
||||
propertyName, animationType, keyframes, totalDuration);
|
||||
|
||||
renderPropertyGraph(graphEl, totalDuration, minSegmentDuration,
|
||||
DEFAULT_MIN_PROGRESS_THRESHOLD, graphHelper);
|
||||
|
||||
// Destroy ProgressGraphHelper resources.
|
||||
graphHelper.destroy();
|
||||
|
||||
// Append elements to display keyframe values.
|
||||
this.keyframesEl.classList.add(animation.state.type);
|
||||
for (let frame of this.keyframes) {
|
||||
let offset = frame.offset + iterationStartOffset;
|
||||
|
@ -79,3 +123,52 @@ Keyframes.prototype = {
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Render a graph representing the progress of the animation over one iteration.
|
||||
* @param {Element} parentEl - Parent element of this appended path element.
|
||||
* @param {Number} duration - Duration of one iteration.
|
||||
* @param {Number} minSegmentDuration - Minimum segment duration.
|
||||
* @param {Number} minProgressThreshold - Minimum progress threshold.
|
||||
* @param {ProgressGraphHelper} graphHelper - The object of ProgressGraphHalper.
|
||||
*/
|
||||
function renderPropertyGraph(parentEl, duration, minSegmentDuration,
|
||||
minProgressThreshold, graphHelper) {
|
||||
const segments = graphHelper.createPathSegments(0, duration, minSegmentDuration,
|
||||
minProgressThreshold);
|
||||
|
||||
const graphType = graphHelper.getGraphType();
|
||||
if (graphType !== "color") {
|
||||
appendPathElement(parentEl, segments, graphType);
|
||||
return;
|
||||
}
|
||||
|
||||
// Append the color to the path.
|
||||
segments.forEach(segment => {
|
||||
segment.y = 1;
|
||||
});
|
||||
const path = appendPathElement(parentEl, segments, graphType);
|
||||
const defEl = createSVGNode({
|
||||
parent: parentEl,
|
||||
nodeType: "def"
|
||||
});
|
||||
const id = `color-property-${ LINEAR_GRADIENT_ID_COUNTER++ }`;
|
||||
const linearGradientEl = createSVGNode({
|
||||
parent: defEl,
|
||||
nodeType: "linearGradient",
|
||||
attributes: {
|
||||
"id": id
|
||||
}
|
||||
});
|
||||
segments.forEach(segment => {
|
||||
createSVGNode({
|
||||
parent: linearGradientEl,
|
||||
nodeType: "stop",
|
||||
attributes: {
|
||||
"stop-color": segment.style,
|
||||
"offset": segment.x / duration
|
||||
}
|
||||
});
|
||||
});
|
||||
path.style.fill = `url(#${ id })`;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,461 @@
|
|||
/* -*- 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 {createSVGNode, getJsPropertyName} =
|
||||
require("devtools/client/animationinspector/utils");
|
||||
const {colorUtils} = require("devtools/shared/css/color.js");
|
||||
const {parseTimingFunction} = require("devtools/client/shared/widgets/CubicBezierWidget");
|
||||
|
||||
// In the createPathSegments function, an animation duration is divided by
|
||||
// DURATION_RESOLUTION in order to draw the way the animation progresses.
|
||||
// But depending on the timing-function, we may be not able to make the graph
|
||||
// smoothly progress if this resolution is not high enough.
|
||||
// So, if the difference of animation progress between 2 divisions is more than
|
||||
// DEFAULT_MIN_PROGRESS_THRESHOLD, then createPathSegments re-divides
|
||||
// by DURATION_RESOLUTION.
|
||||
// DURATION_RESOLUTION shoud be integer and more than 2.
|
||||
const DURATION_RESOLUTION = 4;
|
||||
// DEFAULT_MIN_PROGRESS_THRESHOLD shoud be between more than 0 to 1.
|
||||
const DEFAULT_MIN_PROGRESS_THRESHOLD = 0.1;
|
||||
exports.DEFAULT_MIN_PROGRESS_THRESHOLD = DEFAULT_MIN_PROGRESS_THRESHOLD;
|
||||
// BOUND_EXCLUDING_TIME should be less than 1ms and is used to exclude start
|
||||
// and end bounds when dividing duration in createPathSegments.
|
||||
const BOUND_EXCLUDING_TIME = 0.001;
|
||||
|
||||
/**
|
||||
* This helper return the segment coordinates and style for property graph,
|
||||
* also return the graph type.
|
||||
* Parameters of constructor are below.
|
||||
* @param {Window} win - window object to animate.
|
||||
* @param {String} propertyCSSName - CSS property name (e.g. background-color).
|
||||
* @param {String} animationType - Animation type of CSS property.
|
||||
* @param {Object} keyframes - AnimationInspector's keyframes object.
|
||||
* @param {float} duration - Duration of animation.
|
||||
*/
|
||||
function ProgressGraphHelper(win, propertyCSSName, animationType, keyframes, duration) {
|
||||
this.win = win;
|
||||
const doc = this.win.document;
|
||||
this.targetEl = doc.createElement("div");
|
||||
doc.documentElement.appendChild(this.targetEl);
|
||||
|
||||
this.propertyCSSName = propertyCSSName;
|
||||
this.propertyJSName = getJsPropertyName(this.propertyCSSName);
|
||||
this.animationType = animationType;
|
||||
|
||||
// Create keyframe object to make dummy animation.
|
||||
const keyframesObject = keyframes.map(keyframe => {
|
||||
const keyframeObject = Object.assign({}, keyframe);
|
||||
keyframeObject[this.propertyJSName] = keyframe.value;
|
||||
return keyframeObject;
|
||||
});
|
||||
|
||||
// Create effect timing object to make dummy animation.
|
||||
const effectTiming = {
|
||||
duration: duration,
|
||||
fill: "forwards"
|
||||
};
|
||||
|
||||
this.keyframes = keyframesObject;
|
||||
this.devtoolsKeyframes = keyframes;
|
||||
this.animation = this.targetEl.animate(this.keyframes, effectTiming);
|
||||
this.animation.pause();
|
||||
this.valueHelperFunction = this.getValueHelperFunction();
|
||||
}
|
||||
|
||||
ProgressGraphHelper.prototype = {
|
||||
/**
|
||||
* Destory this object.
|
||||
*/
|
||||
destroy: function () {
|
||||
this.targetEl.remove();
|
||||
this.animation.cancel();
|
||||
|
||||
this.targetEl = null;
|
||||
this.animation = null;
|
||||
this.valueHelperFunction = null;
|
||||
this.propertyCSSName = null;
|
||||
this.propertyJSName = null;
|
||||
this.animationType = null;
|
||||
this.keyframes = null;
|
||||
this.win = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return graph type.
|
||||
* @return {String} if property is 'opacity' or 'transform', return that value.
|
||||
* Otherwise, return given animation type in constructor.
|
||||
*/
|
||||
getGraphType: function () {
|
||||
return (this.propertyJSName === "opacity" || this.propertyJSName === "transform")
|
||||
? this.propertyJSName : this.animationType;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a segment in graph by given the time.
|
||||
* @return {Object} Computed result which has follwing values.
|
||||
* - x: x value of graph (float)
|
||||
* - y: y value of graph (float between 0 - 1)
|
||||
* - style: the computed style value of the property at the time
|
||||
*/
|
||||
getSegment: function (time) {
|
||||
this.animation.currentTime = time;
|
||||
const style = this.win.getComputedStyle(this.targetEl)[this.propertyJSName];
|
||||
const value = this.valueHelperFunction(style);
|
||||
return { x: time, y: value, style: style };
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a value helper function which calculates the value of Y axis by animation type.
|
||||
* @return {function} ValueHelperFunction returns float value of Y axis
|
||||
* from given progress and style (e.g. rgb(0, 0, 0))
|
||||
*/
|
||||
getValueHelperFunction: function () {
|
||||
switch (this.animationType) {
|
||||
case "none": {
|
||||
return () => 1;
|
||||
}
|
||||
case "float": {
|
||||
return this.getFloatValueHelperFunction();
|
||||
}
|
||||
case "coord": {
|
||||
return this.getCoordinateValueHelperFunction();
|
||||
}
|
||||
case "color": {
|
||||
return this.getColorValueHelperFunction();
|
||||
}
|
||||
case "discrete": {
|
||||
return this.getDiscreteValueHelperFunction();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return value helper function of animation type 'float'.
|
||||
* @param {Object} keyframes - This object shoud be same as
|
||||
* the parameter of getGraphHelper.
|
||||
* @return {function} ValueHelperFunction returns float value of Y axis
|
||||
* from given float (e.g. 1.0, 0.5 and so on)
|
||||
*/
|
||||
getFloatValueHelperFunction: function () {
|
||||
let maxValue = 0;
|
||||
let minValue = Infinity;
|
||||
this.keyframes.forEach(keyframe => {
|
||||
maxValue = Math.max(maxValue, keyframe.value);
|
||||
minValue = Math.min(minValue, keyframe.value);
|
||||
});
|
||||
const distance = maxValue - minValue;
|
||||
return value => {
|
||||
return (value - minValue) / distance;
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Return value helper function of animation type 'coord'.
|
||||
* @return {function} ValueHelperFunction returns float value of Y axis
|
||||
* from given style (e.g. 100px)
|
||||
*/
|
||||
getCoordinateValueHelperFunction: function () {
|
||||
let maxValue = 0;
|
||||
let minValue = Infinity;
|
||||
for (let i = 0, n = this.keyframes.length; i < n; i++) {
|
||||
if (this.keyframes[i].value.match(/calc/)) {
|
||||
return null;
|
||||
}
|
||||
const value = parseFloat(this.keyframes[i].value);
|
||||
minValue = Math.min(minValue, value);
|
||||
maxValue = Math.max(maxValue, value);
|
||||
}
|
||||
const distance = maxValue - minValue;
|
||||
return value => {
|
||||
return (parseFloat(value) - minValue) / distance;
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Return value helper function of animation type 'color'.
|
||||
* @param {Object} keyframes - This object shoud be same as
|
||||
* the parameter of getGraphHelper.
|
||||
* @return {function} ValueHelperFunction returns float value of Y axis
|
||||
* from given color (e.g. rgb(0, 0, 0))
|
||||
*/
|
||||
getColorValueHelperFunction: function () {
|
||||
const maxObject = { distance: 0 };
|
||||
for (let i = 0; i < this.keyframes.length - 1; i++) {
|
||||
const value1 = getRGBA(this.keyframes[i].value);
|
||||
for (let j = i + 1; j < this.keyframes.length; j++) {
|
||||
const value2 = getRGBA(this.keyframes[j].value);
|
||||
const distance = getRGBADistance(value1, value2);
|
||||
if (maxObject.distance >= distance) {
|
||||
continue;
|
||||
}
|
||||
maxObject.distance = distance;
|
||||
maxObject.value1 = value1;
|
||||
maxObject.value2 = value2;
|
||||
}
|
||||
}
|
||||
const baseValue =
|
||||
maxObject.value1 < maxObject.value2 ? maxObject.value1 : maxObject.value2;
|
||||
|
||||
return value => {
|
||||
const colorValue = getRGBA(value);
|
||||
return getRGBADistance(baseValue, colorValue) / maxObject.distance;
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Return value helper function of animation type 'discrete'.
|
||||
* @return {function} ValueHelperFunction returns float value of Y axis
|
||||
* from given style (e.g. center)
|
||||
*/
|
||||
getDiscreteValueHelperFunction: function () {
|
||||
const discreteValues = [];
|
||||
this.keyframes.forEach(keyframe => {
|
||||
if (!discreteValues.includes(keyframe.value)) {
|
||||
discreteValues.push(keyframe.value);
|
||||
}
|
||||
});
|
||||
return value => {
|
||||
return discreteValues.indexOf(value) / (discreteValues.length - 1);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Create the path segments from given parameters.
|
||||
* @param {Number} startTime - Starting time of animation.
|
||||
* @param {Number} endTime - Ending time of animation.
|
||||
* @param {Number} minSegmentDuration - Minimum segment duration.
|
||||
* @param {Number} minProgressThreshold - Minimum progress threshold.
|
||||
* @return {Array} path segments -
|
||||
* [{x: {Number} time, y: {Number} progress}, ...]
|
||||
*/
|
||||
createPathSegments: function (startTime, endTime,
|
||||
minSegmentDuration, minProgressThreshold) {
|
||||
return !this.valueHelperFunction
|
||||
? createKeyframesPathSegments(endTime - startTime, this.devtoolsKeyframes)
|
||||
: createPathSegments(startTime, endTime,
|
||||
minSegmentDuration, minProgressThreshold, this);
|
||||
},
|
||||
};
|
||||
|
||||
exports.ProgressGraphHelper = ProgressGraphHelper;
|
||||
|
||||
/**
|
||||
* Create the path segments from given parameters.
|
||||
* @param {Number} startTime - Starting time of animation.
|
||||
* @param {Number} endTime - Ending time of animation.
|
||||
* @param {Number} minSegmentDuration - Minimum segment duration.
|
||||
* @param {Number} minProgressThreshold - Minimum progress threshold.
|
||||
* @param {Object} segmentHelper
|
||||
* - getSegment(time): Helper function that, given a time,
|
||||
* will calculate the animation progress.
|
||||
* @return {Array} path segments -
|
||||
* [{x: {Number} time, y: {Number} progress}, ...]
|
||||
*/
|
||||
function createPathSegments(startTime, endTime, minSegmentDuration,
|
||||
minProgressThreshold, segmentHelper) {
|
||||
// If the duration is too short, early return.
|
||||
if (endTime - startTime < minSegmentDuration) {
|
||||
return [segmentHelper.getSegment(startTime),
|
||||
segmentHelper.getSegment(endTime)];
|
||||
}
|
||||
|
||||
// Otherwise, start creating segments.
|
||||
let pathSegments = [];
|
||||
|
||||
// Append the segment for the startTime position.
|
||||
const startTimeSegment = segmentHelper.getSegment(startTime);
|
||||
pathSegments.push(startTimeSegment);
|
||||
let previousSegment = startTimeSegment;
|
||||
|
||||
// Split the duration in equal intervals, and iterate over them.
|
||||
// See the definition of DURATION_RESOLUTION for more information about this.
|
||||
const interval = (endTime - startTime) / DURATION_RESOLUTION;
|
||||
for (let index = 1; index <= DURATION_RESOLUTION; index++) {
|
||||
// Create a segment for this interval.
|
||||
const currentSegment =
|
||||
segmentHelper.getSegment(startTime + index * interval);
|
||||
|
||||
// If the distance between the Y coordinate (the animation's progress) of
|
||||
// the previous segment and the Y coordinate of the current segment is too
|
||||
// large, then recurse with a smaller duration to get more details
|
||||
// in the graph.
|
||||
if (Math.abs(currentSegment.y - previousSegment.y) > minProgressThreshold) {
|
||||
// Divide the current interval (excluding start and end bounds
|
||||
// by adding/subtracting BOUND_EXCLUDING_TIME).
|
||||
pathSegments = pathSegments.concat(
|
||||
createPathSegments(previousSegment.x + BOUND_EXCLUDING_TIME,
|
||||
currentSegment.x - BOUND_EXCLUDING_TIME,
|
||||
minSegmentDuration, minProgressThreshold,
|
||||
segmentHelper));
|
||||
}
|
||||
|
||||
pathSegments.push(currentSegment);
|
||||
previousSegment = currentSegment;
|
||||
}
|
||||
|
||||
return pathSegments;
|
||||
}
|
||||
exports.createPathSegments = createPathSegments;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return {Element} path element.
|
||||
*/
|
||||
function appendPathElement(parentEl, pathSegments, cls) {
|
||||
// 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];
|
||||
path += pathSegment.easing.startsWith("steps")
|
||||
? createStepsPathString(pathSegment, nextPathSegment)
|
||||
: createCubicBezierPathString(pathSegment, nextPathSegment);
|
||||
}
|
||||
path += ` L${ pathSegments[pathSegments.length - 1].x },0 Z`;
|
||||
// Append and return the path element.
|
||||
return createSVGNode({
|
||||
parent: parentEl,
|
||||
nodeType: "path",
|
||||
attributes: {
|
||||
"d": path,
|
||||
"class": cls,
|
||||
"vector-effect": "non-scaling-stroke",
|
||||
"transform": "scale(1, -1)"
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.appendPathElement = appendPathElement;
|
||||
|
||||
/**
|
||||
* Create the path segments from given keyframes.
|
||||
* @param {Number} duration - Duration of animation.
|
||||
* @param {Object} Keyframes of devtool's format.
|
||||
* @return {Array} path segments -
|
||||
* [{x: {Number} time, y: {Number} distance,
|
||||
* easing: {String} keyframe's easing,
|
||||
* style: {String} keyframe's value}, ...]
|
||||
*/
|
||||
function createKeyframesPathSegments(duration, keyframes) {
|
||||
return keyframes.map(keyframe => {
|
||||
return {
|
||||
x: keyframe.offset * duration,
|
||||
y: keyframe.distance,
|
||||
easing: keyframe.easing,
|
||||
style: keyframe.value
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a line path string.
|
||||
* @param {Object} segment - e.g. { x: 100, y: 1 }
|
||||
* @return {String} path string - e.g. "L100,1"
|
||||
*/
|
||||
function createLinePathString(segment) {
|
||||
return ` L${ segment.x },${ segment.y }`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path string to represents a step function.
|
||||
* @param {Object} currentSegment - e.g. { x: 0, y: 0, easing: "steps(2)" }
|
||||
* @param {Object} nextSegment - e.g. { x: 1, y: 1 }
|
||||
* @return {String} path string - e.g. "C 0.25 0.1, 0.25 1, 1 1"
|
||||
*/
|
||||
function createStepsPathString(currentSegment, nextSegment) {
|
||||
const matches =
|
||||
currentSegment.easing.match(/^steps\((\d+)(,\sstart)?\)/);
|
||||
const stepNumber = parseInt(matches[1], 10);
|
||||
const oneStepX = (nextSegment.x - currentSegment.x) / stepNumber;
|
||||
const oneStepY = (nextSegment.y - currentSegment.y) / stepNumber;
|
||||
const isStepStart = matches[2];
|
||||
const stepOffsetY = isStepStart ? 1 : 0;
|
||||
let path = "";
|
||||
for (let step = 0; step < stepNumber; step++) {
|
||||
const sx = currentSegment.x + step * oneStepX;
|
||||
const ex = sx + oneStepX;
|
||||
const y = currentSegment.y + (step + stepOffsetY) * oneStepY;
|
||||
path += ` L${ sx },${ y } L${ ex },${ y }`;
|
||||
}
|
||||
if (!isStepStart) {
|
||||
path += ` L${ nextSegment.x },${ nextSegment.y }`;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path string to represents a bezier curve.
|
||||
* @param {Object} currentSegment - e.g. { x: 0, y: 0, easing: "ease" }
|
||||
* @param {Object} nextSegment - e.g. { x: 1, y: 1 }
|
||||
* @return {String} path string - e.g. "C 0.25 0.1, 0.25 1, 1 1"
|
||||
*/
|
||||
function createCubicBezierPathString(currentSegment, nextSegment) {
|
||||
const controlPoints = parseTimingFunction(currentSegment.easing);
|
||||
if (!controlPoints) {
|
||||
// Just return line path string since we could not parse this easing.
|
||||
return createLinePathString(currentSegment);
|
||||
}
|
||||
|
||||
const cp1x = controlPoints[0];
|
||||
const cp1y = controlPoints[1];
|
||||
const cp2x = controlPoints[2];
|
||||
const cp2y = controlPoints[3];
|
||||
|
||||
const diffX = nextSegment.x - currentSegment.x;
|
||||
const diffY = nextSegment.y - currentSegment.y;
|
||||
let path =
|
||||
` C ${ currentSegment.x + diffX * cp1x } ${ currentSegment.y + diffY * cp1y }`;
|
||||
path += `, ${ currentSegment.x + diffX * cp2x } ${ currentSegment.y + diffY * cp2y }`;
|
||||
path += `, ${ nextSegment.x } ${ nextSegment.y }`;
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse given RGBA string.
|
||||
* @param {String} colorString - e.g. rgb(0, 0, 0) or rgba(0, 0, 0, 0.5) and so on.
|
||||
* @return {Object} RGBA {r: r, g: g, b: b, a: a}.
|
||||
*/
|
||||
function getRGBA(colorString) {
|
||||
const color = new colorUtils.CssColor(colorString);
|
||||
return color.getRGBATuple();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the distance from give two RGBA.
|
||||
* @param {Object} rgba1 - RGBA (format is same to getRGBA)
|
||||
* @param {Object} rgba2 - RGBA (format is same to getRGBA)
|
||||
* @return {float} distance.
|
||||
*/
|
||||
function getRGBADistance(rgba1, rgba2) {
|
||||
const startA = rgba1.a;
|
||||
const startR = rgba1.r * startA;
|
||||
const startG = rgba1.g * startA;
|
||||
const startB = rgba1.b * startA;
|
||||
const endA = rgba2.a;
|
||||
const endR = rgba2.r * endA;
|
||||
const endG = rgba2.g * endA;
|
||||
const endB = rgba2.b * endA;
|
||||
const diffA = startA - endA;
|
||||
const diffR = startR - endR;
|
||||
const diffG = startG - endG;
|
||||
const diffB = startB - endB;
|
||||
return Math.sqrt(diffA * diffA + diffR * diffR + diffG * diffG + diffB * diffB);
|
||||
}
|
|
@ -12,8 +12,9 @@ DIRS += [
|
|||
]
|
||||
|
||||
DevToolsModules(
|
||||
'utils.js',
|
||||
'graph-helper.js',
|
||||
'utils.js'
|
||||
)
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Firefox', 'Developer Tools: Animation Inspector')
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Firefox', 'Developer Tools: Animation Inspector')
|
||||
|
|
|
@ -381,11 +381,11 @@ function* clickOnAnimation(panel, index, shouldClose) {
|
|||
? "animation-unselected"
|
||||
: "animation-selected");
|
||||
|
||||
// If we're opening the animation, also wait for the keyframes-retrieved
|
||||
// event.
|
||||
// If we're opening the animation, also wait for
|
||||
// the animation-detail-rendering-completed event.
|
||||
let onReady = shouldClose
|
||||
? Promise.resolve()
|
||||
: timeline.details[index].once("keyframes-retrieved");
|
||||
: timeline.details[index].once("animation-detail-rendering-completed");
|
||||
|
||||
info("Click on animation " + index + " in the timeline");
|
||||
let timeBlock = timeline.rootWrapperEl.querySelectorAll(".time-block")[index];
|
||||
|
|
|
@ -20,6 +20,9 @@ const OPTIMAL_TIME_INTERVAL_MULTIPLES = [1, 2.5, 5];
|
|||
|
||||
const MILLIS_TIME_FORMAT_MAX_DURATION = 4000;
|
||||
|
||||
// SVG namespace
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
/**
|
||||
* DOM node creation helper function.
|
||||
* @param {Object} Options to customize the node to be created.
|
||||
|
@ -57,6 +60,22 @@ function createNode(options) {
|
|||
|
||||
exports.createNode = createNode;
|
||||
|
||||
/**
|
||||
* SVG DOM node creation helper function.
|
||||
* @param {Object} Options to customize the node to be created.
|
||||
* - nodeType {String} Optional, defaults to "div",
|
||||
* - attributes {Object} Optional attributes object like
|
||||
* {attrName1:value1, attrName2: value2, ...}
|
||||
* - parent {DOMNode} Mandatory node to append the newly created node to.
|
||||
* - textContent {String} Optional text for the node.
|
||||
* @return {DOMNode} The newly created node.
|
||||
*/
|
||||
function createSVGNode(options) {
|
||||
options.namespace = SVG_NS;
|
||||
return createNode(options);
|
||||
}
|
||||
exports.createSVGNode = createSVGNode;
|
||||
|
||||
/**
|
||||
* Find the optimal interval between time graduations in the animation timeline
|
||||
* graph based on a minimum time interval
|
||||
|
@ -273,3 +292,19 @@ var TimeScale = {
|
|||
};
|
||||
|
||||
exports.TimeScale = TimeScale;
|
||||
|
||||
/**
|
||||
* Convert given CSS property name to JavaScript CSS name.
|
||||
* @param {String} CSS property name (e.g. background-color).
|
||||
* @return {String} JavaScript CSS property name (e.g. backgroundColor).
|
||||
*/
|
||||
function getJsPropertyName(cssPropertyName) {
|
||||
if (cssPropertyName == "float") {
|
||||
return "cssFloat";
|
||||
}
|
||||
// https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
|
||||
return cssPropertyName.replace(/-([a-z])/gi, (str, group) => {
|
||||
return group.toUpperCase();
|
||||
});
|
||||
}
|
||||
exports.getJsPropertyName = getJsPropertyName;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
var Cu = Components.utils;
|
||||
var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
var {CubicBezier, _parseTimingFunction} = require("devtools/client/shared/widgets/CubicBezierWidget");
|
||||
var {CubicBezier, parseTimingFunction} = require("devtools/client/shared/widgets/CubicBezierWidget");
|
||||
|
||||
function run_test() {
|
||||
throwsWhenMissingCoordinates();
|
||||
|
@ -112,22 +112,22 @@ function testParseTimingFunction() {
|
|||
do_print("test parseTimingFunction");
|
||||
|
||||
for (let test of ["ease", "linear", "ease-in", "ease-out", "ease-in-out"]) {
|
||||
ok(_parseTimingFunction(test), test);
|
||||
ok(parseTimingFunction(test), test);
|
||||
}
|
||||
|
||||
ok(!_parseTimingFunction("something"), "non-function token");
|
||||
ok(!_parseTimingFunction("something()"), "non-cubic-bezier function");
|
||||
ok(!_parseTimingFunction("cubic-bezier(something)",
|
||||
ok(!parseTimingFunction("something"), "non-function token");
|
||||
ok(!parseTimingFunction("something()"), "non-cubic-bezier function");
|
||||
ok(!parseTimingFunction("cubic-bezier(something)",
|
||||
"cubic-bezier with non-numeric argument"));
|
||||
ok(!_parseTimingFunction("cubic-bezier(1,2,3:7)",
|
||||
ok(!parseTimingFunction("cubic-bezier(1,2,3:7)",
|
||||
"did not see comma"));
|
||||
ok(!_parseTimingFunction("cubic-bezier(1,2,3,7:",
|
||||
"did not see close paren"));
|
||||
ok(!_parseTimingFunction("cubic-bezier(1,2", "early EOF after number"));
|
||||
ok(!_parseTimingFunction("cubic-bezier(1,2,", "early EOF after comma"));
|
||||
deepEqual(_parseTimingFunction("cubic-bezier(1,2,3,7)"), [1, 2, 3, 7],
|
||||
ok(!parseTimingFunction("cubic-bezier(1,2,3,7:",
|
||||
"did not see close paren"));
|
||||
ok(!parseTimingFunction("cubic-bezier(1,2", "early EOF after number"));
|
||||
ok(!parseTimingFunction("cubic-bezier(1,2,", "early EOF after comma"));
|
||||
deepEqual(parseTimingFunction("cubic-bezier(1,2,3,7)"), [1, 2, 3, 7],
|
||||
"correct invocation");
|
||||
deepEqual(_parseTimingFunction("cubic-bezier(1, /* */ 2,3, 7 )"),
|
||||
deepEqual(parseTimingFunction("cubic-bezier(1, /* */ 2,3, 7 )"),
|
||||
[1, 2, 3, 7],
|
||||
"correct with comments and whitespace");
|
||||
}
|
||||
|
|
|
@ -462,7 +462,7 @@ ColorWidget.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
const { r, g, b, a } = color._getRGBATuple();
|
||||
const { r, g, b, a } = color.getRGBATuple();
|
||||
this.rgb = [r, g, b, a];
|
||||
this.updateUI();
|
||||
this.onChange();
|
||||
|
@ -531,7 +531,7 @@ ColorWidget.prototype = {
|
|||
}
|
||||
|
||||
const cssString = ColorWidget.hslToCssString(hsl[0], hsl[1], hsl[2], hsl[3]);
|
||||
const { r, g, b, a } = new colorUtils.CssColor(cssString)._getRGBATuple();
|
||||
const { r, g, b, a } = new colorUtils.CssColor(cssString).getRGBATuple();
|
||||
|
||||
this.rgb = [r, g, b, a];
|
||||
|
||||
|
|
|
@ -873,8 +873,7 @@ function parseTimingFunction(value) {
|
|||
return result;
|
||||
}
|
||||
|
||||
// This is exported for testing.
|
||||
exports._parseTimingFunction = parseTimingFunction;
|
||||
exports.parseTimingFunction = parseTimingFunction;
|
||||
|
||||
/**
|
||||
* Removes a class from a node and adds it to another.
|
||||
|
|
|
@ -194,7 +194,7 @@ SwatchColorPickerTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.pr
|
|||
|
||||
_colorToRgba: function (color) {
|
||||
color = new colorUtils.CssColor(color, this.cssColor4);
|
||||
let rgba = color._getRGBATuple();
|
||||
let rgba = color.getRGBATuple();
|
||||
return [rgba.r, rgba.g, rgba.b, rgba.a];
|
||||
},
|
||||
|
||||
|
|
|
@ -10,6 +10,15 @@
|
|||
--pause-image: url(chrome://devtools/skin/images/pause.svg);
|
||||
--rewind-image: url(chrome://devtools/skin/images/rewind.svg);
|
||||
--play-image: url(chrome://devtools/skin/images/play.svg);
|
||||
/* The color for animation type 'opacity' */
|
||||
--opacity-border-color: var(--theme-highlight-pink);
|
||||
--opacity-background-color: #df80ff80;
|
||||
/* The color for animation type 'transform' */
|
||||
--transform-border-color: var(--theme-graphs-yellow);
|
||||
--transform-background-color: #d99b2880;
|
||||
/* The color for other animation type */
|
||||
--other-border-color: var(--theme-graphs-bluegrey);
|
||||
--other-background-color: #5e88b080;
|
||||
}
|
||||
|
||||
.theme-light {
|
||||
|
@ -28,6 +37,18 @@
|
|||
--play-image: url(chrome://devtools/skin/images/firebug/play.svg);
|
||||
}
|
||||
|
||||
.theme-light, .theme-firebug {
|
||||
/* The color for animation type 'opacity' */
|
||||
--opacity-border-color: var(--theme-highlight-pink);
|
||||
--opacity-background-color: #b82ee580;
|
||||
/* The color for animation type 'transform' */
|
||||
--transform-border-color: var(--theme-graphs-orange);
|
||||
--transform-background-color: #efc05280;
|
||||
/* The color for other animation type */
|
||||
--other-border-color: var(--theme-graphs-bluegrey);
|
||||
--other-background-color: #0072ab80;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* How high should toolbars be */
|
||||
--toolbar-height: 20px;
|
||||
|
@ -582,30 +603,22 @@ body {
|
|||
/* Actual keyframe markers are positioned absolutely within this container and
|
||||
their position is relative to its size (we know the offset of each frame
|
||||
in percentage) */
|
||||
position: relative;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
}
|
||||
height: 100%;
|
||||
|
||||
.keyframes.cssanimation {
|
||||
background-color: var(--theme-contrast-background);
|
||||
}
|
||||
|
||||
.keyframes.csstransition {
|
||||
background-color: var(--theme-highlight-blue);
|
||||
}
|
||||
|
||||
.keyframes.scriptanimation {
|
||||
background-color: var(--theme-graphs-green);
|
||||
}
|
||||
|
||||
.keyframes .frame {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
background-color: inherit;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.keyframes .frame::before {
|
||||
|
@ -619,5 +632,44 @@ body {
|
|||
width: var(--keyframes-marker-size);
|
||||
height: var(--keyframes-marker-size);
|
||||
border-radius: 100%;
|
||||
border: 1px solid var(--theme-highlight-gray);
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.keyframes.cssanimation .frame {
|
||||
background-color: var(--theme-contrast-background);
|
||||
}
|
||||
|
||||
.keyframes.csstransition .frame {
|
||||
background-color: var(--theme-highlight-blue);
|
||||
}
|
||||
|
||||
.keyframes.scriptanimation .frame {
|
||||
background-color: var(--theme-graphs-green);
|
||||
}
|
||||
|
||||
.keyframes svg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.keyframes svg path {
|
||||
fill: var(--other-background-color);
|
||||
stroke: var(--other-border-color);
|
||||
}
|
||||
|
||||
/* color of path is decided by the animation type */
|
||||
.keyframes svg path.opacity {
|
||||
fill: var(--opacity-background-color);
|
||||
stroke: var(--opacity-border-color);
|
||||
}
|
||||
|
||||
.keyframes svg path.transform {
|
||||
fill: var(--transform-background-color);
|
||||
stroke: var(--transform-border-color);
|
||||
}
|
||||
|
||||
.keyframes svg path.color {
|
||||
stroke: none;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
* /dom/webidl/Animation*.webidl
|
||||
*/
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
const {Cu, Ci} = require("chrome");
|
||||
const promise = require("promise");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
const {Actor} = protocol;
|
||||
|
@ -461,12 +461,122 @@ var AnimationPlayerActor = protocol.ActorClassWithSpec(animationPlayerSpec, {
|
|||
/**
|
||||
* Get data about the animated properties of this animation player.
|
||||
* @return {Array} Returns a list of animated properties.
|
||||
* Each property contains a list of values and their offsets
|
||||
* Each property contains a list of values, their offsets and distances.
|
||||
*/
|
||||
getProperties: function () {
|
||||
return this.player.effect.getProperties().map(property => {
|
||||
const properties = this.player.effect.getProperties().map(property => {
|
||||
return {name: property.property, values: property.values};
|
||||
});
|
||||
|
||||
const DOMWindowUtils =
|
||||
this.window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
// Fill missing keyframe with computed value.
|
||||
for (let property of properties) {
|
||||
let underlyingValue = null;
|
||||
// Check only 0% and 100% keyframes.
|
||||
[0, property.values.length - 1].forEach(index => {
|
||||
const values = property.values[index];
|
||||
if (values.value !== undefined) {
|
||||
return;
|
||||
}
|
||||
if (!underlyingValue) {
|
||||
let pseudo = null;
|
||||
let target = this.player.effect.target;
|
||||
if (target.type) {
|
||||
// This target is a pseudo element.
|
||||
pseudo = target.type;
|
||||
target = target.parentElement;
|
||||
}
|
||||
const value =
|
||||
DOMWindowUtils.getUnanimatedComputedStyle(target, pseudo, property.name);
|
||||
const animationType = DOMWindowUtils.getAnimationTypeForLonghand(property.name);
|
||||
underlyingValue = animationType === "float" ? parseFloat(value, 10) : value;
|
||||
}
|
||||
values.value = underlyingValue;
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate the distance.
|
||||
for (let property of properties) {
|
||||
const propertyName = property.name;
|
||||
const maxObject = { distance: -1 };
|
||||
for (let i = 0; i < property.values.length - 1; i++) {
|
||||
const value1 = property.values[i].value;
|
||||
for (let j = i + 1; j < property.values.length; j++) {
|
||||
const value2 = property.values[j].value;
|
||||
const distance = this.getDistance(this.player.effect.target, propertyName,
|
||||
value1, value2, DOMWindowUtils);
|
||||
if (maxObject.distance >= distance) {
|
||||
continue;
|
||||
}
|
||||
maxObject.distance = distance;
|
||||
maxObject.value1 = value1;
|
||||
maxObject.value2 = value2;
|
||||
}
|
||||
}
|
||||
if (maxObject.distance === 0) {
|
||||
// Distance is zero means that no values change or can't calculate the distance.
|
||||
// In this case, we use the keyframe offset as the distance.
|
||||
property.values.reduce((previous, current) => {
|
||||
// If the current value is same as previous value, use previous distance.
|
||||
current.distance =
|
||||
current.value === previous.value ? previous.distance : current.offset;
|
||||
return current;
|
||||
}, property.values[0]);
|
||||
continue;
|
||||
}
|
||||
const baseValue =
|
||||
maxObject.value1 < maxObject.value2 ? maxObject.value1 : maxObject.value2;
|
||||
for (let values of property.values) {
|
||||
const value = values.value;
|
||||
const distance = this.getDistance(this.player.effect.target, propertyName,
|
||||
baseValue, value, DOMWindowUtils);
|
||||
values.distance = distance / maxObject.distance;
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the animation types for a given list of CSS property names.
|
||||
* @param {Array} propertyNames - CSS property names (e.g. background-color)
|
||||
* @return {Object} Returns animation types (e.g. {"background-color": "rgb(0, 0, 0)"}.
|
||||
*/
|
||||
getAnimationTypes: function (propertyNames) {
|
||||
const DOMWindowUtils = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
const animationTypes = {};
|
||||
for (let propertyName of propertyNames) {
|
||||
animationTypes[propertyName] =
|
||||
DOMWindowUtils.getAnimationTypeForLonghand(propertyName);
|
||||
}
|
||||
return animationTypes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the distance of between value1, value2.
|
||||
* @param {Object} target - dom element
|
||||
* @param {String} propertyName - e.g. transform
|
||||
* @param {String} value1 - e.g. translate(0px)
|
||||
* @param {String} value2 - e.g. translate(10px)
|
||||
* @param {Object} DOMWindowUtils
|
||||
* @param {float} distance
|
||||
*/
|
||||
getDistance: function (target, propertyName, value1, value2, DOMWindowUtils) {
|
||||
if (value1 === value2) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
const distance =
|
||||
DOMWindowUtils.computeAnimationDistance(target, propertyName, value1, value2);
|
||||
return distance;
|
||||
} catch (e) {
|
||||
// We can't compute the distance such the 'discrete' animation,
|
||||
// 'auto' keyword and so on.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ CssColor.prototype = {
|
|||
if (!this.valid) {
|
||||
return false;
|
||||
}
|
||||
return this._getRGBATuple().a !== 1;
|
||||
return this.getRGBATuple().a !== 1;
|
||||
},
|
||||
|
||||
get valid() {
|
||||
|
@ -153,7 +153,7 @@ CssColor.prototype = {
|
|||
*/
|
||||
get transparent() {
|
||||
try {
|
||||
let tuple = this._getRGBATuple();
|
||||
let tuple = this.getRGBATuple();
|
||||
return !(tuple.r || tuple.g || tuple.b || tuple.a);
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
@ -171,7 +171,7 @@ CssColor.prototype = {
|
|||
}
|
||||
|
||||
try {
|
||||
let tuple = this._getRGBATuple();
|
||||
let tuple = this.getRGBATuple();
|
||||
|
||||
if (tuple.a !== 1) {
|
||||
return this.hex;
|
||||
|
@ -227,7 +227,7 @@ CssColor.prototype = {
|
|||
return this.longAlphaHex;
|
||||
}
|
||||
|
||||
let tuple = this._getRGBATuple();
|
||||
let tuple = this.getRGBATuple();
|
||||
return "#" + ((1 << 24) + (tuple.r << 16) + (tuple.g << 8) +
|
||||
(tuple.b << 0)).toString(16).substr(-6);
|
||||
},
|
||||
|
@ -238,7 +238,7 @@ CssColor.prototype = {
|
|||
return invalidOrSpecialValue;
|
||||
}
|
||||
|
||||
let tuple = this._getRGBATuple();
|
||||
let tuple = this.getRGBATuple();
|
||||
return "#" + ((1 << 24) + (tuple.r << 16) + (tuple.g << 8) +
|
||||
(tuple.b << 0)).toString(16).substr(-6) +
|
||||
Math.round(tuple.a * 255).toString(16).padEnd(2, "0");
|
||||
|
@ -254,7 +254,7 @@ CssColor.prototype = {
|
|||
// The color is valid and begins with rgb(.
|
||||
return this.authored;
|
||||
}
|
||||
let tuple = this._getRGBATuple();
|
||||
let tuple = this.getRGBATuple();
|
||||
return "rgb(" + tuple.r + ", " + tuple.g + ", " + tuple.b + ")";
|
||||
}
|
||||
return this.rgba;
|
||||
|
@ -269,7 +269,7 @@ CssColor.prototype = {
|
|||
// The color is valid and begins with rgba(.
|
||||
return this.authored;
|
||||
}
|
||||
let components = this._getRGBATuple();
|
||||
let components = this.getRGBATuple();
|
||||
return "rgba(" + components.r + ", " +
|
||||
components.g + ", " +
|
||||
components.b + ", " +
|
||||
|
@ -301,7 +301,7 @@ CssColor.prototype = {
|
|||
return this.authored;
|
||||
}
|
||||
if (this.hasAlpha) {
|
||||
let a = this._getRGBATuple().a;
|
||||
let a = this.getRGBATuple().a;
|
||||
return this._hsl(a);
|
||||
}
|
||||
return this._hsl(1);
|
||||
|
@ -401,7 +401,7 @@ CssColor.prototype = {
|
|||
* Returns a RGBA 4-Tuple representation of a color or transparent as
|
||||
* appropriate.
|
||||
*/
|
||||
_getRGBATuple: function () {
|
||||
getRGBATuple: function () {
|
||||
let tuple = colorToRGBA(this.authored, this.cssColor4);
|
||||
|
||||
tuple.a = parseFloat(tuple.a.toFixed(1));
|
||||
|
@ -432,7 +432,7 @@ CssColor.prototype = {
|
|||
return this.authored;
|
||||
}
|
||||
|
||||
let {r, g, b} = this._getRGBATuple();
|
||||
let {r, g, b} = this.getRGBATuple();
|
||||
let [h, s, l] = rgbToHsl([r, g, b]);
|
||||
if (maybeAlpha !== undefined) {
|
||||
return "hsla(" + h + ", " + s + "%, " + l + "%, " + maybeAlpha + ")";
|
||||
|
@ -534,7 +534,7 @@ function setAlpha(colorValue, alpha, useCssColor4ColorFunction = false) {
|
|||
alpha = 1;
|
||||
}
|
||||
|
||||
let { r, g, b } = color._getRGBATuple();
|
||||
let { r, g, b } = color.getRGBATuple();
|
||||
return "rgba(" + r + ", " + g + ", " + b + ", " + alpha + ")";
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,14 @@ const animationPlayerSpec = generateActorSpec({
|
|||
response: {
|
||||
properties: RetVal("array:json")
|
||||
}
|
||||
},
|
||||
getAnimationTypes: {
|
||||
request: {
|
||||
propertyNames: Arg(0, "array:string")
|
||||
},
|
||||
response: {
|
||||
animationTypes: RetVal("json")
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -148,4 +156,3 @@ const animationsSpec = generateActorSpec({
|
|||
});
|
||||
|
||||
exports.animationsSpec = animationsSpec;
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче