动画
GoJS offers several built-in animations, enabled by default, as well as the ability to create arbitrary animations. The Diagram.animationManager handles animations within a Diagram. The AnimationManager automatically sets up and dispatches default animations, and has properties to customize and disable them. Custom animations are possible by creating instances of Animation or AnimationTrigger.
Default Animations
By default, the AnimationManager creates and runs several animations for its Diagram using a single instance of Animation, the AnimationManager.defaultAnimation. These animations occur on various commands, by the Diagram.model setter, and upon layouts. Unlike other Animations, they will be stopped if a new transaction is started during animation. GoJS will begin an animation automatically for these reasons:
Invoked by CommandHandler:
- "Collapse SubGraph" - Animates the collapsing nodes by "disappearing" them, animating their scales and positions into their group.
- "Expand SubGraph" - Expands groups by animating the scales and positions of nodes starting within the collapsed group.
- "Collapse Tree" - Animates the collapsing nodes by "disappearing" them, animating their scales and positions into the root node.
- "Expand Tree" - Expands subtrees by animating the scales and positions of descendant nodes starting within the collapsed root node.
- "Scroll To Part" - Animates the Diagram.position and may expand groups or expand subtrees to make the node visible.
- "Zoom To Fit" - Animates the Diagram.position and Diagram.scale.
Invoked by Diagram:
- "Model" - Animates all node positions when a new model is set.
- "Layout" - Animates all changed node positions on a layout.
Invoked by AnimationTriggers, if any are declared:
- "Trigger" - Animates the change of a defined GraphObject property.
The above quoted names are strings in the AnimationManager.animationReasons set.
Default Initial Animation
As of GoJS 2.1, the default initial animation fades the diagram upwards into view. Prior versions animated Part locations separately. To control the initial animation behavior, there now exists AnimationManager.initialAnimationStyle, which is set to AnimationManager,Default by default, but can be set to AnimationManager,AnimateLocations to use the animation style from GoJS 2.0. You can also set this property to AnimationManager,None and define your own initial animation using the "InitialAnimationStarting"
DiagramEvent.
Here is an example with buttons which set AnimationManager.initialAnimationStyle to the three different values, then reload the Diagram.A fourth button illustrates how one might use the "InitialAnimationStarting"
DiagramEvent to make a custom "zoom in" animation.
diagram.nodeTemplate = $(go.Node, "Auto", $(go.Shape, "RoundedRectangle", { strokeWidth: 0, fill: "lightblue" }), $(go.TextBlock, { margin: 8, font: "bold 14px sans-serif", stroke: '#333' }, new go.Binding("text", "key")) ); diagram.model = new go.GraphLinksModel([{ key: 'Alpha' }, { key: 'Beta' }, { key: 'Delta' }, { key: 'Gamma' }]); // only needed for this demonstration, this flag is used to stop // the "InitialAnimationStarting" listener when other buttons are pressed window.custom = false; window.animateDefault = function() { window.custom = false; diagram.animationManager.initialAnimationStyle = go.AnimationManager.Default; diagram.model = go.Model.fromJSON(diagram.model.toJSON()); } window.animateLocations = function() { window.custom = false; diagram.animationManager.initialAnimationStyle = go.AnimationManager.AnimateLocations; diagram.model = go.Model.fromJSON(diagram.model.toJSON()); } window.animateNone = function() { window.custom = false; diagram.animationManager.initialAnimationStyle = go.AnimationManager.None; diagram.model = go.Model.fromJSON(diagram.model.toJSON()); } window.animateCustom = function() { window.custom = true; diagram.animationManager.initialAnimationStyle = go.AnimationManager.None; // Customer listener zooms-in the Diagram on load: diagram.addDiagramListener("InitialAnimationStarting", function(e) { var animation = e.subject.defaultAnimation; if (window.custom === false) { // a different button was pressed, restore default values on the default animation: animation.easing = go.Animation.EaseInOutQuad; animation.duration = NaN; return; } animation.easing = go.Animation.EaseOutExpo; animation.duration = 1500; animation.add(e.diagram, 'scale', 0.1, 1); animation.add(e.diagram, 'opacity', 0, 1); }) diagram.model = go.Model.fromJSON(diagram.model.toJSON()); }
Limitations of Default Animations
The AnimationManager can be turned off by setting AnimationManager.isEnabled to false
. Specific default animations can be turned off or modified by overriding AnimationManager.canStart, querying the reasons it is about to start (AnimationManager.animationReasons), and potentially returning false
. The default animation will be stopped if a new transaction begins during the animation. The same is not true of other Animations, which are not stopped by new transactions, and can continue indefinitely.
Animatable Properties
By default, AnimationTriggers and Animations can animate these properties of GraphObjects:
position
location
(on Parts)scale
opacity
angle
desiredSize
width
height
background
(for solid string colors only)areaBackground
(for solid string colors only)fill
(on Shapes, for solid string colors only)strokeWidth
(on Shapes)strokeDashOffset
(on Shapes)stroke
(on Shapes, TextBlocks, for solid string colors only) Additionally Animations (but not AnimationTriggers) can animate these properties of Diagram:position
scale
opacity
It is possible to animate other properties if they are defined by the programmer -- see the section "Custom Animation Effects" below.
The AnimationTrigger Class
New in 2.1 An AnimationTrigger is used to declare GraphObject properties to animate when their value has changed. When a trigger is defined, changes to the target property will animate from the old value to the new value. In templates, triggers are defined in a similar fashion to Bindings:
// In this shape definition, two triggers are defined on a Shape. // These will cause all changes to Shape.stroke and Shape.fill to animate // from their old values to their new values. $(go.Shape, "Rectangle", { strokeWidth: 12, stroke: 'black', fill: 'white' }, new go.AnimationTrigger('stroke'), new go.AnimationTrigger('fill') )
Here is an example, with an HTML button that sets the Shape's stroke
and fill
to new random values:
diagram.nodeTemplate = $(go.Node, $(go.Shape, "Rectangle", { strokeWidth: 12, stroke: 'black', fill: 'white' }, new go.AnimationTrigger('stroke'), new go.AnimationTrigger('fill') ) ); diagram.model = new go.GraphLinksModel([{ key: 'Alpha' }]); // One node // attach this Diagram to the window to use a button window.animateTrigger1 = function() { diagram.commit(function(diag) { var node = diag.nodes.first(); node.elt(0).stroke = go.Brush.randomColor(); node.elt(0).fill = go.Brush.randomColor(); }); }
AnimationTriggers can invoke an animation immediately, starting a new animation with each property of each GraphObject that has been modified, or they can (much more efficiently) be bundled together into the default animation (AnimationManager.defaultAnimation) and begin at the end of the next transaction. These behaviors can be set with AnimationTrigger.startCondition by the values AnimationTrigger,Immediate and AnimationTrigger,Bundled, respectively. The default value, AnimationTrigger,Default, attempts to infer which is best. It will start immediately if there is no ongoing transaction or if Diagram.skipsUndoManager is true. AnimationTriggers are only definable in templates, on GraphObjects, and cannot be used on RowColumnDefinitions or Diagrams.
The Animation Class
New in 2.1 General animation of GraphObject and Diagram properties is possible by creating one or more instances of the Animation class.
var animation = new go.Animation(); // Animate the node's angle from its current value to a random value between 0 and 150 degrees animation.add(node, "angle", node.angle, Math.random() * 150); animation.duration = 1000; // Animate over 1 second, instead of the default 600 milliseconds animation.start(); // starts the animation immediatelyAnimation.add is used to specify which objects should animate, which properties, and their starting and ending values:
animation.add(GraphObjectOrDiagram, "EffectName", StartingValue, EndingValue);Here's the above animation in an example, where each node is animated by an HTML button. Note carefully that each node is added to the same animation. The same effect would be had with one animation per node, but it is always more efficient to group the properties you are animating into a single animation, if possible (for instance, it is possible if they are all going to start at the same time and have the same duration).
// define a simple Node template diagram.nodeTemplate = $(go.Node, "Spot", { locationSpot: go.Spot.Center }, new go.Binding("angle"), $(go.Shape, "Diamond", { strokeWidth: 0, width: 75, height: 75 }, new go.Binding("fill", "color")), $(go.TextBlock, { margin: 8, font: 'bold 12pt sans-serif' }, new go.Binding("text", "key")) );Animating the Diagram is possible by passing it as the object to be animated:diagram.model = new go.GraphLinksModel( [ { key: "Alpha", color: "lightblue" }, { key: "Beta", color: "orange" }, { key: "Gamma", group: 'G1', color: "lightgreen" }, { key: "Delta", group: 'G1', color: "pink", angle: 45 } ], [ { from: "Alpha", to: "Beta" }, { from: "Gamma", to: "Delta" } ]);
window.animate1 = function() { var animation = new go.Animation(); diagram.nodes.each(function(node) { // Animate the node's angle from its current value to a random value between 0 and 150 degrees animation.add(node, "angle", node.angle, Math.random() * 150); }); animation.duration = 1000; // Animate over 1 second, instead of the default 600 milliseconds animation.start(); // starts the animation immediately }
animation.add(myDiagram, "position", myDiagram.position, myDiagram.position.copy().offset(200, 15)); ... animation.add(myDiagram, "scale", myDiagram.scale, 0.2);
Animations can also be reversed, as is common with animations that are intended to be cosmetic in nature, by setting Animation.reversible to true. This doubles the effective duration of the Animation. Below are several example Animations, all with Animation.reversible set to true. The first animates Nodes, the other three animate Diagram position and scale.
// define a simple Node template diagram.nodeTemplate = $(go.Node, "Spot", { locationSpot: go.Spot.Center }, new go.Binding("angle"), $(go.Shape, "Diamond", { strokeWidth: 0, width: 75, height: 75 }, new go.Binding("fill", "color")), $(go.TextBlock, { margin: 8, font: 'bold 12pt sans-serif' }, new go.Binding("text", "key")) ); diagram.model = new go.GraphLinksModel( [ { key: "Alpha", color: "lightblue" }, { key: "Beta", color: "orange" }, { key: "Gamma", group: 'G1', color: "lightgreen" }, { key: "Delta", group: 'G1', color: "pink" } ], [ { from: "Alpha", to: "Beta" }, { from: "Gamma", to: "Delta" } ]); function protectedAnimation(f) { // return a button event handler to start an animation return function() { // Stop any currently running animations diagram.animationManager.stopAnimation(true); var animation = new go.Animation(); animation.reversible = true; // reverse the animation at the end, doubling its total time f(animation); // initialize the Animation animation.start(); // start the animation immediately }; } window.animateAngleReverse = protectedAnimation(function(animation) { diagram.nodes.each(function(node) { // Animate the node's angle from its current value a random value between 0 and 90 animation.add(node, "angle", node.angle, Math.random() * 90); }); }); window.animateDiagramPosition = protectedAnimation(function(animation) { // shift the diagram contents towards the right and then back animation.add(diagram, "position", diagram.position, diagram.position.copy().offset(200, 15)); animation.duration = 700; }); window.animateZoomOut = protectedAnimation(function(animation) { animation.add(diagram, "scale", diagram.scale, 0.2); }); window.animateZoomIn = protectedAnimation(function(animation) { animation.add(diagram, "scale", diagram.scale, 4); });
Without the call to AnimationManager.stopAnimation to protect against rapid button clicks, you would notice that if you clicked Zoom Out, and then during the animation clicked the same button again, the Diagram's scale would not return to its initial value of 1.0. This is because the Animation animates from the current Diagram scale value, to its final value, and back again, but the current value is also what's being changed due to the ongoing animation.
Custom Animation Effects
It is sometimes helpful to add custom ways to modify one or more properties during an animation. You can register new animatable effects with AnimationManager,defineAnimationEffect. The name passed is an arbitrary string, but often reflects a property of a GraphObject class. The body of the function passed determines what property or properties are animated.
Here is an example, creating an "fraction"
Animation effect to animate the value of GraphObject.segmentFraction, which will give the appearance of a Link label moving along its path.
// This presumes the object to be animated is a label within a Link go.AnimationManager.defineAnimationEffect('fraction', function(obj, startValue, endValue, easing, currentTime, duration, animation) { obj.segmentFraction = easing(currentTime, startValue, endValue - startValue, duration); });
After defining this, we can use it as a property name in an Animation. The following example sets up an indefinite (Animation.runCount = Infinity
) and reversible animation, where each link is assigned a random duration to cycle the fill color and segmentFraction of its label. This produces labels that appear to move along their path while pulsating colors. The setting of Animation.reversible causes them to go backwards once finished, to start from their beginning again.
function animateColorAndFraction() { // create one Animation for each link, so that they have independent durations myDiagram.links.each(function(node) { var animation = new go.Animation() animation.add(node.elt(1), "fill", node.elt(0).fill, go.Brush.randomColor()); animation.add(node.elt(1), "fraction", 0, 1); animation.duration = 1000 + (Math.random()*2000); animation.reversible = true; // Re-run backwards animation.runCount = Infinity; // Animate forever animation.start(); }); }
Since Animation.runCount was set to Infinity
, this Animation wil run indefinitely.
Animating Deletion
Parts to be deleted can be animated, but since they will no longer exist in the Diagram after removal, a copy must be added to the Animation so that there is an object to animate. This can be done with Animation.addTemporaryPart. The part can then have its deletion animated using Animation.add. This temporary part will be the object that animates, and will automatically appear when animation begins and be removed when animaton completes. It is typical for deletion animations to shrink the mock Part, move it off-screen, reduce its opacity to zero, or otherwise show it disappearing in some way.
In this example, each Part being deleted will be scaled to an imperceptible size (by animating scale to 0.01) and spun around (by animating angle), to give the appearance of swirling away. There are other example deletion (and creation) effects in the Custom Animations extension sample.
myDiagram.addDiagramListener('SelectionDeleting', function(e) { // the DiagramEvent.subject is the collection of Parts about to be deleted e.subject.each(function(part) { if (!(part instanceof go.Node)) return; // only animate Nodes var animation = new go.Animation(); var deletePart = part.copy(); animation.add(deletePart, "scale", deletePart.scale, 0.01); animation.add(deletePart, "angle", deletePart.angle, 360); animation.addTemporaryPart(deletePart, myDiagram); animation.start(); }); });
Animation Examples
To see more examples of custom animations, visit the Custom Animations extension sample. It demonstrates a number of Node creation/deletion animations, linking animations, and more. There are also several samples which contain animation:
- Tree Load Animation - New Sample: recursive animation upon model load.
- Flowchart - In the Palette only, initial animation is disabled in favor of a custom fade-in animation.
- State Chart - Initial animation is disabled in favor of a custom zoom fade-in animation.
- Data Visualization - Nodes now move using an AnimationTrigger.
- Kitten Monitor - Kittens now move using an AnimationTrigger.
- Process Flow - Custom animation defined to animate the Link's strokeDashArray.
- Shop Floor Monitor - Link color changes now use an AnimationTrigger.