Code Monkey home page Code Monkey logo

shifty's Introduction

Shifty - The fastest TypeScript animation engine on the web

Current Shifty version

  • main: CI

Shifty is a tweening engine for TypeScript. It is a lightweight library meant to be encapsulated by higher level tools. At its core, Shifty provides:

  • Best-in-class animation performance
  • Playback control of an individual tween
  • Extensibility hooks for key points in the tween lifecycle
  • Promise support for async/await programming

This is useful because it is the least amount of functionality needed to build customizable animations. Shifty is optimized to run with the minimal processing and memory overhead.

import { tween } from 'shifty'
;(async () => {
  const element = document.querySelector('#tweenable')

  const { tweenable } = await tween({
    render: ({ scale, x }) => {
      element.style.transform = `translateX(${x}px) scale(${scale})`
    },
    easing: 'easeInOutQuad',
    duration: 500,
    from: { scale: 1, x: 0 },
    to: { x: 200 },
  })

  await tweenable.tween({
    to: { x: 0 },
  })

  await tweenable.tween({
    to: { scale: 3 },
  })
})()

Installation

npm install --save shifty

Environment compatibility

Shifty officially supports Evergreen browsers, Safari, and Node 10 and above. Internet Explorer is supported by v2 If you encounter a browser-specific bug, please open an issue about it!

Support this project!

Shifty is a labor of love that will always be free and open source. If you've gotten value out of Shifty, please consider supporting the developer with a small donation!

Developing Shifty

First, install the development dependencies via NPM:

npm ci

Once those are installed, you can generate dist/shifty.js with:

npm run build

To run the tests:

npm test

To generate the documentation (in dist/doc):

npm run doc

Loading Shifty

Shifty exposes a UMD module, so you can load it however you like:

// ES6
import { tween } from 'shifty'

Or:

// AMD
define(['shifty'], function(shifty) {
  shifty.tween({ from: { x: 0 }, to: { x: 10 } })
})

Or even:

// CommonJS
const { tween } = require('shifty')

tween({ from: { x: 0 }, to: { x: 10 } })

Using Shifty

See the Getting Started guide and check out the API documentation.

Troubleshooting

Jest

With later versions of Jest it is known that by default Shifty may cause warnings that look like:

Jest has detected the following 1 open handle potentially keeping Jest from exiting:

To prevent this, use shouldScheduleUpdate in your test setup like so:

import { shouldScheduleUpdate } from 'shifty'

afterAll(() => {
  shouldScheduleUpdate(false)
})

Breaking changes

From v2 to v3

Shifty's legacy version 2 remains available in the v2 branch. Legacy documentation can be found at: https://shifty-git-v2-jeremyckahn.vercel.app/

  • Tweenable.formulas has been renamed to Tweenable.easing
  • tweenConfig.step has been removed in favor of tweenConfig.render (behavior and API is unchanged).
  • tweenConfig.attachment has been removed in favor of tweenConfig.data (behavior and API is unchanged).
  • Tweenable#tweenable has been removed.
  • Tweenable#set() is now Tweenable#setState.
  • Tweenable#get() is now Tweenable#state (a getter, not a method).
  • Tweenable#hasEnded() is now Tweenable#hasEnded (a getter, not a method).
  • Tweenable#isPlaying() is now Tweenable#isPlaying (a getter, not a method).
  • Tweenable#setScheduleFunction has been removed. The static method Tweenable.setScheduleFunction method should be used instead.
  • Render handler parameters have been reordered:
    • In v2, the function signature was (state: TweenState, data: Data, timeElapsed: number) => void
    • In v3, the function signature was (state: TweenState, timeElapsed: number, data: Data) => void
  • Scene#play() has been renamed to Scene#tween.
  • Scene#isPlaying() is now Scene#isPlaying (a getter, not a method).
  • Scene#playingTweenables() has been removed.
  • unsetBezierFunction has been removed.
  • Shifty "Core" build has been removed.

Non-breaking changes

  • Token extension is now baked into Shifty Core.

Contributors

Take a look at the Network page to see all of the Shifty contributors.

Special thanks goes to Thomas Fuchs: Shifty's easing functions and Bezier curve code was adapted from his awesome Scripty2 project.

License

Shifty is distributed under the MIT license. You are encouraged to use and modify the code to suit your needs, as well as redistribute it.

shifty's People

Contributors

3rd-eden avatar arthuro555 avatar dependabot[bot] avatar github-actions[bot] avatar jeremyckahn avatar joelambert avatar jspears avatar kris-b avatar laurieboyes avatar millermedeiros avatar rkofman avatar sboudrias avatar unstoppablecarl avatar vascanera avatar wparad avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shifty's Issues

Tweenable cannot be seeked if not previously played

The resume method is the only place this._timeoutHandler get setup.

This mean you cannot create a tween and seek it directly:

var tween = new Tweenable({ duration: 1000 });
tween.seek(50); // this throw because undefined is passed
                // to the scheduler (requestAnimationFrame)

seek should probably make sure the tween is configured before it tries playing it.

Also, is there a reason why seek() play a tween? For our use case, we just want to use shifty for coordinating animations based on user interaction.

Feed elapsed time to play/pause/resume/step/stop.

A new "feature" that really needs to get in the library is that of feeding the elapsed time since the tween started (with respect to pauses/resumes) as a (third?) parameter.
Thus, with the attachment and the elapsed time passed to the processing functions we have everything we need to build "magic".

named function expressions and IE < 9

JScript have some issues with named function expressions (memory leaks + generates 2 distinct functions) I would probably remove the names since they cause more problems than it solves...

PS: closure compiler already remove the names if they aren't being used anywhere, so no big deal, just a heads up..

TODO: Update the README

The README does not reflect the current state of Shifty as accurately as it could. Plugin information is outdated, for example.

Should not be able to seek to a negative value

If I enter a seek value less than 0, it should default to the properties specified at 0ms.

For example,

var tweenable = new Tweenable({}, { from: {opacity:0}, to: {opacity:1}, duration:100});
tweenable.seek(-10); //This should probably seek to the point where opacity = 0, but 
                     //what is happening is that opacity is now -0.1

Look-ahead optimisation, Reusability

Hi,
It's been a while, but I'm back. :) One does not simply stay away from Shifty.
I have a few power optimisations in the backlog that I want to address.
First of all, just to get warmed up, I want to add support for LOOKAHEAD tables inside Shifty.
Second, I want REUSABLE Tweenables.
I am using Shifty on mobile (PhoneGap) and I want to squeeze every bit of performance out of the webview. But to avoid stuttering due to inefficient garbage collection in the webview, I need to reuse the objects (tweenable instances), and not use the default create/destroy cycle.
Lookahead could be enforced through a Tweenable setup option (lookaheadOptimisation : TRUE). When this is used, we translate performance gain into data, meaning that we are not doing math computations while running the tweenable's lifecycle, but instead, precompute all the values for each frame in advance, in an Array (arrays are still the fastest and most comprehensive JS data structures for reading). Depending on the duration of the tweenable and the framerate, that's going to be a big array, but it pays off in performance. Some usage scenarios include big background animated layers and situations where many objects are animating at the same time.
On the reusability side, I have to check the current state of Shifty. In the past (a few months ago) there were some issues that stopped me from reusing the same tweenable instances. Will check it out and continue from there.
It's good to be back to Shifty-ing. Cheers!

Make #seek() a lower level method

I remember from past discussion that Tweenable#seek() wasn't a method designed into shifty when it got created.

As so, when calling #seek(), we actually just set the play head to a specific value and triggers a timeout handler.

I believe #seek() shouldn't think about scheduling and should simply update the state of the tweenable then call the step methods synchronously.

It might make sense then to actually calls the seek method internally when playing a tween (the timeoutHandler() could actually calls #seek()). Then seek might really be built into the core and fit better with other parts. Thoughts?

Pause state control

Users need to be able to toggle the pause state on a tween.

var myTween = tweeny.tween(...);

myTween.pause();
myTween.resume();

More examples please.

The code looks really clean and is extremely readable, but how do you actually use it ?
Please provide some examples in the documentation as well as (at least) one real-world animation example (with multiple tweenable objects). That would be great.

Needs a "to" function

You shouldn't always have to specify a from state for each tween, Tweenables should be smart enough to tween from their most recent state.

Keep a TypeScript definition file for Shifty.

While I keep using Javascript for lower-level stuff in the webview, to squeeze out some more horsepower, where applicable, I also use TypeScript as my main programming language for doing the more structured code my phonegap/web/webview-related apps.
That's why I would like to keep an up-to-date TypeScript definition file for Shifty.
Anybody else interested?

Tween single value instead of object of values

Hi there!

It would be nice if one could simply tween only a single value without creating an object of values first, that would save quite some code in my case. I.e.:

this.tweenable = new twn(); this.tweenable.tween({ from: 1.3, to: 6.3, duration: 1000 });

Then this.tweenable.get() would return the Number directly.

Or is this function already built in? I found .tweenProp (vs. .tweenProps) in your source, but didn't find out if I could use this somehow...

Thanks and best,
Benni.

Javascript extensions

Hi, I've seen your template to extend Tweenable.

function Cartoon () {
// Borrow Tweenable's constructor
this.constructor.call(this);
console.log('Whoop whoop! This is my framerate: ', this.fps);
}

// Set Cartoon to share Tweenable's prototype
Cartoon.prototype = Tweenable.prototype;
var myCartoon = new Cartoon();

And I see two errors:


1 - Use constructor property to call parent constructor:

function Cartoon () {
// Borrow Tweenable's constructor
this.constructor.call(this);
console.log('Whoop whoop! This is my framerate: ', this.fps);
}

This means two things: the instance's constructor property is Tweeneable, not Cartoon

// 'static' method
Cartoon.staticMethod = function() { };
this.constructor.staticMethod(); // Error! constructor is Tweeneable

and than if I extend Cartoon

function Goku () {
// Borrow Cartoon's constructor
this.constructor.call(this);
}

// Set Goku to share Cartoon's prototype
Goku.prototype = Cartoon.prototype;

this.constructor is calling Tweeneable so Cartoon constructor is never called so 'Whoop whoop! This is my framerate: ' message will never be shown.


2 - Assing prototype. Not prototyping it
// Set Cartoon to share Tweenable's prototype
Cartoon.prototype = Tweenable.prototype;

They are the same object! I cannot extend Cartoon.prototype without modify Tweenable.prototype

Tweenable.prototype.method = null;
Cartoon.prototype.method = 1;
Cartoon.prototype.method === Tweenable.prototype.method; // true


My solution:

Use real prototyping for prototypes, call directly the parent at constructor:

function Cartoon() {
Tweeneable.call(this);
}

Cartoon.prototype = new Tweeneable();

OR

function Cartoon() {
Tweeneable.call(this);
}

function emptyConstructor() { };
emptyConstructor.prototype = Tweeneable.prototype;
Cartoon.prototype = new emptyConstructor();

invalid assignment left-hand side

There's an error when the minified version is loaded on Firefox 13.0.1. It works fine on Chromium and IE8.

The error happens when it evaluates (false=function(){return+(new Date)}) at the beginning of the file. I don't know if the same happens with the non minified version.

You can try it there : http://jsfiddle.net/kuVnu/ (I only copied the minified version into the javascript box).

getInterpolatedValues() does not take "delay" into account.

It seems that getInterpolatedValues() method from shifty.interpolate.js module does not take "delay" factor into account, thus, we are actually left with a fake frame (ahead by "delay" milliseconds). Should we get delay into account?

Provide a way to manually define the "scheduling" policy

Sometimes requestAnimationFrame is not usable mainly because of its throtling feature when the window is not visible. That's is usualy fine for UI tweening but for example I'm tweening the volume value of a MediaElement and I don't want this tween to be throttled.

Currently, the internal schedule function is automaticaly picked between RAF and then setTimeout if RAF is not available. Would be great to at least allow to force setTimout usage or even better provide a way to define a schedule function.

Suggestion: iterate through array.

This would be a great add-on to Shifty:
in the "to" property object, the tweened variables could be arrays (of numbers, strings, whatever), and the engine would iterate through the array's items and return the current value as state.

Example:
var T = (new Tweenable()).setConfig({
from : { x : 0, y : 0 },
to : { x : [1, 6, 13, 8, 19, 25, 42, 66, 'ALMOST THERE', 'DONE'], y :100 }
});

It should work like this: the contents of the array are irrelevant to the engine.
What is relevant is the number of items in the array. This is in fact the number of phases in the tween.
On each tweening cycle, the state returned is the value of the item that corresponds to current phase (segment). It is, theoretically, a tween from 0 to ARRAY.length() plus/minus offset/range.

The "from".x member should be the starting index of the array (start offset), or a range (array with 2 integer items). For instance, from { x : 2, y : 0 }... should iterate through the "to".x array starting from index 2 and above.
For ranges, the tweening will be limited between the specified range of items in the array.

I will try an implementation and see how this goes.
I will also come up with a real world use-case of this scenario (I already have one, a 2-dimensional line chart representation).

Needs a homepage

Show some demos, son!

Shifty needs a homepage to show why people should use it. People shouldn't have to read the README.

Readme.md needs an update.

Hello.
Readme.md file needs an update with the new features in the given examples. So, when you have a few minutes to spare, please, fill up the text with the missing info.
Thanks and keep up the good work!
Cheers!

root is empty when using browserify

When requiring shifty via browserify, the root object is set as an empty object. When Tweenable.prototype.stop is called, shifty throws

Uncaught TypeError: undefined is not a function     shifty.js:397
Tweenable.stop                                      shifty.js:397
timeoutHandler                                      shifty.js:186_timeoutHandler

Configuration options for "autostart" and initial "delay".

A configuration option "autostart" should be added that will allow a tween to not start when it is created, leaving that to the user to decide (calling "start"). The "seek" feature should also work with "autostart: false", and, when the "start" is called on a sought tween, it should behave like a resume from that location. This is a bit deeper, really needs some discussion.

And initial "delay" is also a must-have config option. This will allow a tween to do "nothing" for a certain amount of time before starting. I know, this could be done with a "beforeTween" hook, but having it front-and-center in the library would be of much higher importance.

Per-property duration value(s)

Here's a new thing that I want to implement: per-property duration values.
Instead of using
{
....
duration : 1000
...
}
we should allow "duration" to be an object with each property specifying its own duration, and, even better, a start delay, something like this:
{
'x' : [10, 2000],
'y' : [1000, 500],
'rot' : [500, 3000]
}
and thus, one Tweenable instance takes care of many properties in one go. The maximum duration should be extracted by parsing and choosing the maximum 'start' + 'duration' (in our case the maximum is [500, 3000] = 3500ms).
All the properties that are declared in 'from' but not specified in 'duration' use the maximum amount of time and have no start delay.
Hopefully, this will not introduce a lot of logic, but the results will bring some real simplicity in the code that is using the tweenables.
Cheers!

Timeout Handler not using the correct currentTime

For the way I want to use Shifty, I want to be able to seek to any point in a Tweenable's duration, but I never really want to seek to the end of the duration since the stop() function will get called in TimeoutHandler and I don't want to stop the tween.

As a result, what I tried to do is check if the millisecond I'm providing the seek function is >= the Tweenable object's duration, and if it is, change the millisecond to a value just smaller than the duration to ensure that stop() never gets called.
However, even after this check, it seemed that stop() would still get called because, the timeoutHandler_isEnded variable in timeoutHandler() would turn true because of the way currentTime was being calculated.

@SBoudrias and I noticed that in seek() when creating the timestamp to pass to timeoutHandler, you call now(), and then when creating the currentTime variable in TimeoutHandler() you call now() again. As a result of calling now() a second time, it isn't the same as the now() call in seek(). As a result there can be a mismatch when calculating currentTime and timeoutHandler_isEnded, which can result in an inaccurate value of the timeoutHandler_offset and can result in stop() being called if you didn't want to.

I made a simple fix in the following link.
https://github.com/ZainManji/shifty/blob/master/src/shifty.core.js

You just need to call now() once and pass a reference to it.

shouldn't `callback()` and `step()` receive `state.current` as first argument?

currently it is being passed as the this object but I feel that it should be the first argument instead (to work closer to the way the hooks work), it will also allow setting a different context for the callbacks in the future if needed.

diff --git a/src/shifty.core.js b/src/shifty.core.js
index e5f2ca2..2b95e92 100644
--- a/src/shifty.core.js
+++ b/src/shifty.core.js
@@ -167,7 +167,7 @@
         invokeHook('step', params.hook, params.owner, [state.current]);
       }

-      params.step.call(state.current);
+      params.step.call(null, state.current);

     } else {
       // The duration of the tween has expired
@@ -375,7 +375,7 @@
     if (gotoEnd) {
       simpleCopy(this._state.current, this._tweenParams.to);
       applyFilter('afterTweenEnd', this, [this._state.current, this._tweenParams.originalState, this._tweenParams.to]);
-      this._tweenParams.callback.call(this._state.current);
+      this._tweenParams.callback.call(null, this._state.current);
     }

     return this;

PS: didn't asked for a pull request since this change will break compatibility with legacy code that depends on this inside callbacks and changes are so trivial...

Add a way to create a Tweenable() instance and set tween() params without starting tween

I am queuing multiple tweens it would be nice to be able to completely create and configure a tweenable object without starting the tween.

For example I think you should be able to do this (or similar) easily:

var queue = [];

// second param accepts same params as tween()
var tween1 = new Tweenable(obj, {
    from: { x: 0,  y: 50  },
    to:   { x: 10, y: -30 },
    duration: 1500,
});
queue.push(tween1);

// second param accepts same params as tween()
var tween2 = new Tweenable(obj, {
    from: { x: 10,  y: 0  },
    to:   { x: 110, y: -10 },
    duration: 1000,
});
queue.push(tween2);

// later when ready
for (var i = 0; i < queue.length; i++) {
    var tween = queue[i];
    tween.resume();
}

Delay

Hi, is there a possibility to define a delay in ms before starting a tween?
I'm aware that it's easy to make it with a timer, but a native shifty feature would be more smart.

Kris

Can't tween with CSS of Transform, -Webkit-Transform, -Moz-Transform, etc

If I make a Tweenable and specify a tween as the one below, an error comes up when Shifty tries to getFormattedValues from it. I looked into it, and it has to do again with not cloning the returned value from getFormatChunksFrom(rawValues, prop) on Line 1287.

E.g.

var config = {from : {
         'transform': 'scale(1) translateZ(0)',
         '-webkit-transform': 'scale(1) translateZ(0)' 
     },  to: { 
         'transform': 'scale(2) translateZ(0)', 
         '-webkit-transform': 'scale(2) translateZ(0)' }, 
     duration: 100 
};
var tween = new Tweenable({}, config);
tween.seek(50); // Throws an error, because the 
             // manifestAccumulator[prop] variable's chunknames property 
             // are all mutated to the last array returned by getFormatChunksFrom()

Color plugin fails if there is whitespace in the string

The color outputted isn't a valid string when there is whitespace in the input string.

e.g.

from: { color: 'rgb(0, 0, 255);  }
to: { color: 'rgb(255, 0, 0); }

The output is either NaN or NaNrgb(255, 0, 0)

This is an issue as its how WebKit browsers format the colour param of an element. I suspect this should be an easy fix, just clean the string on initialisation?

string.replace(/\s/g, '');

New interpolate API proposal

Hi,

I love the idea of Shifty and how it only deals with generating interpolated values but not doing anything with them, this is perfect for my use case!

Unfortunately I can't quite figure out how to make it work with my code with the current API.

What I'd love to be able to do is the following:

shifty.interpolate(from, to, easingFn, progress);

or

shifty.interpolate({
    from: from, 
    to: to, 
    easing: easing, 
    progress: progress
});

So an example of this would be:

var from = {
    width: 100px,
    color: #FFF
};  

var to = {
    width: 200px,
    color: #CCC
};

// Work out interpolated states for various positions in the animation
var p10   = shifty.interpolate(from, to, 'linear', 0.1); // 10%
var p30   = shifty.interpolate(from, to, 'linear', 0.3); // 30%
var p50   = shifty.interpolate(from, to, 'linear', 0.5); // 50%
var p70   = shifty.interpolate(from, to, 'linear', 0.7); // 70%
var p100  = shifty.interpolate(from, to, 'linear', 1.0); // 100%

The return value of the function should be a key/value object containing the CSS values at the given percentage complete.

Is this something that could be added?

Wrap the formula methods to support Scripty2 syntax

It would be helpful if the tweening methods supported a simpler for non-Tweenable use syntax. This wrapper method should be attached to the public util property:

/**
*@param {String} formula The name of the Penner formula to use
*@param {Number} pos A normalized value (between 0 and 1) to calculate
*/
Tweenable.util.tweenValFor  = function (formula, pos) {
  var calculatedValue;
  // Do some crazy math...
  return calculatedValue;
};

Don't retriggers handler when seeking to the same frame

When seeking through a tweenable, I think it would make sense to not triggers the step handlers if there's no change at the time head.

For example:

tween.seek(15);
// This is triggering the step handler as expected.

// Then some time later
tween.seek(15);
// As we're still at the same position, we shouldn't triggers the handlers again

This is easily handled by user side code, but handling this on the side of shifty would make user code cleaner and probably helps to silently improve performances.

comma-first and lint

I know you are using Vim, did you considered running node-jshint at each file save? it will catch comma errors - Syntastic plugin is hightly recommended (it will lint files during save). I also coded a plugin to enhance the location list with useful info about the buffer.

just posting it here since when I open the files I get errors about the commas (since my lint settings are different). it would be also good to have a .jshintrc file (for node-jshint) with the lint settings of the project.

cheers

dist: Leaked "SHIFTY_DEBUG_NOW" global

using this as a dep (via browserify), a global SHIFTY_DEBUG_NOW is leaked. looks like it gets "compiled away" if in ./dist/shifty.min.js, but not if you're using the un-minified version.

any way we could get rid of the unnecessary global (without using the minified version)?

`step()` should be called with the end value as well

if you have a tween like:

var foo = new Tweenable();
foo.tween({
  from : { a : 1},
  to : { a : 10 },
  duration : 500,
  step : function() {
     console.log( foo.get() );
  }
});

the step callback never gets called with the value 10 (end value) and I think it should since in many cases the user may not want to set a callback for complete as well (if you are handling the changes based only on step).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.