Nesting tweens with GSAP don't inherit the defaults of the master timline - gsap

I want to nest 2 or more tweens in 1 master timeline instead of just sequencing them. If you sequence them you can add defaults to the timeline, like duration or ease and every tween in the sequence would inherit those defaults. If you nest the same tweens with the same defaults, these defaults seem to be ignored. I was wondering if this is indeed the way it should work. I like the idea of nesting but I also find defaults very useful in this case. In the nesting example I would have to add the duration and ease to each tween.
With sequencing: the defaults are inherited
let tl = gsap.timeline({
defaults: {
duration: 0.75,
ease: 'power2.inOut',
yoyoEase: 'sine.out',
},
repeat: -1,
yoyo: true,
});
tl
.fromTo(
'#Robot',
{
y: 2.5,
},
{
y: -5,
}
)
.to(
'#Shadow',
{
scale: 0.75,
},
'<'
);
With nesting: the defaults are ignored
function robotHover() {
var tl = gsap.timeline();
tl.fromTo(
'#Robot',
{
y: 2.5,
},
{
y: -5,
}
);
return tl;
}
function shadowPulse() {
var tl = gsap.timeline();
tl.to('#Shadow', {
scale: 0.75,
});
return tl;
}
let robotMaster = gsap.timeline({
defaults: {
duration: 0.75,
ease: 'power2.inOut',
yoyoEase: 'sine.out',
},
repeat: -1,
yoyo: true,
});
robotMaster.add(robotHover()).add(shadowPulse(), '<');

Related

jsPlumb + Panzoom infinite droppable canvas

