自定义动画
概述
实现动画
对于圆形节点(Circle)元素,其主图形是一个圆形,现在为其编写一个动画,当节点的尺寸发生变化时,能够以缩放的方式进行过渡动画:
[
{
fields: ['r'],
shape: 'key',
},
];
下面我们创建一个图实例并更新元素尺寸来触发更新动画:
const graph = new Graph({
container: 'container',
width: 50,
height: 50,
data: {
nodes: [{ id: 'node-1', style: { x: 25, y: 25, size: 20 } }],
},
node: {
animation: {
update: [{ fields: ['r'], shape: 'key' }],
},
},
});
graph.draw().then(() => {
graph.updateNodeData([{ id: 'node-1', style: { size: 40 } }]);
graph.draw();
});
⬇️ 指针移动至下方图中,并点击左侧播放按钮进行重新播放
import { Graph } from '@antv/g6';
const graph = new Graph({
width: 50,
height: 50,
container,
data: {
nodes: [{ id: 'node-1', style: { x: 25, y: 25, size: 20 } }],
},
node: {
animation: {
update: [
{
fields: ['r'],
shape: 'key',
},
],
},
},
});
graph.draw().then(() => {
graph.updateNodeData([{ id: 'node-1', style: { size: 40 } }]);
graph.draw();
});
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';
export function addPanel(renderPanel: (gui: GUI) => void) {
const gui = new GUI({ container: document.body });
gui.title('Control');
Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
renderPanel(gui);
addEventListener('pagehide', () => gui.destroy(), { once: true });
}
export async function createGraph(
options: ConstructorParameters<typeof Graph>[0],
size: { width?: number; height?: number } = {},
renderPanel?: (gui: GUI, graph: Graph) => void,
) {
const container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
maxWidth: `${size.width || 600}px`,
height: `${size.height || 400}px`,
});
document.getElementById('container')!.append(container);
const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
addEventListener('pagehide', () => graph.destroy(), { once: true });
await graph.render();
if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
return container;
}原理分析
当对一个元素执行动画时,该元素会将其动画帧参数转化为其各个子图形上的动画帧参数,并执行对应的动画。
在上面的例子中,通过更新节点尺寸(size),对该节点执行了动画,其动画帧参数为:
[{ "size": 20 }, { "size": 40 }]
节点元素拿到该属性后,将其转化为主图形(圆形)的动画帧参数:
[{ "r": 10 }, { "r": 20 }]
因此这里最终是对圆形执行了半径从 10 到 20 的过渡动画。
复合动画
直接将位置变化动画和尺寸变化动画合并到一个动画范式即可得到复合动画范式:
[
{
fields: ['x', 'y'],
},
{
fields: ['r'],
shape: 'key',
},
];
并同时更新该节点的位置和尺寸:
graph.updateNodeData([{ id: 'node-1', style: { x: 175, size: 40 } }]);
graph.draw();
⬇️ 指针移动至下方图中,并点击左侧播放按钮进行重新播放
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
width: 200,
height: 50,
data: {
nodes: [{ id: 'node-1', style: { x: 25, y: 25, size: 20 } }],
},
node: {
animation: {
update: [
{
fields: ['x', 'y'],
},
{ fields: ['r'], shape: 'key' },
],
},
},
});
graph.draw().then(() => {
graph.updateNodeData([{ id: 'node-1', style: { x: 175, size: 40 } }]);
graph.draw();
});
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';
export function addPanel(renderPanel: (gui: GUI) => void) {
const gui = new GUI({ container: document.body });
gui.title('Control');
Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
renderPanel(gui);
addEventListener('pagehide', () => gui.destroy(), { once: true });
}
export async function createGraph(
options: ConstructorParameters<typeof Graph>[0],
size: { width?: number; height?: number } = {},
renderPanel?: (gui: GUI, graph: Graph) => void,
) {
const container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
maxWidth: `${size.width || 600}px`,
height: `${size.height || 400}px`,
});
document.getElementById('container')!.append(container);
const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
addEventListener('pagehide', () => graph.destroy(), { once: true });
await graph.render();
if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
return container;
}加入颜色过渡:
[
{
fields: ['x', 'y'],
},
{
fields: ['r', 'fill'],
shape: 'key',
},
];
执行节点更新:
graph.updateNodeData([{ id: 'node-1', style: { x: 175, size: 40, fill: 'pink' } }]);
graph.draw();
⬇️ 指针移动至下方图中,并点击左侧播放按钮进行重新播放
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
width: 200,
height: 50,
data: {
nodes: [{ id: 'node-1', style: { x: 25, y: 25, size: 20 } }],
},
node: {
animation: {
update: [
{
fields: ['x', 'y'],
},
{ fields: ['r', 'fill'], shape: 'key' },
],
},
},
});
graph.draw().then(() => {
graph.updateNodeData([{ id: 'node-1', style: { x: 175, size: 40, fill: 'pink' } }]);
graph.draw();
});
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';
export function addPanel(renderPanel: (gui: GUI) => void) {
const gui = new GUI({ container: document.body });
gui.title('Control');
Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
renderPanel(gui);
addEventListener('pagehide', () => gui.destroy(), { once: true });
}
export async function createGraph(
options: ConstructorParameters<typeof Graph>[0],
size: { width?: number; height?: number } = {},
renderPanel?: (gui: GUI, graph: Graph) => void,
) {
const container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
maxWidth: `${size.width || 600}px`,
height: `${size.height || 400}px`,
});
document.getElementById('container')!.append(container);
const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
addEventListener('pagehide', () => graph.destroy(), { once: true });
await graph.render();
if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
return container;
}