示例#1
0
  init: function(containerEl) {
    this.win = containerEl.ownerDocument.defaultView;

    this.rootWrapperEl = createNode({
      parent: containerEl,
      attributes: {
        "class": "animation-timeline"
      }
    });

    this.scrubberEl = createNode({
      parent: this.rootWrapperEl,
      attributes: {
        "class": "scrubber"
      }
    });

    this.timeHeaderEl = createNode({
      parent: this.rootWrapperEl,
      attributes: {
        "class": "time-header"
      }
    });
    this.timeHeaderEl.addEventListener("mousedown", this.onTimeHeaderMouseDown);

    this.animationsEl = createNode({
      parent: this.rootWrapperEl,
      nodeType: "ul",
      attributes: {
        "class": "animations"
      }
    });
  },
示例#2
0
  render: function (animations) {
    let allRates = this.getAnimationsRates(animations);
    let hasOneRate = allRates.length === 1;

    this.selectEl.innerHTML = "";

    if (!hasOneRate) {
      // When the animations displayed have mixed playback rates, we can't
      // select any of the predefined ones, instead, insert an empty rate.
      createNode({
        parent: this.selectEl,
        nodeType: "option",
        attributes: {value: "", selector: "true"},
        textContent: "-"
      });
    }
    for (let rate of this.getAllRates(animations)) {
      let option = createNode({
        parent: this.selectEl,
        nodeType: "option",
        attributes: {value: rate},
        textContent: L10N.getFormatStr("player.playbackRateLabel", rate)
      });

      // If there's only one rate and this is the option for it, select it.
      if (hasOneRate && rate === allRates[0]) {
        option.setAttribute("selected", "true");
      }
    }
  },
示例#3
0
  drawTimeBlock: function({state}, el) {
    let width = el.offsetWidth;

    // Create a container element to hold the delay and iterations.
    // It is positioned according to its delay (divided by the playbackrate),
    // and its width is according to its duration (divided by the playbackrate).
    let start = state.previousStartTime || 0;
    let duration = state.duration;
    let rate = state.playbackRate;
    let count = state.iterationCount;
    let delay = state.delay || 0;

    let x = TimeScale.startTimeToDistance(start + (delay / rate), width);
    let w = TimeScale.durationToDistance(duration / rate, width);

    let iterations = createNode({
      parent: el,
      attributes: {
        "class": state.type + " iterations" + (count ? "" : " infinite"),
        // Individual iterations are represented by setting the size of the
        // repeating linear-gradient.
        "style": `left:${x}px;
                  width:${w * (count || 1)}px;
                  background-size:${Math.max(w, 2)}px 100%;`
      }
    });

    // The animation name is displayed over the iterations.
    // Note that in case of negative delay, we push the name towards the right
    // so the delay can be shown.
    createNode({
      parent: iterations,
      attributes: {
        "class": "name",
        "title": this.getAnimationTooltipText(state),
        "style": delay < 0
                 ? "margin-left:" +
                   TimeScale.durationToDistance(Math.abs(delay), width) + "px"
                 : ""
      },
      textContent: state.name
    });

    // Delay.
    if (delay) {
      // Negative delays need to start at 0.
      let x = TimeScale.durationToDistance((delay < 0 ? 0 : delay) / rate, width);
      let w = TimeScale.durationToDistance(Math.abs(delay) / rate, width);
      createNode({
        parent: iterations,
        attributes: {
          "class": "delay" + (delay < 0 ? " negative" : ""),
          "style": `left:-${x}px;
                    width:${w}px;`
        }
      });
    }
  }
示例#4
0
  drawHeaderAndBackground: function() {
    let width = this.timeHeaderEl.offsetWidth;
    let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
    let minTimeInterval = TIME_GRADUATION_MIN_SPACING *
                          animationDuration / width;
    let intervalLength = findOptimalTimeInterval(minTimeInterval);
    let intervalWidth = intervalLength * width / animationDuration;

    drawGraphElementBackground(this.win.document, "time-graduations",
                               width, intervalWidth);

    // And the time graduation header.
    this.timeHeaderEl.innerHTML = "";

    for (let i = 0; i <= width / intervalWidth; i++) {
      let pos = 100 * i * intervalWidth / width;

      createNode({
        parent: this.timeHeaderEl,
        nodeType: "span",
        attributes: {
          "class": "time-tick",
          "style": `left:${pos}%`
        },
        textContent: TimeScale.formatTime(TimeScale.distanceToRelativeTime(pos))
      });
    }
  }
示例#5
0
  render: function ({keyframes, propertyName, animation, animationType}) {
    this.keyframes = keyframes;
    this.propertyName = propertyName;
    this.animation = animation;

    // 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;

    // Minimum segment duration is the duration of one pixel.
    const minSegmentDuration =
      totalDuration / this.containerEl.clientWidth;

    // Create graph helper to render the animation property graph.
    const win = this.containerEl.ownerGlobal;
    const graphHelper =
      new ProgressGraphHelper(win, propertyName, animationType, keyframes, totalDuration);

    renderPropertyGraph(graphEl, totalDuration, minSegmentDuration, graphHelper);

    // 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) {
      createNode({
        parent: this.keyframesEl,
        attributes: {
          "class": "frame",
          "style": `left:${frame.offset * 100}%;`,
          "data-offset": frame.offset,
          "data-property": propertyName,
          "title": frame.value
        }
      });
    }
  }
示例#6
0
  render: function ({keyframes, propertyName, animation}) {
    this.keyframes = keyframes;
    this.propertyName = propertyName;
    this.animation = animation;

    let iterationStartOffset =
      animation.state.iterationStart % 1 == 0
      ? 0
      : 1 - animation.state.iterationStart % 1;

    this.keyframesEl.classList.add(animation.state.type);
    for (let frame of this.keyframes) {
      let offset = frame.offset + iterationStartOffset;
      createNode({
        parent: this.keyframesEl,
        attributes: {
          "class": "frame",
          "style": `left:${offset * 100}%;`,
          "data-offset": frame.offset,
          "data-property": propertyName,
          "title": frame.value
        }
      });
    }
  },
示例#7
0
  init: function (containerEl) {
    this.containerEl = containerEl;

    this.keyframesEl = createNode({
      parent: this.containerEl,
      attributes: {"class": "keyframes"}
    });
  },
示例#8
0
  init: function(containerEl) {
    this.selectEl = createNode({
      parent: containerEl,
      nodeType: "select",
      attributes: {"class": "devtools-button"}
    });

    this.selectEl.addEventListener("change", this.onRateChanged);
  },
示例#9
0
  init: function (containerEl) {
    this.containerEl = containerEl;

    this.keyframesEl = createNode({
      parent: this.containerEl,
      attributes: {"class": "keyframes"}
    });

    this.containerEl.addEventListener("click", this.onClick);
  },
示例#10
0
 renderProgressIndicator: function () {
   // The wrapper represents the area which the indicator is displayable.
   const progressIndicatorWrapperEl = createNode({
     parent: this.containerEl,
     attributes: {
       "class": "track-container progress-indicator-wrapper"
     }
   });
   this.progressIndicatorEl = createNode({
     parent: progressIndicatorWrapperEl,
     attributes: {
       "class": "progress-indicator"
     }
   });
   createNode({
     parent: this.progressIndicatorEl,
     attributes: {
       "class": "progress-indicator-shape"
     }
   });
 },
示例#11
0
  init: function (containerEl) {
    this.selectEl = createNode({
      parent: containerEl,
      nodeType: "select",
      attributes: {
        "class": "devtools-button",
        "title": L10N.getStr("timeline.rateSelectorTooltip")
      }
    });

    this.selectEl.addEventListener("change", this.onRateChanged);
  },
示例#12
0
  renderAnimatedPropertiesHeader: function () {
    // Add animated property header.
    const headerEl = createNode({
      parent: this.containerEl,
      attributes: { "class": "animated-properties-header" }
    });

    // Add progress tick container.
    const progressTickContainerEl = createNode({
      parent: this.containerEl,
      attributes: { "class": "progress-tick-container track-container" }
    });

    // Add label container.
    const headerLabelContainerEl = createNode({
      parent: headerEl,
      attributes: { "class": "track-container" }
    });

    // Add labels
    for (let label of [L10N.getFormatStr("detail.propertiesHeader.percentage", 0),
                       L10N.getFormatStr("detail.propertiesHeader.percentage", 50),
                       L10N.getFormatStr("detail.propertiesHeader.percentage", 100)]) {
      createNode({
        parent: progressTickContainerEl,
        nodeType: "span",
        attributes: { "class": "progress-tick" }
      });
      createNode({
        parent: headerLabelContainerEl,
        nodeType: "label",
        attributes: { "class": "header-item" },
        textContent: label
      });
    }
  },
示例#13
0
  init: function(containerEl) {
    this.win = containerEl.ownerDocument.defaultView;

    this.rootWrapperEl = createNode({
      parent: containerEl,
      attributes: {
        "class": "animation-timeline"
      }
    });

    let scrubberContainer = createNode({
      parent: this.rootWrapperEl,
      attributes: {"class": "scrubber-wrapper track-container"}
    });

    this.scrubberEl = createNode({
      parent: scrubberContainer,
      attributes: {
        "class": "scrubber"
      }
    });

    this.scrubberHandleEl = createNode({
      parent: this.scrubberEl,
      attributes: {
        "class": "scrubber-handle"
      }
    });
    this.scrubberHandleEl.addEventListener("mousedown",
      this.onScrubberMouseDown);

    this.timeHeaderEl = createNode({
      parent: this.rootWrapperEl,
      attributes: {
        "class": "time-header track-container"
      }
    });
    this.timeHeaderEl.addEventListener("mousedown",
      this.onScrubberMouseDown);

    this.animationsEl = createNode({
      parent: this.rootWrapperEl,
      nodeType: "ul",
      attributes: {
        "class": "animations"
      }
    });

    this.win.addEventListener("resize",
      this.onWindowResize);
  },