I have created a codepen that uses jquery ui droppable(for drag/drop), jsPlumb (for flowcharting) and Panzoom (panning and zooming) to create a flowchart builder. You could drag the list items from the draggable container (1st column) to the flowchart (2nd column) and then connect the items using the dots to create a flowchart. The #flowchart is a Panzoom target with both pan and zoom enabled. This all works fine.
However, I would like to have the #flowchart div always span the whole area of the flowchart-wrapper i.e. the #flowchart should be an infinite canvas that supports panning, zooming and is a droppable container.
It should have the same effect as flowchart-builder-demo. The canvas there is infinite where you can drag and drop items (Questions, Actions, Outputs) from the right column.
Any pointers on how to achieve this (like the relevant events or multiple panzoom elements and/or css changes) would be greatly appreciated.
const BG_SRC_TGT = "#2C7BE5";
const HEX_SRC_ENDPOINT = BG_SRC_TGT;
const HEX_TGT_ENDPOINT = BG_SRC_TGT;
const HEX_ENDPOINT_HOVER = "#fd7e14";
const HEX_CONNECTOR = "#39afd1";
const HEX_CONNECTOR_HOVER = "#fd7e14";
const connectorPaintStyle = {
strokeWidth: 2,
stroke: HEX_CONNECTOR,
joinstyle: "round",
outlineStroke: "white",
outlineWidth: 1
},
connectorHoverStyle = {
strokeWidth: 3,
stroke: HEX_CONNECTOR_HOVER,
outlineWidth: 2,
outlineStroke: "white"
},
endpointHoverStyle = {
fill: HEX_ENDPOINT_HOVER,
stroke: HEX_ENDPOINT_HOVER
},
sourceEndpoint = {
endpoint: "Dot",
paintStyle: {
stroke: HEX_SRC_ENDPOINT,
fill: "transparent",
radius: 4,
strokeWidth: 3
},
isSource: true,
connector: ["Flowchart", { stub: [40, 60], gap: 8, cornerRadius: 5, alwaysRespectStubs: true }],
connectorStyle: connectorPaintStyle,
hoverPaintStyle: endpointHoverStyle,
connectorHoverStyle: connectorHoverStyle,
dragOptions: {},
overlays: [
["Label", {
location: [0.5, 1.5],
label: "Drag",
cssClass: "endpointSourceLabel",
visible: false
}]
]
},
targetEndpoint = {
endpoint: "Dot",
paintStyle: {
fill: HEX_TGT_ENDPOINT,
radius: 5
},
hoverPaintStyle: endpointHoverStyle,
maxConnections: -1,
dropOptions: { hoverClass: "hover", activeClass: "active" },
isTarget: true,
overlays: [
["Label", { location: [0.5, -0.5], label: "Drop", cssClass: "endpointTargetLabel", visible: false }]
]
};
const getUniqueId = () => Math.random().toString(36).substring(2, 8);
// Setup jquery ui draggable, droppable
$("li.list-group-item").draggable({
helper: "clone",
zIndex: 100,
scroll: false,
start: function (event, ui) {
var width = event.target.getBoundingClientRect().width;
$(ui.helper).css({
'width': Math.ceil(width)
});
}
});
$('#flowchart').droppable({
hoverClass: "drop-hover",
tolerance: "pointer",
drop: function (event, ui) {
var helper = $(ui.helper);
var fieldId = getUniqueId();
var offset = $(this).offset(),
x = event.pageX - offset.left,
y = event.pageY - offset.top;
helper.find('div.field').clone(false)
.animate({ 'min-height': '40px', width: '180px' })
.css({ position: 'absolute', left: x, top: y })
.attr('id', fieldId)
.appendTo($(this)).fadeIn('fast', function () {
var field = $("#" + fieldId);
jsPlumbInstance.draggable(field, {
containment: "parent",
scroll: true,
grid: [5, 5],
stop: function (event, ui) {
}
});
field.addClass('panzoom-exclude');
var bottomEndpoints = ["BottomCenter"];
var topEndPoints = ["TopCenter"];
addEndpoints(fieldId, bottomEndpoints, topEndPoints);
jsPlumbInstance.revalidate(fieldId);
});
}
});
const addEndpoints = (toId, sourceAnchors, targetAnchors) => {
for (var i = 0; i < sourceAnchors.length; i++) {
var sourceUUID = toId + sourceAnchors[i];
jsPlumbInstance.addEndpoint(toId, sourceEndpoint, { anchor: sourceAnchors[i], uuid: sourceUUID });
}
for (var j = 0; j < targetAnchors.length; j++) {
var targetUUID = toId + targetAnchors[j];
jsPlumbInstance.addEndpoint(toId, targetEndpoint, { anchor: targetAnchors[j], uuid: targetUUID });
}
$('.jtk-endpoint').addClass('panzoom-exclude');
}
// Setup jsPlumbInstance
var jsPlumbInstance = jsPlumb.getInstance({
DragOptions: { cursor: 'pointer', zIndex: 12000 },
ConnectionOverlays: [
["Arrow", { location: 1 }],
["Label", {
location: 0.1,
id: "label",
cssClass: "aLabel"
}]
],
Container: 'flowchart'
});
// Setup Panzoom
const elem = document.getElementById('flowchart');
const panzoom = Panzoom(elem, {
excludeClass: 'panzoom-exclude',
canvas: true
});
const parent = elem.parentElement;
parent.addEventListener('wheel', panzoom.zoomWithWheel);
I've just been working on the exact same issue and came across this as the only answer
Implementing pan and zoom in jsPlumb
The PanZoom used looks to be quite old - but the idea was the same, use the JQuery Draggable plugin for the movable elements, instead of the in-built JsPlumb one. This allows the elements to move out of bounds.
The below draggable function fixed it for me using the PanZoom library.
var that = this;
var currentScale = 1;
var element = $('.element');
element.draggable({
start: function (e) {
//we need current scale factor to adjust coordinates of dragging element
currentScale = that.panzoom.getScale();
$(this).css("cursor", "move");
that.panzoom.setOptions({ disablePan: true });
},
drag: function (e, ui) {
ui.position.left = ui.position.left / currentScale;
ui.position.top = ui.position.top / currentScale;
if ($(this).hasClass("jtk-connected")) {
that.jsPlumbInstance.repaintEverything();
}
},
stop: function (e, ui) {
var nodeId = $(this).attr('id');
that.jsPlumbInstance.repaintEverything();
$(this).css("cursor", "");
that.panzoom.setOptions({ disablePan: false });
}
});
I'm not sure if redrawing everything on drag is that efficient - so maybe just redraw both the connecting elements.

Get correct mouseover interaction in a ThreeJS VueJS app changing the window

I'm quite proud of what I've done: I have a menu which comprises 4 shapes. When you hover a shape, it's chaging color, growing and pushing the other shapes on top, while the rotation gets slower.
I read the ThreeJS docs and follow the advices of StackOverflow members.
I'm struggling with mouse interactions and window resizing: when I first open the browser, the mouseover doesn't seem to be called exactly when the mouse is over.
And when I resize the window, it's clearly messed up.
If anybody has a clue on what I'm doing wrong, thanks in advance :)
Here is my component:
<template>
<v-container>
<div #click="onClick" #mousemove="onMouseMove" id="menu3D" style="background-color: transparent; position: fixed; left: 20px; width:15%; height:100%;"></div>
<v-row class="text-center">
<v-col
class="mb-5"
cols="12"
>
<h2 class="headline font-weight-bold mb-3">
Accueil
</h2>
<v-row justify="center">
<p>
THIS IS ONLY A TEST
</p>
</v-row>
</v-col>
</v-row>
</v-container>
</template>
<script>
import * as Three from 'three'
export default {
name: 'Home',
mounted() {
this.init();
},
methods: {
init: function() {
this.createScene();
this.createCamera();
this.userData.formes.forEach(x=>this.createShape(x))
this.addSpotlight(16777215/*'#fdffab'*/);
this.addAmbientLight();
this.animate();
window.addEventListener('resize', this.onResize())
},
onResize: function() {
let container = document.getElementById('menu3D');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.camera.aspect = container.clientWidth / container.clientHeight;
this.camera.updateProjectionMatrix();
},
createScene: function() {
this.renderer = new Three.WebGLRenderer({
antialias: true,
alpha: true
});
let container = document.getElementById('menu3D');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setClearColor(0xffffff,0);
container.appendChild(this.renderer.domElement);
},
createCamera: function() {
//let container = document.getElementById('container');
this.camera = new Three.PerspectiveCamera(50, 1.686275 /*container.clientWidth/container.clientHeight*/, 0.01, 1000);
this.camera.position.set(0, 5, 20);
this.camera.zoom = 1;
},
createShape: function(shape) {
let material = new Three.MeshStandardMaterial({
"color": '#0000ff'/*16777215*/,
"roughness": 1,
"metalness": 0.5,
"emissive": 0,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true,
"stencilWrite": false,
"stencilWriteMask": 255,
"stencilFunc": 519,
"stencilRef": 0,
"stencilFuncMask": 255,
"stencilFail": 7680,
"stencilZFail": 7680,
"stencilZPass": 7680
})
switch (shape.nom) {
case "Box": {
this.geometry = new Three.BoxBufferGeometry(1.8,1.8,1.8)
break;
}
case "Sphere": {
this.geometry = new Three.SphereBufferGeometry(1,8,6,0,6.283185,0, 3.141593)
break;
}
case "Dodecahedron": {
this.geometry = new Three.DodecahedronBufferGeometry(1.2,0)
break;
}
case "Icosahedron": {
this.geometry = new Three.IcosahedronBufferGeometry(1.5,0)
break;
}
}
this.mesh = new Three.Mesh(this.geometry, material)
this.mesh.name = shape.nom
this.mesh.userData = shape.userData
this.mesh.receiveShadow = true
this.mesh.castShadow = true
this.mesh.position.set(0, shape.userData.position.y, 0)
this.scene.add(this.mesh)
},
addSpotlight: function(color) {
const light = new Three.SpotLight(color, 2, 1000)
light.position.set(0, 0, 30)
this.scene.add(light)
},
addAmbientLight: function() {
const light = new Three.AmbientLight('#fff', 0.5)
this.scene.add(light)
},
verifForme: function(e) {
let t = this
let elt = t.scene.getObjectByName(e);
t.intersects = t.raycaster.intersectObject(elt);
if (t.intersects.length !== 0) {
// if it's not in the array, we put it at the beginning
if (t.userData.souris.indexOf(e)<0) {
t.userData.souris.unshift(e);
console.log(t.userData.souris[0] + " survolé!");
}
if (t.userData.souris[0] == e) {
let obj = t.intersects[0].object;
obj.material.color.set('#'+elt.userData.couleurs[1]);
obj.scale.set(obj.scale.x<1.4?obj.scale.x+t.VITESSE_ZOOM:obj.scale.x,obj.scale.y<1.4?obj.scale.y+t.VITESSE_ZOOM:obj.scale.y,obj.scale.z<1.4?obj.scale.z+t.VITESSE_ZOOM:obj.scale.z);
obj.rotation.y += t.VITESSE_ROTATION/t.RALENTISSEMENT
t.replacer(obj,obj.userData.position.y+obj.userData.decalage)
}
else {
t.retrecir(e,elt);
}
}
else {
if (t.userData.souris.indexOf(e)>=0) {
t.userData.souris = t.userData.souris.filter(forme => forme != e);
}
t.retrecir(e,elt);
}
},
onClick: function ( event ) {
event.preventDefault();
if (this.userData.souris.length >0 ) { console.log(this.userData.souris[0] + " clicked!"); }
else {
console.log("click outside!")
}
},
onMouseMove: function(event){
let container = document.getElementById('menu3D');
this.mouse.x = ( event.clientX / container.clientWidth ) * 2 - 1;
this.mouse.y = - ( event.clientY / container.clientHeight ) * 2 + 1;
//console.log(JSON.stringify(this.mouse))
},
replacer: function(e,py) {
// next line to prevent shaking
if (Math.abs(e.position.y - py) < 0.05) { return true }
let rhesus = 10*this.VITESSE_ZOOM
if (this.userData.souris[0] != e.name) { rhesus *= 3 }
//console.log(e.name+': '+this.userData.souris[0]+' - '+rhesus)
if (e.position.y > py) { rhesus = -1 }
e.position.set(0,Math.trunc(10*e.position.y+rhesus)/10,0)
},
retrecir: function (n,e) {
// checking if the clicked element is on top
let dec = 0
let elt = this
if ((elt.userData.souris.length > 0) && (elt.userData.formes.map(x=>x.nom).indexOf(n)<elt.userData.formes.map(x=>x.nom).indexOf(elt.userData.souris[0]))) {
dec = Math.trunc(10*e.parent.getObjectByName(elt.userData.souris[0]).userData.decalage*2.1)/10;
}
e.material.color.set('#'+e.userData.couleurs[0]);
e.rotation.y += elt.VITESSE_ROTATION
e.scale.set(e.scale.x>1?e.scale.x-elt.VITESSE_ZOOM:e.scale.x,e.scale.y>1?e.scale.y-elt.VITESSE_ZOOM:e.scale.y,e.scale.z>1?e.scale.z-elt.VITESSE_ZOOM:e.scale.z);
let newY = e.userData.position.y+dec
if (e.position.y != newY) {
elt.replacer(e,newY)
}
},
animate: function() {
let elt = this
requestAnimationFrame(this.animate);
this.raycaster.setFromCamera(this.mouse, this.camera);
this.userData.formes.map(x=>x.nom).forEach(x=>elt.verifForme(x))
if (this.userData.souris.length >0 ) { document.body.style.cursor = "pointer"; }
else { document.body.style.cursor = "default"; }
this.camera.updateProjectionMatrix();
this.renderer.render(this.scene, this.camera);
}
},
data: () => ({
scene: new Three.Scene(),
camera: null,
renderer: Three.WebGLRenderer,
mesh: new Three.Mesh,
factor:0,
mouse : new Three.Vector2(1, 1),
raycaster : new Three.Raycaster(),
intersects : [],
VITESSE_ROTATION: 0.05,
VITESSE_ZOOM: 0.1,
RALENTISSEMENT: 3,
userData: {
"souris": [],
"formes": [
{
"nom": "Box",
"userData": {
"position": {
"x": 0,
"y": 7.8,
"z": 0
},
"couleurs": [
"aaaaaa",
"095256"
],
"decalage": 0.5
}
},
{
"nom": "Icosahedron",
"userData": {
"position": {
"x": 0,
"y": 5.5,
"z": 0
},
"couleurs": [
"aaaaaa",
"087F8C"
],
"decalage": 0.5
}
},
{
"nom": "Dodecahedron",
"userData": {
"position": {
"x": 0,
"y": 3.1,
"z": 0
},
"couleurs": [
"aaaaaa",
"5AAA95"
],
"decalage": 0.4
}
},
{
"nom": "Sphere",
"userData": {
"position": {
"x": 0,
"y": 1,
"z": 0
},
"couleurs": [
"aaaaaa",
"86A873"
],
"decalage": 0.2
}
}
]
}
}),
}
</script>
I investigated the problem using the code you provided and fixed it locally so I'm hoping it also works for you. The issues are the following:
A tiny hard to find typo in the attachment of the resize event: instead of window.addEventListener('resize', this.onResize()) you need to use window.addEventListener('resize', this.onResize); removing the () because you don't want to call the function at the time of the attachment, you want it called each time the event is triggered.
Due to the first issue, as the resize function wasn't getting called when you expected, I guess this is what led you to use a hard-coded value (1.686275) in the camera instantiation instead of the recommended formula container.clientWidth / container.clientHeight so you need to change that back to
createCamera: function () {
let container = document.getElementById('menu3D');
this.camera = new Three.PerspectiveCamera(50, container.clientWidth / container.clientHeight, 0.01, 1000);
...
Also as the 3D container div is not in the root level of the HTML body due to VueJS requirements, in onMouseMove() you need to consume the offset coordinates instead of the client ones as follows:
onMouseMove: function (event) {
let container = document.getElementById('menu3D');
this.mouse.x = (event.offsetX / container.clientWidth) * 2 - 1;
this.mouse.y = - (event.offsetY / container.clientHeight) * 2 + 1;
...
I'm afraid the problem originates from the creation of your camera:
this.camera = new Three.PerspectiveCamera(50, 1.686275 /*container.clientWidth/container.clientHeight*/, 0.01, 1000);
Why are you using the magical aspect number 1.686275 instead of the actual width/height ratio like you do on resize? This is giving you a different behavior before and after resizing.
This is my best guess at first glance, although I presume there are other instances of hard coded “magic numbers” in your app that need to be re-calculated based on the screen’s width and height. I couldn't possibly read through the 300 lines of code you posted. You should consider isolating the problem to create a minimal working example and add it to your question via a code snippet so we can see your code in action.

Vue removeListener within directive on destroy

I got an directive that i use for animating elements within the DOM, the directive looks like this:
Vue.directive('animate', {
bind(el, binding) {
let start = (binding.value)?binding.value.start:{y: 50, autoAlpha: 0};
TweenMax.set(el, start);
},
inserted: function(el, binding) {
let end = (binding.value)?binding.value.end:{y: 0, autoAlpha: 1};
end.ease = Quart.easeOut;
end.onComplete = () => {
el.removeAttribute("style");
el.classList.add("animation-done");
};
const f = function() {
console.log("scrolling");
if (InViewPort(el)) {
window.removeEventListener('scroll', f);
TweenMax.to(el, 1.5, end);
}
};
window.addEventListener('scroll', f);
f();
}
});
The directive is called within the component like this:
v-animate="{start: {y: 50, autoAlpha: 0}, end: {y: 0, autoAlpha: 1, delay: index * 0.5}}"
This gives me the flexibility i would like to have, only one problem. The event listener is within the inserted scope, and not available on the unbind hook.
Maybe directive isn't the way to go in Vue, does anybody have some advice how to handle this?

GSAP pause animation in a function

I have a set of buttons that when clicked show a sort of pop up and some simple animations. Each pop up contain the same animations for most of the content. Each pop up also has its own sets of animations so I have done the following to get this to work correctly.
$gridTitles.click(function() {
const tl = new TimelineMax();
const $pop = $(this).next('.grid__pop');
const $chars = $pop.find(".grid__pop-title span");
const $items = $pop.find(".grid__pop-list li");
const func = $(this).data("graphic-function");
tl.set($pop, {
autoAlpha: 0,
display: 'block',
scale: .5
})
.to($pop, 1, {
autoAlpha: 1,
scale: 1,
ease: Power2.easeInOut
})
.staggerFrom($chars, 0.01, {
autoAlpha: 0,
ease: Power2.easeIn
}, 0.1)
.add(graphicAnimation[func])
.staggerFrom($items, 0.8, {
autoAlpha: 0,
rotationX: 90,
ease: Power2.easeOut
}, .8);
return tl;
});
This runs my pop up code and also using the .add function I call another function that runs the specific animation for the pop up based on a data attribute matching the name of a function in an object.
const graphicAnimation = {
graphicServer: function() {
const tl = new TimelineMax();
const $server = $(".graphic-server__server");
const $one = $(".graphic-server__one");
const $two = $(".graphic-server__two");
const $three = $(".graphic-server__three");
return tl.to($server, 1, {
autoAlpha: 1,
xPercent: "0",
ease: Power2.easeInOut
})
.to($one, 1, {
autoAlpha: 1,
xPercent: "0",
ease: Power2.easeInOut
})
.to($two, 1, {
autoAlpha: 1,
xPercent: "0",
ease: Power2.easeInOut
})
.to($three, 1, {
autoAlpha: 1,
xPercent: "0",
ease: Power2.easeInOut
})
},
// more functions
}
This works great depending on which button is clicked the correct function is called in the object and my animations run. The problem now is that some of these animations are looping and when I close the popup I can't pause them.
Using something like the following I tried to accomplish this
$gridCloses.click(function() {
const tl = new TimelineMax();
const $pop = $(this).parents(".grid__pop");
const func = $(this).parents('.grid__pop').siblings('.grid__title').data("graphic-function");
graphicAnimation[func].pause();
return tl.to($pop, 1, {
autoAlpha: 0,
scale: .5,
display: 'none'
});
});
But calling graphicAnimation[func].pause(); isn't going to work as pause() is a function on the returned timeline from that function. How can I access the current running function and pause / kill it.
So I thought this out and just need to store the timelines in their own object as well
const graphicTimeLines = {
graphicServer : new TimelineMax(),
graphicType: new TimelineMax()
}
so with that in my closing action I can do something like the following to completely reset my loops and have the timeline be available to be called again.
graphicTimeLines[func].pause().progress(0).kill();
graphicTimeLines[func] = new TimelineMax();

Animating dimensions in Sencha Touch 2

I'm trying to animate the height of a dataview, but it's currently just sliding the panel around the viewport instead of keeping it in place and changing it's height. The code is as follows:
Ext.Anim.run(el, 'slide', {
from: { height: height },
to: { height: newHeight },
out: false,
direction: 'up',
easing: 'ease-out',
duration: 1000
});
For instance, height=200, newHeight=100 will result in the dataview dropping immediately so that it's top is at 200px below the viewport, and then animating back to the top of the viewport.
How can I get it to change the height? Thanks.
Try using Ext.Animator.run instead:
Ext.Animator.run({
element: dataview.element,
duration: 500,
easing: 'ease-in',
preserveEndState: true,
from: {
height: dataview.element.getHeight()
},
to: {
height: 100
}
});
And within a full example:
Ext.application({
name: 'Sencha',
launch: function() {
var dataview = Ext.create('Ext.DataView', {
fullscreen: true,
style: 'background:red',
store: {
fields: ['text'],
data: [
{ text: 'one' },
{ text: 'two' },
{ text: 'three' }
]
},
itemTpl: '{text}'
});
Ext.Viewport.add({
xtype: 'button',
docked: 'top',
handler: function() {
Ext.Animator.run({
element: dataview.element,
duration: 500,
easing: 'ease-in',
preserveEndState: true,
to: {
height: 100
},
from: {
height: dataview.element.getHeight()
}
});
}
});
}
});
Since I can't add comments, I'll have to put this as a separate answer. I just wanted to add to what rdougan said and show how you can catch the animation end event. I find it's necessary in the above situation because Sencha Touch's component.getTop/Left/Height/Width() functions return incorrect values after an animation such as the one shown.
dataview.setHeight(dataview.element.getHeight()); // you may or may not need this
console.log('height before\t', dataview.getHeight());
var a = new Ext.fx.Animation({
element: dataview.element,
duration: 500,
easing: 'ease-in',
preserveEndState: true,
from: {
height: dataview.element.getHeight()
},
to: {
height: 100
}
});
a.on('animationend', function (animation, element, isInterrupted) {
console.log('height before\t', dataview.getHeight());
dataview.setHeight(dataview.element.getHeight());
console.log('height set\t', dataview.getHeight());
});
Ext.Animator.run(a);
I left in some logging so you can see just what I mean. This example was written against ST 2.1 RC2.
Here's a clean utility function you can use to accomplish this
function animatron (target, prop, duration, to, from, easing) {
// return if no target or prop
if (target == null || prop == null) { return; }
// defaults
if (duration == null) { duration = 250; }
if (to == null) { to = 0; }
if (from == null) { from = target.getHeight(); }
if (easing == null) { easing = 'ease-out'; }
// to property
var t = {};
t[prop] = to;
// from property
var f = {};
f[prop] = from;
// Animation Options
var opts = {
duration: duration,
easing: easing,
element: target.element,
from: f,
preserveEndState: true,
to: t
};
// Animation Object
var anime = new Ext.fx.Animation(opts);
// On animationend Event
anime.on('animationend', function (animation, element, isInterrupted) {
// Hide the target if the to is 0
if (to == 0 && (prop == 'height' || prop == 'width')) {
if (!isInterrupted) { target.hide(); }
}
// Update property if width or height
if (prop == 'height') { target.setHeight(to); }
if (prop == 'width') { target.setWidth(to); }
// Dispatch 'animated' event to target
target.fireEvent('animated', animation, element, to, from, isInterrupted);
});
// Show the target if it's hidden and to isn't 0
if (target.getHidden() == true && to != 0) { target.show(); }
// Run the animation
Ext.Animator.run(anime);
}
You can listen for the 'animated' event on the target element
animatron(dataview, 'height', 500, 0);
dataview.addListener('animated', function (animation, element, to, from, isInterrupted) {
console.log('animation ended');
console.log('interrupted: '+ isInterrupted);
});