create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
571 lines (515 loc) • 24.2 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
<meta name="description" content="Custom animation examples for GoJS." />
<meta itemprop="description" content="Custom animation examples for GoJS." />
<meta property="og:description" content="Custom animation examples for GoJS." />
<meta name="twitter:description" content="Custom animation examples for GoJS." />
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="../assets/css/style.css">
<!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
<meta itemprop="name" content="Custom Node Animations Demonstration" />
<meta property="og:title" content="Custom Node Animations Demonstration" />
<meta name="twitter:title" content="Custom Node Animations Demonstration" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/customanimations.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/customanimations.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/customanimations.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/customAnimations.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/customAnimations.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Custom Node Animations Demonstration | GoJS Diagramming Library
</title>
</head>
<body>
<!-- This top nav is not part of the sample code -->
<nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200">
<div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4">
<a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo"
href="../">
GoJS
</a>
<div class="relative">
<button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div id="topnavList" class="hidden md:block">
<div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold
md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit">
<a href="../learn/">Learn</a>
<a href="../samples/">Samples</a>
<a href="../intro/">Intro</a>
<a href="../api/">API</a>
<a href="../download.html">Download</a>
<a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a>
<a id="tc" href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
<a id="tb" href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</div>
</div>
</div>
</div>
</nav>
<script>
window.addEventListener("DOMContentLoaded", function () {
// topnav
var topButton = document.getElementById("topnavButton");
var topnavList = document.getElementById("topnavList");
if (topButton && topnavList) {
topButton.addEventListener("click", function (e) {
topnavList
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) {
topButton.click();
}
});
// set active <a> element
var url = window
.location
.href
.toLowerCase();
var aTags = topnavList.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.endsWith('.html'))
lowerhref = lowerhref.slice(0, -5);
if (url.startsWith(lowerhref)) {
aTags[i]
.classList
.add('active');
break;
}
}
}
});
</script>
<div class="flex flex-col prose">
<div class="w-full max-w-screen-xl mx-auto">
<!-- * * * * * * * * * * * * * -->
<!-- Start of GoJS sample code -->
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<div id="allSampleContent" class="p-4 w-full">
<style>
.flex-container {
display: flex;
flex-wrap: nowrap;
flex-direction: column;
}
.flex-container > div {
margin-bottom: 10px;
}
</style>
<script id="code">
function init() {
myDiagram = new go.Diagram('myDiagramDiv', {
'clickCreatingTool.archetypeNodeData': {
color: 'palegreen',
text: 'node'
},
'undoManager.isEnabled': true,
'animationManager.isInitial': false, // To use custom initial animation instead
InitialLayoutCompleted: animateFadeIn // animate with this function
});
function animateFadeIn(e) {
var diagram = e.diagram;
var animation = new go.Animation();
animation.isViewportUnconstrained = true;
animation.easing = go.Animation.EaseOutExpo; // Looks better for a fade in animation
animation.duration = 900;
animation.add(diagram, 'position', diagram.position.copy().offset(0, diagram instanceof go.Palette ? 200 : -200), diagram.position);
animation.add(diagram, 'opacity', 0, 1);
animation.start();
}
var addNodeAdornment =
new go.Adornment('Spot')
.add(
new go.Panel('Auto')
.add(
new go.Shape({ fill: null, stroke: 'dodgerblue', strokeWidth: 3 }),
new go.Placeholder()
),
// the button to create a "next" node, at the top-right corner
go.GraphObject.build('Button', {
alignment: go.Spot.TopRight,
click: addNode // this function is defined below
})
.add(
new go.Shape('PlusLine', { desiredSize: new go.Size(6, 6) })
)
);
myDiagram.nodeTemplate =
new go.Node('Auto', {
selectionAdornmentTemplate: addNodeAdornment,
locationSpot: go.Spot.Center
})
.bindTwoWay('location', 'loc')
.add(
new go.Shape('RoundedRectangle', {
strokeWidth: 2,
portId: '', // this Shape is the Node's port, not the whole Node
fromLinkable: true,
fromLinkableSelfNode: true,
fromLinkableDuplicates: true,
toLinkable: true,
toLinkableSelfNode: true,
toLinkableDuplicates: true,
cursor: 'pointer'
})
.bind('fill', 'color'),
new go.TextBlock({ margin: 10, font: '14px sans-serif' })
.bind('text')
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, text: 'Alpha', loc: new go.Point(0, 0), color: 'lightblue' },
{ key: 2, text: 'Beta', loc: new go.Point(150, 0), color: 'orange' },
{ key: 3, text: 'Gamma', loc: new go.Point(0, 150), color: 'lightgreen' },
{ key: 4, text: 'Delta', loc: new go.Point(150, 150), color: 'pink' }
],
[
// No links to start
]
);
// This animation can be used in LinkDrawn Diagram listeners to animate
// from a straight temporary link to a Bezier finished link
// Custom animation for the curviness of a bezier link
go.AnimationManager.defineAnimationEffect('curviness', (obj, startValue, endValue, easing, currentTime, duration, animationState) => {
if (isNaN(startValue)) startValue = 0;
if (isNaN(endValue)) endValue = 20;
obj.curviness = easing(currentTime, startValue, endValue - startValue, duration);
});
// This animation can be used in LinkDrawn Diagram listeners to animate
// from a straight temporary link to an Orthogonal finished link
go.AnimationManager.defineAnimationEffect('orthogonalLinkanim', (link, initPoints, tempPoints, easing, currentTime, duration, animation) => {
var animationState = animation.getTemporaryState(link);
if (animationState.initial === undefined) {
// On the first animaiton tick, save the initial points
animationState.initial = true;
var pts = link.points.copy();
tempPoints.points = pts;
animationState.startPt = pts.first();
animationState.toPt1 = pts.elt(2);
animationState.toPt2 = pts.elt(3);
animationState.endPt = pts.last();
}
var newpt1 = new go.Point(
easing(currentTime, animationState.startPt.x, animationState.toPt1.x - animationState.startPt.x, duration),
easing(currentTime, animationState.startPt.y, animationState.toPt1.y - animationState.startPt.y, duration)
);
var newpt2 = new go.Point(
easing(currentTime, animationState.endPt.x, -(animationState.endPt.x - animationState.toPt2.x), duration),
easing(currentTime, animationState.endPt.y, -(animationState.endPt.y - animationState.toPt2.y), duration)
);
// Setting the array of points will automatically call makeGeometry which will redraw the segments of the line
link.points = [animationState.startPt, tempPoints.points.elt(1), newpt1, newpt2, tempPoints.points.elt(4), animationState.endPt];
});
go.AnimationManager.defineAnimationEffect('corner', (obj, startValue, endValue, easing, currentTime, duration, animation) => {
// If no corner set, default to 0 -> 20
startValue = startValue || 0;
endValue = endValue || 20;
obj.corner = easing(currentTime, startValue, endValue - startValue, duration);
});
myDiagram.addDiagramListener('LinkDrawn', e => {
var link = e.subject;
var animation = new go.Animation();
var linkChoice = document.getElementById('links').value;
if (linkChoice == 'bezier') {
link.curve = go.Curve.Bezier;
animation.easing = elasticEase;
animation.add(link, 'curviness', 0, link.curviness);
animation.duration = 500;
} else if (linkChoice == 'orthogonal') {
// The orthogonal animation is two animations, chained together. One to modify the points,
// and then another to modify the link.corner value.
// Store the initial link.corner value,
// then set it to 0 so that in between animations it does not revert back to the original state
var initCorner = link.corner;
link.corner = 0;
// Store the original points to this object
var tempPoints = {};
animation.add(link, 'orthogonalLinkanim', link.points, tempPoints);
animation.duration = 300;
// Chain animation after first one is completed
animation.finished = () => {
// Set points back to what they were before the animation
myDiagram.startTransaction('Fix Points');
link.points = tempPoints.points;
myDiagram.commitTransaction('Fix Points');
// Need to make a new animation object
var animation2 = new go.Animation();
animation2.add(link, 'corner', 0, initCorner);
animation2.duration = 250;
animation2.start();
};
animation.start();
link.routing = go.Routing.Orthogonal;
// NYI ortho animation
link.ensureBounds();
}
animation.start();
});
go.AnimationManager.defineAnimationEffect('bounceDelete', (obj, startValue, endValue, easing, currentTime, duration, animation) => {
var animationState = animation.getTemporaryState(obj);
if (animationState.initial === undefined) {
// Set the initial positions as part of the animationState data
animationState.yPos = obj.location.y;
animationState.xPos = obj.location.x;
animationState.yVelo = 0;
animationState.xVelo = 0;
animationState.newTime = 0;
animationState.oldTime = 0;
animationState.initial = true;
}
obj.location = getPointBounceDelete(currentTime, obj, animationState, obj.diagram);
});
// Get the point the object should be at based upon the time and original point
function getPointBounceDelete(currentTime, obj, animationState, diagram) {
if (diagram === null) return new go.Point(animationState.xPos, animationState.yPos);
let height = obj.actualBounds.height;
animationState.newTime = currentTime;
// Animation uses a change in time in order to be more consistant
let delTime = (animationState.newTime - animationState.oldTime) / 3;
animationState.yVelo += 0.05 * delTime;
// Add a slight easing to the x movement at the beginning of the animation
if (currentTime < 200) {
animationState.xVelo = currentTime / 300;
}
// Adjust positions based on the velocities and the change in time
animationState.yPos += animationState.yVelo * delTime;
animationState.xPos += animationState.xVelo * delTime;
// Check to see if the Y position will be less than the bottom of the diagram, if so,
// change the direction and scale down the velocity and set the position to the bottom of the diagram
if (animationState.yPos > diagram.viewportBounds.height / 2 - height) {
animationState.yVelo = -0.75 * animationState.yVelo;
animationState.yPos = diagram.viewportBounds.height / 2 - height;
}
let myPoint = new go.Point(animationState.xPos, animationState.yPos);
// Get the new old time for use in the next iteration
animationState.oldTime = animationState.newTime;
return myPoint;
}
myDiagram.addDiagramListener('SelectionDeleting', e => {
var deletionSelection = document.getElementById('deletion');
var animation = new go.Animation();
var diagram = e.diagram;
e.subject.each(p => {
// Because we are deleting this part, we cannot animate it, instead we must animate a temporary copy
// The animation handles this via addTemporaryPart, which must be passed a copy
var part = p.copy();
animation.addTemporaryPart(part, diagram);
var initPosition = part.position.copy();
part.locationSpot = go.Spot.Center;
part.position = initPosition;
switch (deletionSelection.value) {
case 'spinOut':
animation.add(part, 'angle', part.angle, part.angle + 1000);
animation.add(part, 'scale', part.scale, 0.01);
break;
case 'fadeOut':
animation.add(part, 'opacity', part.opacity, 0);
break;
case 'zoomOut':
animation.add(part, 'scale', part.scale, 0.01);
break;
case 'floatOut':
animation.add(part, 'opacity', part.opacity, 0);
animation.add(part, 'position', part.position, part.position.copy().add(new go.Point(0, -80)));
break;
case 'bounceOut':
animation.add(part, 'bounceDelete', part.location); // does't need an end value, bounceDelete determines one
animation.add(part, 'scale', part.scale, 0.01);
animation.duration = 1500;
animation.isViewportUnconstrained = true;
break;
default:
// nothing animates
break;
}
});
animation.start();
});
myDiagram.addDiagramListener('ClipboardPasted', e => {
var creationSelection = document.getElementById('creation');
// For best performance, be sure to use only one Animation for the entire selection
// instead of creating one animation for each object in the selection
var animation = new go.Animation();
e.subject.each(part => {
addCreatedPart(part, animation, creationSelection.value);
});
animation.start();
});
myDiagram.addDiagramListener('PartCreated', e => {
// From ClickCreatingTool
var creationSelection = document.getElementById('creation');
var animation = new go.Animation();
addCreatedPart(e.subject, animation, creationSelection.value);
animation.start();
});
function addCreatedPart(part, animation, creationSelection) {
switch (creationSelection) {
case 'spinIn':
animation.add(part, 'angle', part.angle + 1000, part.angle);
animation.add(part, 'scale', 0.01, part.scale);
break;
case 'fadeIn':
animation.add(part, 'opacity', 0, part.opacity);
break;
case 'zoomIn':
animation.add(part, 'scale', 0.01, part.scale);
break;
case 'floatIn':
animation.add(part, 'opacity', 0, part.opacity);
animation.add(part, 'location', part.location.copy().add(new go.Point(0, -80)), part.location);
break;
default:
// nothing animates
break;
}
}
document.getElementById('addNode').addEventListener('click', e => addNode());
function addNode() {
var diagram = myDiagram;
var tempNodes = new go.List();
diagram.startTransaction('Add States');
diagram.nodes.each(node => {
if (node.isSelected) {
tempNodes.push(node);
}
});
var animation = new go.Animation();
// Set the easing to a custom easing function
animation.easing = elasticEase;
// Add a new node to the right of each node
tempNodes.each(part => {
// get the node data for which the user clicked the button
var fromNode = part;
var fromData = part.data;
// create a new "State" data object, positioned off to the right of the adorned Node
var toData = { key: 'new' };
toData.color = 'purple';
var p = fromNode.location.copy();
// Place the new node randomly somewhere along a circular 200px radius
var angle = Math.random() * Math.PI * 2;
p.x += Math.cos(angle) * 200;
p.y += Math.sin(angle) * 200;
toData.loc = p;
// add the new node data to the model
var model = diagram.model;
model.addNodeData(toData);
// create a link data from the old node data to the new node data
var linkdata = {
from: model.getKeyForNodeData(fromData), // or just: fromData.key
to: model.getKeyForNodeData(toData)
};
// and add the link data to the model
model.addLinkData(linkdata);
var newnode = diagram.findNodeForData(toData);
// Animate each newly created node
animation.add(newnode, 'position', part.location, newnode.location);
});
animation.start();
diagram.commitTransaction('Add States');
}
document.getElementById('reloadModel').addEventListener('click', e => {
myDiagram.model = go.Model.fromJson(myDiagram.model.toJson());
});
// Custom easing function used in some animations
function elasticEase(currentTime, startValue, byValue, duration) {
var ts = (currentTime /= duration) * currentTime;
var tc = ts * currentTime;
return startValue + byValue * (56 * tc * ts + -175 * ts * ts + 200 * tc + -100 * ts + 20 * currentTime);
}
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<div id="myDiagramDiv" style="border: solid 1px black; width: 700px; height: 600px"></div>
<div class="flex-container" style="width: 700px">
<p style="margin-bottom: 0">
This extension implements several custom animations within GoJS. It may be useful to copy some of them into your own project.
</p>
<ul>
<li>Double click in the Diagram background to create a node, or copy and paste nodes, to view creation animations.</li>
<li>Delete a node to view deletion animations.</li>
<li>Draw links to see new link creation animations.</li>
<li>Select a node and press the + button or the button below to see a link-and-node creation animation.</li>
<li>Reload the model using the button below to see the custom load animation</li>
</ul>
</div>
<div class="flex-container">
<p><strong>Options:</strong></p>
<div>
Creation Animation
<select id="creation">
<option value="spinIn">Spin In</option>
<option value="fadeIn">Fade In</option>
<option value="floatIn">Float In</option>
<option value="zoomIn">Zoom In</option>
<option>--None--</option>
</select>
</div>
<div>
Deletion Animation
<select id="deletion">
<option value="spinOut">Spin Out</option>
<option value="fadeOut">Fade Out</option>
<option value="floatOut">Float Out</option>
<option value="zoomOut">Zoom Out</option>
<option value="bounceOut">Bounce Out</option>
<option>--None--</option>
</select>
</div>
<div>
Drawn Link Style
<select id="links">
<option value="bezier">Bezier Curve</option>
<option value="orthogonal">Orthogonal Curve</option>
<option>Linear (no animation)</option>
</select>
</div>
</div>
<button id="addNode">Add Node + Link from selected Node</button>
<button id="reloadModel">Reload model</button>
</div>
</div>
<!-- * * * * * * * * * * * * * -->
<!-- End of GoJS sample code -->
</div>
<div id="allTagDescriptions" class="p-4 w-full max-w-screen-xl mx-auto">
<hr/>
<h3 class="text-xl">GoJS Features in this sample</h3>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Animation</h4>
<p>
<b>GoJS</b> offers several built-in animations, enabled by default, as well as the ability to create arbitrary animations.
</p>
<p>
The <a href="../api/symbols/Diagram.html#animationManager" target="api">Diagram.animationManager</a> handles animations within a <a href="../api/symbols/Diagram.html" target="api">Diagram</a>.
The <a href="../api/symbols/AnimationManager.html" target="api">AnimationManager</a> automatically sets up and dispatches default animations, and has properties to customize and disable them.
Custom animations are possible by creating instances of <a href="../api/symbols/Animation.html" target="api">Animation</a> or <a href="../api/symbols/AnimationTrigger.html" target="api">AnimationTrigger</a>.
More information can be found in the <a href="../intro/animation.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#animation">Related samples</a>
</p>
<hr>
</div>
</div>
</body>
<!-- This script is part of the gojs.net website, and is not needed to run the sample -->
<script src="../assets/js/goSamples.js"></script>
</html>