зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1210796 - Part 6: Fixed animation detail panel. r=pbro
MozReview-Commit-ID: CYIka7UkTPx --HG-- extra : rebase_source : 3ff32fe380eebd9a3f0a2b5ea7e9ded046ecb98e
This commit is contained in:
Родитель
dbba9bba01
Коммит
819485ef9b
|
@ -7,6 +7,7 @@
|
|||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/animationinspector.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/splitter/split-box.css"/>
|
||||
<script type="application/javascript" src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
</head>
|
||||
<body class="theme-sidebar devtools-monospace" role="application" empty="true">
|
||||
|
@ -26,6 +27,16 @@
|
|||
<p id="error-hint"></p>
|
||||
<button id="element-picker" data-standalone="true" class="devtools-button"></button>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* eslint-disable */
|
||||
var isInChrome = window.location.href.includes("chrome:");
|
||||
if (isInChrome) {
|
||||
var exports = {};
|
||||
var Cu = Components.utils;
|
||||
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
|
||||
}
|
||||
</script>
|
||||
<script type="application/javascript" src="animation-controller.js"></script>
|
||||
<script type="application/javascript" src="animation-panel.js"></script>
|
||||
</body>
|
||||
|
|
|
@ -205,12 +205,12 @@ AnimationDetails.prototype = {
|
|||
// Add animated property header.
|
||||
const headerEl = createNode({
|
||||
parent: this.containerEl,
|
||||
attributes: { "class": "animated-properties-header property" }
|
||||
attributes: { "class": "animated-properties-header" }
|
||||
});
|
||||
|
||||
// Add progress tick container.
|
||||
const progressTickContainerEl = createNode({
|
||||
parent: headerEl,
|
||||
parent: this.containerEl,
|
||||
attributes: { "class": "progress-tick-container track-container" }
|
||||
});
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {createNode, TimeScale} = require("devtools/client/animationinspector/utils");
|
||||
const {createNode, TimeScale, getFormattedAnimationTitle} =
|
||||
require("devtools/client/animationinspector/utils");
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const L10N =
|
||||
|
@ -354,30 +355,6 @@ AnimationTimeBlock.prototype = {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a formatted title for this animation. This will be either:
|
||||
* "some-name", "some-name : CSS Transition", "some-name : CSS Animation",
|
||||
* "some-name : Script Animation", or "Script Animation", depending
|
||||
* if the server provides the type, what type it is and if the animation
|
||||
* has a name
|
||||
* @param {AnimationPlayerFront} animation
|
||||
*/
|
||||
function getFormattedAnimationTitle({state}) {
|
||||
// Older servers don't send a type, and only know about
|
||||
// CSSAnimations and CSSTransitions, so it's safe to use
|
||||
// just the name.
|
||||
if (!state.type) {
|
||||
return state.name;
|
||||
}
|
||||
|
||||
// Script-generated animations may not have a name.
|
||||
if (state.type === "scriptanimation" && !state.name) {
|
||||
return L10N.getStr("timeline.scriptanimation.unnamedLabel");
|
||||
}
|
||||
|
||||
return L10N.getFormatStr(`timeline.${state.type}.nameLabel`, state.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render delay section.
|
||||
* @param {Element} parentEl - Parent element of this appended path element.
|
||||
|
|
|
@ -10,12 +10,17 @@ const EventEmitter = require("devtools/shared/event-emitter");
|
|||
const {
|
||||
createNode,
|
||||
findOptimalTimeInterval,
|
||||
getFormattedAnimationTitle,
|
||||
TimeScale
|
||||
} = require("devtools/client/animationinspector/utils");
|
||||
const {AnimationDetails} = require("devtools/client/animationinspector/components/animation-details");
|
||||
const {AnimationTargetNode} = require("devtools/client/animationinspector/components/animation-target-node");
|
||||
const {AnimationTimeBlock} = require("devtools/client/animationinspector/components/animation-time-block");
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const L10N =
|
||||
new LocalizationHelper("devtools/client/locales/animationinspector.properties");
|
||||
|
||||
// The minimum spacing between 2 time graduation headers in the timeline (px).
|
||||
const TIME_GRADUATION_MIN_SPACING = 40;
|
||||
// When the container window is resized, the timeline background gets refreshed,
|
||||
|
@ -42,7 +47,6 @@ function AnimationsTimeline(inspector, serverTraits) {
|
|||
this.animations = [];
|
||||
this.targetNodes = [];
|
||||
this.timeBlocks = [];
|
||||
this.details = [];
|
||||
this.inspector = inspector;
|
||||
this.serverTraits = serverTraits;
|
||||
|
||||
|
@ -63,16 +67,51 @@ exports.AnimationsTimeline = AnimationsTimeline;
|
|||
AnimationsTimeline.prototype = {
|
||||
init: function (containerEl) {
|
||||
this.win = containerEl.ownerDocument.defaultView;
|
||||
this.rootWrapperEl = containerEl;
|
||||
|
||||
this.rootWrapperEl = createNode({
|
||||
parent: containerEl,
|
||||
attributes: {
|
||||
"class": "animation-timeline"
|
||||
}
|
||||
this.setupSplitBox();
|
||||
this.setupAnimationTimeline();
|
||||
this.setupAnimationDetail();
|
||||
|
||||
this.win.addEventListener("resize",
|
||||
this.onWindowResize);
|
||||
},
|
||||
|
||||
setupSplitBox: function () {
|
||||
const browserRequire = this.win.BrowserLoader({
|
||||
window: this.win,
|
||||
useOnlyShared: true
|
||||
}).require;
|
||||
|
||||
const React = browserRequire("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
const SplitBox = React.createFactory(
|
||||
browserRequire("devtools/client/shared/components/splitter/split-box"));
|
||||
|
||||
const splitter = SplitBox({
|
||||
className: "animation-root",
|
||||
initialSize: "0 0",
|
||||
maxSize: "calc(100% - (var(--timeline-animation-height) * 2))",
|
||||
splitterSize: 1,
|
||||
endPanelControl: true,
|
||||
startPanel: React.DOM.div({
|
||||
className: "animation-timeline"
|
||||
}),
|
||||
endPanel: React.DOM.div({
|
||||
className: "animation-detail"
|
||||
}),
|
||||
vert: false
|
||||
});
|
||||
|
||||
ReactDOM.render(splitter, this.rootWrapperEl);
|
||||
},
|
||||
|
||||
setupAnimationTimeline: function () {
|
||||
const animationTimelineEl = this.rootWrapperEl.querySelector(".animation-timeline");
|
||||
|
||||
let scrubberContainer = createNode({
|
||||
parent: this.rootWrapperEl,
|
||||
parent: animationTimelineEl,
|
||||
attributes: {"class": "scrubber-wrapper"}
|
||||
});
|
||||
|
||||
|
@ -89,11 +128,17 @@ AnimationsTimeline.prototype = {
|
|||
"class": "scrubber-handle"
|
||||
}
|
||||
});
|
||||
createNode({
|
||||
parent: this.scrubberHandleEl,
|
||||
attributes: {
|
||||
"class": "scrubber-line"
|
||||
}
|
||||
});
|
||||
this.scrubberHandleEl.addEventListener("mousedown",
|
||||
this.onScrubberMouseDown);
|
||||
this.onScrubberMouseDown);
|
||||
|
||||
this.headerWrapper = createNode({
|
||||
parent: this.rootWrapperEl,
|
||||
parent: animationTimelineEl,
|
||||
attributes: {
|
||||
"class": "header-wrapper"
|
||||
}
|
||||
|
@ -107,30 +152,76 @@ AnimationsTimeline.prototype = {
|
|||
});
|
||||
|
||||
this.timeHeaderEl.addEventListener("mousedown",
|
||||
this.onScrubberMouseDown);
|
||||
this.onScrubberMouseDown);
|
||||
|
||||
this.timeTickEl = createNode({
|
||||
parent: this.rootWrapperEl,
|
||||
parent: animationTimelineEl,
|
||||
attributes: {
|
||||
"class": "time-body track-container"
|
||||
}
|
||||
});
|
||||
|
||||
this.animationsEl = createNode({
|
||||
parent: this.rootWrapperEl,
|
||||
parent: animationTimelineEl,
|
||||
nodeType: "ul",
|
||||
attributes: {
|
||||
"class": "animations"
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
this.win.addEventListener("resize",
|
||||
this.onWindowResize);
|
||||
setupAnimationDetail: function () {
|
||||
this.animationDetailEl = this.rootWrapperEl.querySelector(".animation-detail");
|
||||
|
||||
this.animationDetailEl.dataset.defaultDisplayStyle =
|
||||
this.win.getComputedStyle(this.animationDetailEl).display;
|
||||
this.animationDetailEl.style.display = "none";
|
||||
|
||||
const animationDetailHeaderEl = createNode({
|
||||
parent: this.animationDetailEl,
|
||||
attributes: {
|
||||
"class": "animation-detail-header"
|
||||
}
|
||||
});
|
||||
|
||||
const headerTitleEl = createNode({
|
||||
parent: animationDetailHeaderEl,
|
||||
attributes: {
|
||||
"class": "devtools-toolbar"
|
||||
}
|
||||
});
|
||||
|
||||
createNode({
|
||||
parent: headerTitleEl,
|
||||
textContent: L10N.getStr("detail.headerTitle")
|
||||
});
|
||||
|
||||
this.animationAnimationNameEl = createNode({
|
||||
parent: headerTitleEl
|
||||
});
|
||||
|
||||
const animationDetailBodyEl = createNode({
|
||||
parent: this.animationDetailEl,
|
||||
attributes: {
|
||||
"class": "animation-detail-body"
|
||||
}
|
||||
});
|
||||
|
||||
this.animatedPropertiesEl = createNode({
|
||||
parent: animationDetailBodyEl,
|
||||
attributes: {
|
||||
"class": "animated-properties"
|
||||
}
|
||||
});
|
||||
|
||||
this.details = new AnimationDetails(this.serverTraits);
|
||||
this.details.init(this.animatedPropertiesEl);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.stopAnimatingScrubber();
|
||||
this.unrender();
|
||||
this.details.destroy();
|
||||
|
||||
this.win.removeEventListener("resize",
|
||||
this.onWindowResize);
|
||||
|
@ -141,15 +232,18 @@ AnimationsTimeline.prototype = {
|
|||
|
||||
this.rootWrapperEl.remove();
|
||||
this.animations = [];
|
||||
|
||||
this.rootWrapperEl = null;
|
||||
this.timeHeaderEl = null;
|
||||
this.animationsEl = null;
|
||||
this.animatedPropertiesEl = null;
|
||||
this.scrubberEl = null;
|
||||
this.scrubberHandleEl = null;
|
||||
this.win = null;
|
||||
this.inspector = null;
|
||||
this.serverTraits = null;
|
||||
this.animationDetailEl = null;
|
||||
this.animationAnimationNameEl = null;
|
||||
this.animatedPropertiesEl = null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -176,10 +270,8 @@ AnimationsTimeline.prototype = {
|
|||
TimeScale.reset();
|
||||
this.destroySubComponents("targetNodes");
|
||||
this.destroySubComponents("timeBlocks");
|
||||
this.destroySubComponents("details", [{
|
||||
event: "frame-selected",
|
||||
fn: this.onFrameSelected
|
||||
}]);
|
||||
this.details.off("frame-selected", this.onFrameSelected);
|
||||
this.details.unrender();
|
||||
this.animationsEl.innerHTML = "";
|
||||
},
|
||||
|
||||
|
@ -206,18 +298,27 @@ AnimationsTimeline.prototype = {
|
|||
|
||||
let el = this.rootWrapperEl;
|
||||
let animationEl = el.querySelectorAll(".animation")[index];
|
||||
let propsEl = el.querySelectorAll(".animated-properties")[index];
|
||||
|
||||
// Toggle the selected state on this animation.
|
||||
animationEl.classList.toggle("selected");
|
||||
propsEl.classList.toggle("selected");
|
||||
|
||||
// Render the details component for this animation if it was shown.
|
||||
if (animationEl.classList.contains("selected")) {
|
||||
this.details[index].render(animation);
|
||||
// Add class of animation type.
|
||||
if (!this.animatedPropertiesEl.classList.contains(animation.state.type)) {
|
||||
this.animatedPropertiesEl.className =
|
||||
`animated-properties ${ animation.state.type }`;
|
||||
}
|
||||
this.animationDetailEl.style.display =
|
||||
this.animationDetailEl.dataset.defaultDisplayStyle;
|
||||
this.details.render(animation);
|
||||
this.emit("animation-selected", animation);
|
||||
|
||||
this.animationAnimationNameEl.textContent =
|
||||
getFormattedAnimationTitle(animation);
|
||||
} else {
|
||||
this.emit("animation-unselected", animation);
|
||||
this.animationDetailEl.style.display = "none";
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -331,21 +432,6 @@ AnimationsTimeline.prototype = {
|
|||
}
|
||||
});
|
||||
|
||||
// Right below the line is a hidden-by-default line for displaying the
|
||||
// inline keyframes.
|
||||
let detailsEl = createNode({
|
||||
parent: this.animationsEl,
|
||||
nodeType: "li",
|
||||
attributes: {
|
||||
"class": "animated-properties " + animation.state.type
|
||||
}
|
||||
});
|
||||
|
||||
let details = new AnimationDetails(this.serverTraits);
|
||||
details.init(detailsEl);
|
||||
details.on("frame-selected", this.onFrameSelected);
|
||||
this.details.push(details);
|
||||
|
||||
// Left sidebar for the animated node.
|
||||
let animatedNodeEl = createNode({
|
||||
parent: animationEl,
|
||||
|
@ -376,6 +462,7 @@ AnimationsTimeline.prototype = {
|
|||
|
||||
timeBlock.on("selected", this.onAnimationSelected);
|
||||
}
|
||||
this.details.on("frame-selected", this.onFrameSelected);
|
||||
|
||||
// Use the document's current time to position the scrubber (if the server
|
||||
// doesn't provide it, hide the scrubber entirely).
|
||||
|
|
|
@ -52,12 +52,6 @@ add_task(function* () {
|
|||
|
||||
ok(hasExpectedWarnings(propertiesList),
|
||||
"The list of properties panel contains the right warnings");
|
||||
|
||||
info("Click to unselect the animation");
|
||||
yield clickOnAnimation(panel, 0, true);
|
||||
|
||||
ok(!isNodeVisible(propertiesList),
|
||||
"The list of properties panel is hidden again");
|
||||
});
|
||||
|
||||
function hasExpectedProperties(containerEl) {
|
||||
|
|
|
@ -37,8 +37,5 @@ add_task(function* () {
|
|||
|
||||
function isTimeBlockSelected(timeline, index) {
|
||||
let animation = timeline.rootWrapperEl.querySelectorAll(".animation")[index];
|
||||
let animatedProperties = timeline.rootWrapperEl.querySelectorAll(
|
||||
".animated-properties")[index];
|
||||
return animation.classList.contains("selected") &&
|
||||
animatedProperties.classList.contains("selected");
|
||||
return animation.classList.contains("selected");
|
||||
}
|
||||
|
|
|
@ -22,22 +22,22 @@ add_task(function* () {
|
|||
yield clickOnAnimation(panel, 0);
|
||||
|
||||
info("Click on the first keyframe of the first animated property");
|
||||
yield clickKeyframe(panel, 0, "background-color", 0);
|
||||
yield clickKeyframe(panel, "background-color", 0);
|
||||
|
||||
info("Make sure the scrubber stopped moving and is at the right position");
|
||||
yield assertScrubberMoving(panel, false);
|
||||
checkScrubberPos(scrubberEl, 0);
|
||||
|
||||
info("Click on a keyframe in the middle");
|
||||
yield clickKeyframe(panel, 0, "transform", 2);
|
||||
yield clickKeyframe(panel, "transform", 2);
|
||||
|
||||
info("Make sure the scrubber is at the right position");
|
||||
checkScrubberPos(scrubberEl, 50);
|
||||
});
|
||||
|
||||
function* clickKeyframe(panel, animIndex, property, index) {
|
||||
let keyframeComponent = getKeyframeComponent(panel, animIndex, property);
|
||||
let keyframeEl = getKeyframeEl(panel, animIndex, property, index);
|
||||
function* clickKeyframe(panel, property, index) {
|
||||
let keyframeComponent = getKeyframeComponent(panel, property);
|
||||
let keyframeEl = getKeyframeEl(panel, property, index);
|
||||
|
||||
let onSelect = keyframeComponent.once("frame-selected");
|
||||
EventUtils.sendMouseEvent({type: "click"}, keyframeEl,
|
||||
|
|
|
@ -11,7 +11,7 @@ add_task(function* () {
|
|||
let {panel} = yield openAnimationInspector();
|
||||
let timelineComponent = panel.animationsTimelineComponent;
|
||||
let timeBlockComponents = timelineComponent.timeBlocks;
|
||||
let detailsComponents = timelineComponent.details;
|
||||
let detailsComponent = timelineComponent.details;
|
||||
|
||||
for (let i = 0; i < timeBlockComponents.length; i++) {
|
||||
info(`Expand time block ${i} so its keyframes are visible`);
|
||||
|
@ -26,7 +26,7 @@ add_task(function* () {
|
|||
// Get the first set of keyframes (there's only one animated property
|
||||
// anyway), and the first frame element from there, we're only interested in
|
||||
// its offset.
|
||||
let keyframeComponent = detailsComponents[i].keyframeComponents[0];
|
||||
let keyframeComponent = detailsComponent.keyframeComponents[0];
|
||||
let frameEl = keyframeComponent.keyframesEl.querySelector(".frame");
|
||||
checkKeyframeOffset(containerEl, frameEl, state);
|
||||
}
|
||||
|
|
|
@ -385,7 +385,7 @@ function* clickOnAnimation(panel, index, shouldClose) {
|
|||
// the animation-detail-rendering-completed event.
|
||||
let onReady = shouldClose
|
||||
? Promise.resolve()
|
||||
: timeline.details[index].once("animation-detail-rendering-completed");
|
||||
: timeline.details.once("animation-detail-rendering-completed");
|
||||
|
||||
info("Click on animation " + index + " in the timeline");
|
||||
let timeBlock = timeline.rootWrapperEl.querySelectorAll(".time-block")[index];
|
||||
|
@ -399,13 +399,12 @@ function* clickOnAnimation(panel, index, shouldClose) {
|
|||
/**
|
||||
* Get an instance of the Keyframes component from the timeline.
|
||||
* @param {AnimationsPanel} panel The panel instance.
|
||||
* @param {Number} animationIndex The index of the animation in the timeline.
|
||||
* @param {String} propertyName The name of the animated property.
|
||||
* @return {Keyframes} The Keyframes component instance.
|
||||
*/
|
||||
function getKeyframeComponent(panel, animationIndex, propertyName) {
|
||||
function getKeyframeComponent(panel, propertyName) {
|
||||
let timeline = panel.animationsTimelineComponent;
|
||||
let detailsComponent = timeline.details[animationIndex];
|
||||
let detailsComponent = timeline.details;
|
||||
return detailsComponent.keyframeComponents
|
||||
.find(c => c.propertyName === propertyName);
|
||||
}
|
||||
|
@ -413,14 +412,12 @@ function getKeyframeComponent(panel, animationIndex, propertyName) {
|
|||
/**
|
||||
* Get a keyframe element from the timeline.
|
||||
* @param {AnimationsPanel} panel The panel instance.
|
||||
* @param {Number} animationIndex The index of the animation in the timeline.
|
||||
* @param {String} propertyName The name of the animated property.
|
||||
* @param {Index} keyframeIndex The index of the keyframe.
|
||||
* @return {DOMNode} The keyframe element.
|
||||
*/
|
||||
function getKeyframeEl(panel, animationIndex, propertyName, keyframeIndex) {
|
||||
let keyframeComponent = getKeyframeComponent(panel, animationIndex,
|
||||
propertyName);
|
||||
function getKeyframeEl(panel, propertyName, keyframeIndex) {
|
||||
let keyframeComponent = getKeyframeComponent(panel, propertyName);
|
||||
return keyframeComponent.keyframesEl
|
||||
.querySelectorAll(".frame")[keyframeIndex];
|
||||
}
|
||||
|
|
|
@ -308,3 +308,28 @@ function getJsPropertyName(cssPropertyName) {
|
|||
});
|
||||
}
|
||||
exports.getJsPropertyName = getJsPropertyName;
|
||||
|
||||
/**
|
||||
* Get a formatted title for this animation. This will be either:
|
||||
* "some-name", "some-name : CSS Transition", "some-name : CSS Animation",
|
||||
* "some-name : Script Animation", or "Script Animation", depending
|
||||
* if the server provides the type, what type it is and if the animation
|
||||
* has a name
|
||||
* @param {AnimationPlayerFront} animation
|
||||
*/
|
||||
function getFormattedAnimationTitle({state}) {
|
||||
// Older servers don't send a type, and only know about
|
||||
// CSSAnimations and CSSTransitions, so it's safe to use
|
||||
// just the name.
|
||||
if (!state.type) {
|
||||
return state.name;
|
||||
}
|
||||
|
||||
// Script-generated animations may not have a name.
|
||||
if (state.type === "scriptanimation" && !state.name) {
|
||||
return L10N.getStr("timeline.scriptanimation.unnamedLabel");
|
||||
}
|
||||
|
||||
return L10N.getFormatStr(`timeline.${state.type}.nameLabel`, state.name);
|
||||
}
|
||||
exports.getFormattedAnimationTitle = getFormattedAnimationTitle;
|
||||
|
|
|
@ -178,3 +178,7 @@ timeline.unknown.nameLabel=%S
|
|||
# %S represents the value in percentage with two decimal points, localized.
|
||||
# there are two "%" after %S to escape and display "%"
|
||||
detail.propertiesHeader.percentage=%S%%
|
||||
|
||||
# LOCALIZATION NOTE (detail.headerTitle):
|
||||
# This string is displayed on header label in .animation-detail-header.
|
||||
detail.headerTitle=Animated properties for
|
||||
|
|
|
@ -139,8 +139,6 @@ body {
|
|||
|
||||
#players {
|
||||
height: calc(100% - var(--toolbar-height));
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
[empty] #players {
|
||||
|
@ -230,15 +228,20 @@ body {
|
|||
width: 4.5em;
|
||||
}
|
||||
|
||||
.animation-root > .uncontrolled {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Animation timeline component */
|
||||
|
||||
.animation-timeline {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Useful for positioning animations or keyframes in the timeline */
|
||||
.animation-detail .track-container,
|
||||
.animation-timeline .track-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -251,41 +254,35 @@ body {
|
|||
|
||||
.animation-timeline .scrubber-wrapper {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
left: var(--timeline-sidebar-width);
|
||||
/* Leave the width of a marker right of a track so the 100% markers can be
|
||||
selected easily */
|
||||
right: var(--keyframes-marker-size);
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.animation-timeline .scrubber {
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
/* Make the scrubber as tall as the viewport minus the toolbar height and the
|
||||
header-wrapper's borders */
|
||||
height: calc(100vh - var(--toolbar-height) - 1px);
|
||||
min-height: 100%;
|
||||
width: 0;
|
||||
border-right: 1px solid red;
|
||||
box-sizing: border-box;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
||||
/* The scrubber handle is a transparent element displayed on top of the scrubber
|
||||
line that allows users to drag it */
|
||||
.animation-timeline .scrubber .scrubber-handle {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
/* Make it thick enough for easy dragging */
|
||||
width: 6px;
|
||||
right: -1.5px;
|
||||
width: 12px;
|
||||
cursor: col-resize;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.animation-timeline .scrubber .scrubber-handle::before {
|
||||
content: "";
|
||||
position: sticky;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
border-top: 5px solid red;
|
||||
|
@ -293,6 +290,14 @@ body {
|
|||
border-right: 5px solid transparent;
|
||||
}
|
||||
|
||||
.animation-timeline .scrubber .scrubber-handle .scrubber-line {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
left: 5px;
|
||||
width: 0;
|
||||
border-right: 1px solid red;
|
||||
}
|
||||
|
||||
.animation-timeline .time-header {
|
||||
min-height: var(--timeline-animation-height);
|
||||
cursor: col-resize;
|
||||
|
@ -314,29 +319,33 @@ body {
|
|||
border-bottom: 1px solid var(--time-graduation-border-color);
|
||||
z-index: 3;
|
||||
height: var(--timeline-animation-height);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.animation-timeline .time-body {
|
||||
height: 100%;
|
||||
top: var(--timeline-animation-height);
|
||||
}
|
||||
|
||||
.progress-tick-container .progress-tick,
|
||||
.animation-timeline .time-body .time-tick {
|
||||
-moz-user-select: none;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
/* When scroll bar is shown, make it covers entire time-body */
|
||||
height: 100%;
|
||||
/* When scroll bar is hidden, make it as tall as the viewport minus the
|
||||
timeline animation height and the header-wrapper's borders */
|
||||
min-height: calc(100vh - var(--timeline-animation-height) - 1px);
|
||||
}
|
||||
|
||||
.progress-tick-container .progress-tick::before,
|
||||
.animation-timeline .time-body .time-tick::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
width: 0;
|
||||
border-left: 0.5px solid var(--time-graduation-border-color);
|
||||
}
|
||||
|
||||
.animation-timeline .animations {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
margin-top: 0;
|
||||
|
@ -350,13 +359,15 @@ body {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
/* We want animations' background colors to alternate, but each animation has
|
||||
a sibling (hidden by default) that contains the animated properties and
|
||||
keyframes, so we need to alternate every 4 elements. */
|
||||
.animation-timeline .animation:nth-child(4n+1) {
|
||||
/* Display animations' background colors to alternate. */
|
||||
.animation-timeline .animation:nth-child(2n+1) {
|
||||
background-color: var(--even-animation-timeline-background-color);
|
||||
}
|
||||
|
||||
.animation-timeline .animation:last-child {
|
||||
margin-bottom: calc(var(--timeline-animation-height) / 2);
|
||||
}
|
||||
|
||||
.animation-timeline .animation .target {
|
||||
width: var(--timeline-sidebar-width);
|
||||
height: 100%;
|
||||
|
@ -487,7 +498,6 @@ body {
|
|||
}
|
||||
|
||||
/* Animation target node gutter, contains a preview of the dom node */
|
||||
|
||||
.animation-target {
|
||||
background-color: var(--theme-toolbar-background);
|
||||
padding: 0 4px;
|
||||
|
@ -520,24 +530,16 @@ body {
|
|||
|
||||
/* Inline keyframes info in the timeline */
|
||||
|
||||
.animation-timeline .animated-properties:not(.selected) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.animation-timeline .animated-properties {
|
||||
background-color: var(--theme-selection-background-semitransparent);
|
||||
}
|
||||
|
||||
.animation-timeline .animated-properties .property {
|
||||
.animation-detail .animated-properties .property {
|
||||
height: var(--timeline-animation-height);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.animation-timeline .animated-properties .property:nth-child(2n) {
|
||||
.animation-detail .animated-properties .property:nth-child(2n) {
|
||||
background-color: var(--even-animation-timeline-background-color);
|
||||
}
|
||||
|
||||
.animation-timeline .animated-properties .name {
|
||||
.animation-detail .animated-properties .name {
|
||||
width: var(--timeline-sidebar-width);
|
||||
padding-right: var(--keyframes-marker-size);
|
||||
box-sizing: border-box;
|
||||
|
@ -549,24 +551,24 @@ body {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.animation-timeline .animated-properties .name div {
|
||||
.animation-detail .animated-properties .name div {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.animated-properties.cssanimation {
|
||||
.animation-detail .animated-properties.cssanimation {
|
||||
--background-color: var(--theme-contrast-background);
|
||||
}
|
||||
|
||||
.animated-properties.csstransition {
|
||||
.animation-detail .animated-properties.csstransition {
|
||||
--background-color: var(--theme-highlight-blue);
|
||||
}
|
||||
|
||||
.animated-properties.scriptanimation {
|
||||
.animation-detail .animated-properties.scriptanimation {
|
||||
--background-color: var(--theme-graphs-green);
|
||||
}
|
||||
|
||||
.animation-timeline .animated-properties .oncompositor::before {
|
||||
.animation-detail .animated-properties .oncompositor::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 17px;
|
||||
|
@ -576,11 +578,11 @@ body {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.animation-timeline .animated-properties .warning {
|
||||
.animation-detail .animated-properties .warning {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
.animation-timeline .animated-properties .frames {
|
||||
.animation-detail .animated-properties .frames {
|
||||
/* The frames list is absolutely positioned and the left and width properties
|
||||
are dynamically set from javascript to match the animation's startTime and
|
||||
duration */
|
||||
|
@ -606,7 +608,6 @@ body {
|
|||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
|
||||
.keyframes .frame {
|
||||
|
@ -670,11 +671,62 @@ body {
|
|||
|
||||
.keyframes svg path.color {
|
||||
stroke: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.animation-detail {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background-color: var(--theme-body-background);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.animation-detail .animation-detail-header {
|
||||
height: var(--toolbar-height);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.animation-detail .animation-detail-header > div {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
height: var(--toolbar-height);
|
||||
line-height: var(--toolbar-height);
|
||||
background-color: var(--theme-body-background);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.animation-detail .animation-detail-header > div > div {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.animation-detail .animation-detail-header > div > div:first-child {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.animation-detail .animation-detail-header > div > div:nth-child(2) {
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.animation-detail .animation-detail-body {
|
||||
position: relative;
|
||||
background-color: var(--theme-body-background);
|
||||
}
|
||||
|
||||
.animation-detail .animation-detail-body .animated-properties {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.animated-properties-header {
|
||||
min-height: var(--timeline-animation-height);
|
||||
-moz-user-select: none;
|
||||
position: sticky;
|
||||
top: var(--timeline-animation-height);
|
||||
min-height: var(--timeline-animation-height);
|
||||
padding-top: 2px;
|
||||
z-index: 3;
|
||||
background-color: var(--theme-body-background);
|
||||
}
|
||||
|
||||
.animated-properties-header .header-item:nth-child(2) {
|
||||
|
@ -682,7 +734,7 @@ body {
|
|||
}
|
||||
|
||||
.animated-properties-header .header-item:nth-child(3) {
|
||||
right: 0;
|
||||
right: -0.5px;
|
||||
border-left: none;
|
||||
border-right: 0.5px solid var(--time-graduation-border-color);
|
||||
}
|
||||
|
@ -694,3 +746,8 @@ body {
|
|||
.progress-tick-container .progress-tick:nth-child(3) {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.animated-properties-body .property:last-child {
|
||||
/* To display animation progress graph clealy when the scroll is bottom. */
|
||||
padding-bottom: calc(var(--timeline-animation-height) / 2);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче