1

I'm trying to trigger an event half-way through the progress (not time) of a transition. It sounds simple, but since the transition can have any curve it's quite tricky. In my particular case it's not going to be paused or anything so that consideration is out of the way.

(Simplified) essentially I could trigger an animation on a modifier like this:

function scaleModifierTo(stateModifier, scale, animationDuration) {
  stateModifier.setTransform(
    Transform.scale(scale, scale, scale),
    {
      duration: animationDuration,
      curve: this.options.curve   
    }
  );
}

When the interpolated state of the Transitionable hits 0.5 (half-way through) I want to trigger a function.

I haven't dug that deep behind in the source of famo.us yet, but maybe need to do something like

  • subclass something and add the possibility to listen when the state passes through a certain point?
  • reverse the curve defined and use a setTimeout (or try to find a proximity using a few iterations of the chosen curve algorithm (ew))

Is it possible to do this easily? What route should I go down?

Alex
  • 1,689
  • 18
  • 27
  • 1
    Hey, Alex, this is LeXXik from irc. So, basically, I had something similar to the second example of @johntraver. Perhaps another way to explore is making a custom transitionable using a physics engine? I haven't touched physics much yet, but I think there should be a more practical way to know the position of an element? – Alexander Jun 20 '14 at 04:53

2 Answers2

2

I can think of a couple of ways to achieve such, and both lend to the use of Modifier over StateModifier. If you are new, and haven't really had the chance to explore the differences, Modifier consumes state from the transformFrom method which takes a function that returns a transform. This is where we can use our own Transitionable to supply state over the lifetime of our modifier.

To achieve what you wish, I used a Modifier with a basic transformFrom that will alter the X position of the surface based on the value of the Transitionable. I can then monitor the transitionable to determine when it is closest, or in my case greater than or equal to half of the final value. The prerender function will be called and checked on every tick of the engine, and is unbinded when we hit the target.

Here is that example..

var Engine              = require('famous/core/Engine');
var Surface             = require('famous/core/Surface');
var Modifier            = require('famous/core/Modifier');
var Transform           = require('famous/core/Transform');

var Transitionable      = require('famous/transitions/Transitionable');
var SnapTransition      = require('famous/transitions/SnapTransition');

Transitionable.registerMethod('snap',SnapTransition);

var snap = { method:'snap', period:1000, damping:0.6};

var context = Engine.createContext();

var surface = new Surface({
    size:[200,200],
    properties:{
        backgroundColor:'green'
    }
});

surface.trans = new Transitionable(0);

surface.mod = new Modifier();

surface.mod.transformFrom(function(){
    return Transform.translate(surface.trans.get(),0,0);
});

context.add(surface.mod).add(surface);

function triggerTransform(newValue, transition) {

    var prerender = function(){

        var pos = surface.trans.get();

        if (pos >= (newValue / 2.0)) {

            // Do Something.. Emit event etc..
            console.log("Hello at position: "+pos);

            Engine.removeListener('prerender',prerender);
        }
    }

    Engine.on('prerender',prerender);

    surface.trans.halt();
    surface.trans.set(newValue,transition);
}

surface.on('click',function(){ triggerTransform(400, snap); });

The downside of this example is the fact that you are querying the transitionable twice. An alternative is to add your transitionable check right in the transformFrom method. This could get a bit strange, but essentially we are modifying our transformFrom method until we hit our target value, then we revert back to the original transformFrom method.. triggerTransform would be defined as follows..

Hope this helps!

function triggerTransform(newValue, transition) {
    surface.mod.transformFrom(function(){
        pos = surface.trans.get()
        if (pos >= newValue/2.0) {

            // Do something
            console.log("Hello from position: " + pos)

            surface.mod.transformFrom(function(){
                return Transform.translate(surface.trans.get(),0,0);
            });
        }
        return Transform.translate(pos,0,0)
    })

    surface.trans.set(newValue,transition);

}
johntraver
  • 3,612
  • 18
  • 17
0

Thank you for your responses, especially @johntraver for the prerender event, I wasn't aware of the existence of that event.

I realised it made more sense that I should handle this logic together with my move animation, not the scale one. Then, I ended up using a (very hacky) way of accessing the current state of the transition and by defining a threshold in px I can trigger my function when needed.

/**
 * Move view at index to a specified offset
 * @param  {Number} index
 * @param  {Number} xOffset   xOffset to move to
 * @param  {Object} animation Animation properties
 * @return void
 */
function moveView(index, xOffset, animation) {
  var rectModifier = this._views[index].modifiers.rect;
  var baseXOffset = rectModifier._transformState.translate.state[0];

  // After how long movement is reflow needed?
  // for the sake of this example I use half the distance of the animation
  var moveThreshold = Math.abs(baseXOffset - xOffset)/2;

  /**
   * Callback function triggered on each animation frame to see if the view is now covering
   * the opposite so we can trigger a reflow of the z index
   * @return void
   */
  var prerender = function() {
    var numPixelsMoved = Math.abs(baseXOffset - rectModifier._transformState.translate.state[0]);

    if (numPixelsMoved > moveThreshold) {
      Engine.removeListener('prerender', prerender);
      // trigger a method when this is reached
      _reflowZIndex.call(this);
    }
  }.bind(this);

  rectModifier.setTransform(
    Transform.translate(xOffset, 0, 0),
    animation,
    function() {
      Engine.removeListener('prerender', prerender);
    }
  );

  Engine.on('prerender', prerender);
}

Obviously the ._transformState.translate.state[0] is a complete hack, but I couldn't figure out of getting this value in a clean way without adding my own Transitionable, which I don't want. If there is a cleaner way of finding the current state as a Number between 0.0-1.0 that would be ace; anyone knows of one?

Alex
  • 1,689
  • 18
  • 27