/**
 * 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`;
  pathSegments.forEach(pathSegment => {
    path += ` L${ pathSegment.x },${ pathSegment.y }`;
  });
  path += ` L${ pathSegments[pathSegments.length - 1].x },0 Z`;
  // Append and return the path element.
  return createNode({
    parent: parentEl,
    namespace: SVG_NS,
    nodeType: "path",
    attributes: {
      "d": path,
      "class": cls,
      "vector-effect": "non-scaling-stroke",
      "transform": "scale(1, -1)"
    }
  });
}
示例#15
0
  drawHeaderAndBackground: function() {
    let width = this.timeHeaderEl.offsetWidth;
    let scale = width / (TimeScale.maxEndTime - TimeScale.minStartTime);
    drawGraphElementBackground(this.win.document, "time-graduations",
                               width, scale);

    // And the time graduation header.
    this.timeHeaderEl.innerHTML = "";
    let interval = findOptimalTimeInterval(scale, TIME_GRADUATION_MIN_SPACING);
    for (let i = 0; i < width; i += interval) {
      createNode({
        parent: this.timeHeaderEl,
        nodeType: "span",
        attributes: {
          "class": "time-tick",
          "style": `left:${i}px`
        },
        textContent: TimeScale.formatTime(
          TimeScale.distanceToRelativeTime(i, width))
      });
    }
  },
示例#16
0
  init: function(containerEl) {
    let document = containerEl.ownerDocument;

    // Init the markup for displaying the target node.
    this.el = createNode({
      parent: containerEl,
      attributes: {
        "class": "animation-target"
      }
    });

    // Icon to select the node in the inspector.
    this.selectNodeEl = createNode({
      parent: this.el,
      nodeType: "span",
      attributes: {
        "class": "node-selector"
      }
    });

    // Wrapper used for mouseover/out event handling.
    this.previewEl = createNode({
      parent: this.el,
      nodeType: "span"
    });

    if (!this.options.compact) {
      this.previewEl.appendChild(document.createTextNode("<"));
    }

    // Tag name.
    this.tagNameEl = createNode({
      parent: this.previewEl,
      nodeType: "span",
      attributes: {
        "class": "tag-name theme-fg-color3"
      }
    });

    // Id attribute container.
    this.idEl = createNode({
      parent: this.previewEl,
      nodeType: "span"
    });

    if (!this.options.compact) {
      createNode({
        parent: this.idEl,
        nodeType: "span",
        attributes: {
          "class": "attribute-name theme-fg-color2"
        },
        textContent: "id"
      });
      this.idEl.appendChild(document.createTextNode("=\""));
    } else {
      createNode({
        parent: this.idEl,
        nodeType: "span",
        attributes: {
          "class": "theme-fg-color2"
        },
        textContent: "#"
      });
    }

    createNode({
      parent: this.idEl,
      nodeType: "span",
      attributes: {
        "class": "attribute-value theme-fg-color6"
      }
    });

    if (!this.options.compact) {
      this.idEl.appendChild(document.createTextNode("\""));
    }

    // Class attribute container.
    this.classEl = createNode({
      parent: this.previewEl,
      nodeType: "span"
    });

    if (!this.options.compact) {
      createNode({
        parent: this.classEl,
        nodeType: "span",
        attributes: {
          "class": "attribute-name theme-fg-color2"
        },
        textContent: "class"
      });
      this.classEl.appendChild(document.createTextNode("=\""));
    } else {
      createNode({
        parent: this.classEl,
        nodeType: "span",
        attributes: {
          "class": "theme-fg-color6"
        },
        textContent: "."
      });
    }

    createNode({
      parent: this.classEl,
      nodeType: "span",
      attributes: {
        "class": "attribute-value theme-fg-color6"
      }
    });

    if (!this.options.compact) {
      this.classEl.appendChild(document.createTextNode("\""));
      this.previewEl.appendChild(document.createTextNode(">"));
    }

    // Init events for highlighting and selecting the node.
    this.previewEl.addEventListener("mouseover", this.onPreviewMouseOver);
    this.previewEl.addEventListener("mouseout", this.onPreviewMouseOut);
    this.selectNodeEl.addEventListener("click", this.onSelectNodeClick);

    // Start to listen for markupmutation events.
    this.inspector.on("markupmutation", this.onMarkupMutations);
  },
示例#17
0
  render: function(animations, documentCurrentTime) {
    this.unrender();

    this.animations = animations;
    if (!this.animations.length) {
      return;
    }

    // Loop first to set the time scale for all current animations.
    for (let {state} of animations) {
      TimeScale.addAnimation(state);
    }

    this.drawHeaderAndBackground();

    for (let animation of this.animations) {
      animation.on("changed", this.onAnimationStateChanged);

      // Each line contains the target animated node and the animation time
      // block.
      let animationEl = createNode({
        parent: this.animationsEl,
        nodeType: "li",
        attributes: {
          "class": "animation"
        }
      });

      // Left sidebar for the animated node.
      let animatedNodeEl = createNode({
        parent: animationEl,
        attributes: {
          "class": "target"
        }
      });

      let timeBlockEl = createNode({
        parent: animationEl,
        attributes: {
          "class": "time-block"
        }
      });

      this.drawTimeBlock(animation, timeBlockEl);

      // Draw the animated node target.
      let targetNode = new AnimationTargetNode(this.inspector, {compact: true});
      targetNode.init(animatedNodeEl);
      targetNode.render(animation);

      // Save the targetNode so it can be destroyed later.
      this.targetNodes.push(targetNode);
    }

    // Use the document's current time to position the scrubber (if the server
    // doesn't provide it, hide the scrubber entirely).
    // Note that because the currentTime was sent via the protocol, some time
    // may have gone by since then, and so the scrubber might be a bit late.
    if (!documentCurrentTime) {
      this.scrubberEl.style.display = "none";
    } else {
      this.scrubberEl.style.display = "block";
      this.startAnimatingScrubber(documentCurrentTime);
    }
  },
示例#18
0
  render: function(animations, documentCurrentTime) {
    this.unrender();

    this.animations = animations;
    if (!this.animations.length) {
      return;
    }

    // Loop first to set the time scale for all current animations.
    for (let {state} of animations) {
      TimeScale.addAnimation(state);
    }

    this.drawHeaderAndBackground();

    for (let animation of this.animations) {
      animation.on("changed", this.onAnimationStateChanged);

      // Each line contains the target animated node and the animation time
      // block.
      let animationEl = createNode({
        parent: this.animationsEl,
        nodeType: "li",
        attributes: {
          "class": "animation " +
                   animation.state.type +
                   (animation.state.isRunningOnCompositor ? " fast-track" : "")
        }
      });

      // 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"
        }
      });

      let details = new AnimationDetails();
      details.init(detailsEl);
      details.on("frame-selected", this.onFrameSelected);
      this.details.push(details);

      // Left sidebar for the animated node.
      let animatedNodeEl = createNode({
        parent: animationEl,
        attributes: {
          "class": "target"
        }
      });

      // Draw the animated node target.
      let targetNode = new AnimationTargetNode(this.inspector, {compact: true});
      targetNode.init(animatedNodeEl);
      targetNode.render(animation);
      this.targetNodes.push(targetNode);

      // Right-hand part contains the timeline itself (called time-block here).
      let timeBlockEl = createNode({
        parent: animationEl,
        attributes: {
          "class": "time-block track-container"
        }
      });

      // Draw the animation time block.
      let timeBlock = new AnimationTimeBlock();
      timeBlock.init(timeBlockEl);
      timeBlock.render(animation);
      this.timeBlocks.push(timeBlock);

      timeBlock.on("selected", this.onAnimationSelected);
    }

    // Use the document's current time to position the scrubber (if the server
    // doesn't provide it, hide the scrubber entirely).
    // Note that because the currentTime was sent via the protocol, some time
    // may have gone by since then, and so the scrubber might be a bit late.
    if (!documentCurrentTime) {
      this.scrubberEl.style.display = "none";
    } else {
      this.scrubberEl.style.display = "block";
      this.startAnimatingScrubber(this.wasRewound()
                                  ? TimeScale.minStartTime
                                  : documentCurrentTime);
    }
  },
  render: function (animation) {
    this.unrender();

    this.animation = animation;
    let {state} = this.animation;

    // Create a container element to hold the delay and iterations.
    // It is positioned according to its delay (divided by the playbackrate),
    // and its width is according to its duration (divided by the playbackrate).
    const {x, delayX, delayW, endDelayX, endDelayW} =
      TimeScale.getAnimationDimensions(animation);

    // Animation summary graph element.
    const summaryEl = createNode({
      parent: this.containerEl,
      namespace: "http://www.w3.org/2000/svg",
      nodeType: "svg",
      attributes: {
        "class": "summary",
        "preserveAspectRatio": "none",
        "style": `left: ${ x - (state.delay > 0 ? delayW : 0) }%`
      }
    });

    // Total displayed duration
    const totalDisplayedDuration = state.playbackRate * TimeScale.getDuration();

    // Calculate stroke height in viewBox to display stroke of path.
    const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight;

    // Set viewBox
    summaryEl.setAttribute("viewBox",
                           `${ state.delay < 0 ? state.delay : 0 }
                            -${ 1 + strokeHeightForViewBox }
                            ${ totalDisplayedDuration }
                            ${ 1 + strokeHeightForViewBox * 2 }`);

    // Get a helper function that returns the path segment of timing-function.
    const segmentHelper = getSegmentHelper(state, this.win);

    // Minimum segment duration is the duration of one pixel.
    const minSegmentDuration =
      totalDisplayedDuration / this.containerEl.clientWidth;
    // Minimum progress threshold.
    let minProgressThreshold = MIN_PROGRESS_THRESHOLD;
    // If the easing is step function,
    // minProgressThreshold should be changed by the steps.
    const stepFunction = state.easing.match(/steps\((\d+)/);
    if (stepFunction) {
      minProgressThreshold = 1 / (parseInt(stepFunction[1], 10) + 1);
    }

    // Starting time of main iteration.
    let mainIterationStartTime = 0;
    let iterationStart = state.iterationStart;
    let iterationCount = state.iterationCount ? state.iterationCount : Infinity;

    // Append delay.
    if (state.delay > 0) {
      renderDelay(summaryEl, state, segmentHelper);
      mainIterationStartTime = state.delay;
    } else {
      const negativeDelayCount = -state.delay / state.duration;
      // Move to forward the starting point for negative delay.
      iterationStart += negativeDelayCount;
      // Consume iteration count by negative delay.
      if (iterationCount !== Infinity) {
        iterationCount -= negativeDelayCount;
      }
    }

    // Append 1st section of iterations,
    // This section is only useful in cases where iterationStart has decimals.
    // e.g.
    // if { iterationStart: 0.25, iterations: 3 }, firstSectionCount is 0.75.
    const firstSectionCount =
      iterationStart % 1 === 0
      ? 0 : Math.min(iterationCount, 1) - iterationStart % 1;
    if (firstSectionCount) {
      renderFirstIteration(summaryEl, state, mainIterationStartTime,
                           firstSectionCount, minSegmentDuration,
                           minProgressThreshold, segmentHelper);
    }

    if (iterationCount === Infinity) {
      // If the animation repeats infinitely,
      // we fill the remaining area with iteration paths.
      renderInfinity(summaryEl, state, mainIterationStartTime,
                     firstSectionCount, totalDisplayedDuration,
                     minSegmentDuration, minProgressThreshold, segmentHelper);
    } else {
      // Otherwise, we show remaining iterations, endDelay and fill.

      // Append forwards fill-mode.
      if (state.fill === "both" || state.fill === "forwards") {
        renderForwardsFill(summaryEl, state, mainIterationStartTime,
                           iterationCount, totalDisplayedDuration,
                           segmentHelper);
      }

      // Append middle section of iterations.
      // e.g.
      // if { iterationStart: 0.25, iterations: 3 }, middleSectionCount is 2.
      const middleSectionCount =
        Math.floor(iterationCount - firstSectionCount);
      renderMiddleIterations(summaryEl, state, mainIterationStartTime,
                             firstSectionCount, middleSectionCount,
                             minSegmentDuration, minProgressThreshold,
                             segmentHelper);

      // Append last section of iterations, if there is remaining iteration.
      // e.g.
      // if { iterationStart: 0.25, iterations: 3 }, lastSectionCount is 0.25.
      const lastSectionCount =
        iterationCount - middleSectionCount - firstSectionCount;
      if (lastSectionCount) {
        renderLastIteration(summaryEl, state, mainIterationStartTime,
                            firstSectionCount, middleSectionCount,
                            lastSectionCount, minSegmentDuration,
                            minProgressThreshold, segmentHelper);
      }

      // Append endDelay.
      if (state.endDelay > 0) {
        renderEndDelay(summaryEl, state,
                       mainIterationStartTime, iterationCount, segmentHelper);
      }
    }

    // Append negative delay (which overlap the animation).
    if (state.delay < 0) {
      segmentHelper.animation.effect.timing.fill = "both";
      segmentHelper.asOriginalBehavior = false;
      renderNegativeDelayHiddenProgress(summaryEl, state, minSegmentDuration,
                                        minProgressThreshold, segmentHelper);
    }
    // Append negative endDelay (which overlap the animation).
    if (state.iterationCount && state.endDelay < 0) {
      if (segmentHelper.asOriginalBehavior) {
        segmentHelper.animation.effect.timing.fill = "both";
        segmentHelper.asOriginalBehavior = false;
      }
      renderNegativeEndDelayHiddenProgress(summaryEl, state,
                                           minSegmentDuration,
                                           minProgressThreshold,
                                           segmentHelper);
    }

    // The animation name is displayed over the animation.
    createNode({
      parent: createNode({
        parent: this.containerEl,
        attributes: {
          "class": "name",
          "title": this.getTooltipText(state)
        },
      }),
      textContent: state.name
    });

    // Delay.
    if (state.delay) {
      // Negative delays need to start at 0.
      createNode({
        parent: this.containerEl,
        attributes: {
          "class": "delay"
                   + (state.delay < 0 ? " negative" : " positive")
                   + (state.fill === "both" ||
                      state.fill === "backwards" ? " fill" : ""),
          "style": `left:${ delayX }%; width:${ delayW }%;`
        }
      });
    }

    // endDelay
    if (state.iterationCount && state.endDelay) {
      createNode({
        parent: this.containerEl,
        attributes: {
          "class": "end-delay"
                   + (state.endDelay < 0 ? " negative" : " positive")
                   + (state.fill === "both" ||
                      state.fill === "forwards" ? " fill" : ""),
          "style": `left:${ endDelayX }%; width:${ endDelayW }%;`
        }
      });
    }
  },
示例#20
0
  init: function(containerEl) {
    let document = containerEl.ownerDocument;

    // Init the markup for displaying the target node.
    this.el = createNode({
      parent: containerEl,
      attributes: {
        "class": "animation-target"
      }
    });

    // Icon to select the node in the inspector.
    this.highlightNodeEl = createNode({
      parent: this.el,
      nodeType: "span",
      attributes: {
        "class": "node-highlighter",
        "title": L10N.getStr("inspector.nodePreview.highlightNodeLabel")
      }
    });

    // Wrapper used for mouseover/out event handling.
    this.previewEl = createNode({
      parent: this.el,
      nodeType: "span",
      attributes: {
        "title": L10N.getStr("inspector.nodePreview.selectNodeLabel")
      }
    });

    if (!this.options.compact) {
      this.previewEl.appendChild(document.createTextNode("<"));
    }

    // Tag name.
    this.tagNameEl = createNode({
      parent: this.previewEl,
      nodeType: "span",
      attributes: {
        "class": "tag-name theme-fg-color3"
      }
    });

    // Id attribute container.
    this.idEl = createNode({
      parent: this.previewEl,
      nodeType: "span"
    });

    if (!this.options.compact) {
      createNode({
        parent: this.idEl,
        nodeType: "span",
        attributes: {
          "class": "attribute-name theme-fg-color2"
        },
        textContent: "id"
      });
      this.idEl.appendChild(document.createTextNode("=\""));
    } else {
      createNode({
        parent: this.idEl,
        nodeType: "span",
        attributes: {
          "class": "theme-fg-color6"
        },
        textContent: "#"
      });
    }

    createNode({
      parent: this.idEl,
      nodeType: "span",
      attributes: {
        "class": "attribute-value theme-fg-color6"
      }
    });

    if (!this.options.compact) {
      this.idEl.appendChild(document.createTextNode("\""));
    }

    // Class attribute container.
    this.classEl = createNode({
      parent: this.previewEl,
      nodeType: "span"
    });

    if (!this.options.compact) {
      createNode({
        parent: this.classEl,
        nodeType: "span",
        attributes: {
          "class": "attribute-name theme-fg-color2"
        },
        textContent: "class"
      });
      this.classEl.appendChild(document.createTextNode("=\""));
    } else {
      createNode({
        parent: this.classEl,
        nodeType: "span",
        attributes: {
          "class": "theme-fg-color6"
        },
        textContent: "."
      });
    }

    createNode({
      parent: this.classEl,
      nodeType: "span",
      attributes: {
        "class": "attribute-value theme-fg-color6"
      }
    });

    if (!this.options.compact) {
      this.classEl.appendChild(document.createTextNode("\""));
      this.previewEl.appendChild(document.createTextNode(">"));
    }

    this.startListeners();
  },
示例#21
0
  render: function (animation, tracks) {
    this.unrender();

    this.animation = animation;
    let {state} = this.animation;

    // Create a container element to hold the delay and iterations.
    // It is positioned according to its delay (divided by the playbackrate),
    // and its width is according to its duration (divided by the playbackrate).
    const {x, delayX, delayW, endDelayX, endDelayW} =
      TimeScale.getAnimationDimensions(animation);

    // Animation summary graph element.
    const summaryEl = createSVGNode({
      parent: this.containerEl,
      nodeType: "svg",
      attributes: {
        "class": "summary",
        "preserveAspectRatio": "none",
        "style": `left: ${ x - (state.delay > 0 ? delayW : 0) }%`
      }
    });

    // Total displayed duration
    const totalDisplayedDuration = state.playbackRate * TimeScale.getDuration();

    // Calculate stroke height in viewBox to display stroke of path.
    const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight;

    // Set viewBox
    summaryEl.setAttribute("viewBox",
                           `${ state.delay < 0 ? state.delay : 0 }
                            -${ 1 + strokeHeightForViewBox }
                            ${ totalDisplayedDuration }
                            ${ 1 + strokeHeightForViewBox * 2 }`);

    // Minimum segment duration is the duration of one pixel.
    const minSegmentDuration = totalDisplayedDuration / this.containerEl.clientWidth;
    // Minimum progress threshold for effect timing.
    const minEffectProgressThreshold = getPreferredProgressThreshold(state.easing);

    // Render summary graph.
    // The summary graph is constructed from keyframes's easing and effect timing.
    const graphHelper = new SummaryGraphHelper(this.win, state, minSegmentDuration);
    renderKeyframesEasingGraph(summaryEl, state, totalDisplayedDuration,
                               minEffectProgressThreshold, tracks, graphHelper);
    if (state.easing !== "linear") {
      renderEffectEasingGraph(summaryEl, state, totalDisplayedDuration,
                              minEffectProgressThreshold, graphHelper);
    }
    graphHelper.destroy();

    // The animation name is displayed over the animation.
    const nameEl = createNode({
      parent: this.containerEl,
      attributes: {
        "class": "name",
        "title": this.getTooltipText(state)
      }
    });

    createSVGNode({
      parent: createSVGNode({
        parent: nameEl,
        nodeType: "svg",
      }),
      nodeType: "text",
      attributes: {
        "y": "50%",
        "x": "100%",
      },
      textContent: state.name
    });

    // Delay.
    if (state.delay) {
      // Negative delays need to start at 0.
      createNode({
        parent: this.containerEl,
        attributes: {
          "class": "delay"
                   + (state.delay < 0 ? " negative" : " positive")
                   + (state.fill === "both" ||
                      state.fill === "backwards" ? " fill" : ""),
          "style": `left:${ delayX }%; width:${ delayW }%;`
        }
      });
    }

    // endDelay
    if (state.iterationCount && state.endDelay) {
      createNode({
        parent: this.containerEl,
        attributes: {
          "class": "end-delay"
                   + (state.endDelay < 0 ? " negative" : " positive")
                   + (state.fill === "both" ||
                      state.fill === "forwards" ? " fill" : ""),
          "style": `left:${ endDelayX }%; width:${ endDelayW }%;`
        }
      });
    }
  },
示例#22
0
  renderAnimatedPropertiesBody: function (animationTypes) {
    // Add animated property body.
    const bodyEl = createNode({
      parent: this.containerEl,
      attributes: { "class": "animated-properties-body" }
    });

    // Move unchanged value animation to bottom in the list.
    const propertyNames = [];
    const unchangedPropertyNames = [];
    for (let propertyName in this.tracks) {
      if (!isUnchangedProperty(this.tracks[propertyName])) {
        propertyNames.push(propertyName);
      } else {
        unchangedPropertyNames.push(propertyName);
      }
    }
    Array.prototype.push.apply(propertyNames, unchangedPropertyNames);

    for (let propertyName of propertyNames) {
      let line = createNode({
        parent: bodyEl,
        attributes: {"class": "property"}
      });
      if (unchangedPropertyNames.includes(propertyName)) {
        line.classList.add("unchanged");
      }
      let {warning, className} =
        this.getPerfDataForProperty(this.animation, propertyName);
      createNode({
        // text-overflow doesn't work in flex items, so we need a second level
        // of container to actually have an ellipsis on the name.
        // See bug 972664.
        parent: createNode({
          parent: line,
          attributes: {"class": "name"}
        }),
        textContent: getCssPropertyName(propertyName),
        attributes: {"title": warning,
                     "class": className}
      });

      // Add the keyframes diagram for this property.
      let framesWrapperEl = createNode({
        parent: line,
        attributes: {"class": "track-container"}
      });

      let framesEl = createNode({
        parent: framesWrapperEl,
        attributes: {"class": "frames"}
      });

      let keyframesComponent = new Keyframes();
      keyframesComponent.init(framesEl);
      keyframesComponent.render({
        keyframes: this.tracks[propertyName],
        propertyName: propertyName,
        animation: this.animation,
        animationType: animationTypes[propertyName]
      });
      this.keyframeComponents.push(keyframesComponent);
    }
  },
  render: function (animation) {
    this.unrender();

    this.animation = animation;
    let {state} = this.animation;

    // Create a container element to hold the delay and iterations.
    // It is positioned according to its delay (divided by the playbackrate),
    // and its width is according to its duration (divided by the playbackrate).
    let {x, iterationW, delayX, delayW, negativeDelayW, endDelayX, endDelayW} =
      TimeScale.getAnimationDimensions(animation);

    // background properties for .iterations element
    let backgroundIterations = TimeScale.getIterationsBackgroundData(animation);

    createNode({
      parent: this.containerEl,
      attributes: {
        "class": "iterations" + (state.iterationCount ? "" : " infinite"),
        // Individual iterations are represented by setting the size of the
        // repeating linear-gradient.
        // The background-size, background-position, background-repeat represent
        // iterationCount and iterationStart.
        "style": `left:${x}%;
                  width:${iterationW}%;
                  background-size:${backgroundIterations.size}% 100%;
                  background-position:${backgroundIterations.position}% 0;
                  background-repeat:${backgroundIterations.repeat};`
      }
    });

    // The animation name is displayed over the iterations.
    // Note that in case of negative delay, it is pushed towards the right so
    // the delay element does not overlap.
    createNode({
      parent: createNode({
        parent: this.containerEl,
        attributes: {
          "class": "name",
          "title": this.getTooltipText(state),
          // Place the name at the same position as the iterations, but make
          // space for the negative delay if any.
          "style": `left:${x + negativeDelayW}%;
                    width:${iterationW - negativeDelayW}%;`
        },
      }),
      textContent: state.name
    });

    // Delay.
    if (state.delay) {
      // Negative delays need to start at 0.
      createNode({
        parent: this.containerEl,
        attributes: {
          "class": "delay" + (state.delay < 0 ? " negative" : ""),
          "style": `left:${delayX}%;
                    width:${delayW}%;`
        }
      });
    }

    // endDelay
    if (state.endDelay) {
      createNode({
        parent: this.containerEl,
        attributes: {
          "class": "end-delay" + (state.endDelay < 0 ? " negative" : ""),
          "style": `left:${endDelayX}%;
                    width:${endDelayW}%;`
        }
      });
    }
  },
示例#24
0
  render: Task.async(function*(animation) {
    this.unrender();

    if (!animation) {
      return;
    }
    this.animation = animation;

    // We might have been destroyed in the meantime, or the component might
    // have been re-rendered.
    if (!this.containerEl || this.animation !== animation) {
      return;
    }

    // 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");

    for (let propertyName in this.tracks) {
      let line = createNode({
        parent: this.containerEl,
        attributes: {"class": "property"}
      });

      createNode({
        // text-overflow doesn't work in flex items, so we need a second level
        // of container to actually have an ellipsis on the name.
        // See bug 972664.
        parent: createNode({
          parent: line,
          attributes: {"class": "name"},
        }),
        textContent: getCssPropertyName(propertyName)
      });

      // Add the keyframes diagram for this property.
      let framesWrapperEl = createNode({
        parent: line,
        attributes: {"class": "track-container"}
      });

      let framesEl = createNode({
        parent: framesWrapperEl,
        attributes: {"class": "frames"}
      });

      // Scale the list of keyframes according to the current time scale.
      let {x, w} = TimeScale.getAnimationDimensions(animation);
      framesEl.style.left = `${x}%`;
      framesEl.style.width = `${w}%`;

      let keyframesComponent = new Keyframes();
      keyframesComponent.init(framesEl);
      keyframesComponent.render({
        keyframes: this.tracks[propertyName],
        propertyName: propertyName,
        animation: animation
      });
      keyframesComponent.on("frame-selected", this.onFrameSelected);

      this.keyframeComponents.push(keyframesComponent);
    }
  }),
示例#25
0
  render: function (animation, tracks) {
    this.unrender();

    this.animation = animation;

    // Animation summary graph element.
    const summaryEl = createSVGNode({
      parent: this.containerEl,
      nodeType: "svg",
      attributes: {
        "class": "summary",
        "preserveAspectRatio": "none"
      }
    });
    this.updateSummaryGraphViewBox(summaryEl);

    const {state} = this.animation;
    // Total displayed duration
    const totalDisplayedDuration = this.getTotalDisplayedDuration();
    // Minimum segment duration is the duration of one pixel.
    const minSegmentDuration = totalDisplayedDuration / this.containerEl.clientWidth;
    // Minimum progress threshold for effect timing.
    const minEffectProgressThreshold = getPreferredProgressThreshold(state.easing);

    // Render summary graph.
    // The summary graph is constructed from keyframes's easing and effect timing.
    const graphHelper = new SummaryGraphHelper(this.win, state, minSegmentDuration);
    renderKeyframesEasingGraph(summaryEl, state, totalDisplayedDuration,
                               minEffectProgressThreshold, tracks, graphHelper);
    if (state.easing !== "linear") {
      renderEffectEasingGraph(summaryEl, state, totalDisplayedDuration,
                              minEffectProgressThreshold, graphHelper);
    }
    graphHelper.destroy();

    // The animation name is displayed over the animation.
    const nameEl = createNode({
      parent: this.containerEl,
      attributes: {
        "class": "name",
        "title": this.getTooltipText(state)
      }
    });

    createSVGNode({
      parent: createSVGNode({
        parent: nameEl,
        nodeType: "svg",
      }),
      nodeType: "text",
      attributes: {
        "y": "50%",
        "x": "100%",
      },
      textContent: state.name
    });

    // Delay.
    if (state.delay) {
      // Negative delays need to start at 0.
      const delayEl = createNode({
        parent: this.containerEl,
        attributes: {
          "class": "delay"
                   + (state.delay < 0 ? " negative" : " positive")
                   + (state.fill === "both" ||
                      state.fill === "backwards" ? " fill" : "")
        }
      });
      this.updateDelayBounds(delayEl);
    }

    // endDelay
    if (state.iterationCount && state.endDelay) {
      const endDelayEl = createNode({
        parent: this.containerEl,
        attributes: {
          "class": "end-delay"
                   + (state.endDelay < 0 ? " negative" : " positive")
                   + (state.fill === "both" ||
                      state.fill === "forwards" ? " fill" : "")
        }
      });
      this.updateEndDelayBounds(endDelayEl);
    }
  },
示例#26
0
  render: function(animation) {
    this.animation = animation;
    let {state} = this.animation;

    let width = this.containerEl.offsetWidth;

    // Create a container element to hold the delay and iterations.
    // It is positioned according to its delay (divided by the playbackrate),
    // and its width is according to its duration (divided by the playbackrate).
    let start = state.previousStartTime || 0;
    let duration = state.duration;
    let rate = state.playbackRate;
    let count = state.iterationCount;
    let delay = state.delay || 0;

    let x = TimeScale.startTimeToDistance(start + (delay / rate), width);
    let w = TimeScale.durationToDistance(duration / rate, width);
    let iterationW = w * (count || 1);
    let delayW = TimeScale.durationToDistance(Math.abs(delay) / rate, width);

    let iterations = createNode({
      parent: this.containerEl,
      attributes: {
        "class": state.type + " iterations" + (count ? "" : " infinite"),
        // Individual iterations are represented by setting the size of the
        // repeating linear-gradient.
        "style": `left:${x}px;
                  width:${iterationW}px;
                  background-size:${Math.max(w, 2)}px 100%;`
      }
    });

    // The animation name is displayed over the iterations.
    // Note that in case of negative delay, we push the name towards the right
    // so the delay can be shown.
    let negativeDelayW = delay < 0 ? delayW : 0;
    createNode({
      parent: iterations,
      attributes: {
        "class": "name",
        "title": this.getTooltipText(state),
        // Position the fast-track icon with background-position, and make space
        // for the negative delay with a margin-left.
        "style": "background-position:" +
                 (iterationW - FAST_TRACK_ICON_SIZE - negativeDelayW) +
                 "px center;margin-left:" + negativeDelayW + "px"
      },
      textContent: state.name
    });

    // Delay.
    if (delay) {
      // Negative delays need to start at 0.
      let delayX = TimeScale.durationToDistance(
        (delay < 0 ? 0 : delay) / rate, width);
      createNode({
        parent: iterations,
        attributes: {
          "class": "delay" + (delay < 0 ? " negative" : ""),
          "style": `left:-${delayX}px;
                    width:${delayW}px;`
        }
      });
    }
  },