Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# EditorConfig is awesome: https://EditorConfig.org

root = true

[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 2
trim_trailing_whitespace = true
33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ The above code sets up a fairly simple transformer: it sets the opacity of the
element to 0 if `window.scrollY` reaches more than 600, and back to 1 if the
user scrolls back up above 600px again.

In addition to **styles**, transform-when can animate **attrs**, and
**transforms**. transforms is a helper function, and will set the `transform`
style on HTML elements and the `transform` property on SVG elements.
In addition to **styles**, transform-when can animate **attrs**, **transforms**,
and **properties** (CSS variables). transforms is a helper function, and will
set the `transform` style on HTML elements and the `transform` property on SVG
elements.

Let's take a look at a longer example that uses all three:

Expand All @@ -87,6 +88,11 @@ const transforms = new Transformer([
['scale', function (y) {
return Transformer.transform([500, 600], [1, 0.5], y);
}]
],
properties: [
['--scrollX', function (x) {
return x;
}, 'px']
]
}
]);
Expand Down Expand Up @@ -250,16 +256,17 @@ const transforms = new Transformer([

### Types of properties

There are three types of properties, `styles`, `attrs` and `transforms`. The
first two are both pretty simple: they just set styles and attributes of an
element. Be careful animating attributes and styles that aren't the opacity:
they are more expensive to animate than transforms and opacity, and might make
your animation jerky.

Each takes an array of three things: the property (style or attribute) to
animate, the transform functions, and optionally the unit to use - it's better
to let transform-when handle adding the unit, because it will also round the
number for you.
There are four types of properties, `styles`, `attrs`, `properties`, and
`transforms`. The first three are both pretty simple: they just set styles and
attributes of an element. Be careful animating attributes and styles that aren't
the opacity: they are more expensive to animate than transforms and opacity,
and might make your animation jerky.

Each takes an array of three things: the property (style, attribute, or
variable name) to animate, the transform functions, and optionally the unit to
use - it's better to do it this way and let transform-when handle adding the
unit instead of returning the unit in the string yourself, because it will also
round the number for you.

Let's take a look at an example:

Expand Down
42 changes: 39 additions & 3 deletions src/Transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,10 @@ Transformer.prototype._setup = function setupFrame(vars) {
for (let transform of this.transforms) {
if (this.i === 0) {
// This is where data to be put into the DOM is stored until ._frame()
transform._stagedData = { styles: {}, attrs: {} };
transform._stagedData = { styles: {}, attrs: {}, props: {} };

// This is where data from the last run is kept so it can be compared for changes
transform._lastData = { styles: {}, attrs: {} };
transform._lastData = { styles: {}, attrs: {}, props: {} };

// Has to run before visible check
if (transform.transforms) {
Expand Down Expand Up @@ -262,6 +262,22 @@ Transformer.prototype._setup = function setupFrame(vars) {
}
}
}

if (transform.properties) {
for (let [ prop, fn, unit = '' ] of transform.properties) {
let value = callFn('props', prop, fn, transform, unit, args);

if (value === transform._lastData.props[prop]) {
value = UNCHANGED;
}

transform._stagedData.props[prop] = value;

if (value !== UNCHANGED) {
transform._lastData.props[prop] = value;
}
}
}
}

if (this._actionEnded) {
Expand Down Expand Up @@ -369,6 +385,26 @@ Transformer.prototype._frame = function transformFrame(vars) {
});
}
}

if (transform.properties) {
for (let [ prop ] of transform.properties) {
const computed = transform._stagedData.props[prop];

if (computed === UNCHANGED) {
continue;
}

each(transform.el, (el) => {
if (Array.isArray(prop)) {
prop.forEach((attr) => {
el.style.setProperty(attr, computed);
});
} else {
el.style.setProperty(prop, computed);
}
});
}
}
}

const currentTime = window.performance ? performance.now() : Date.now();
Expand All @@ -384,7 +420,7 @@ Transformer.prototype._frame = function transformFrame(vars) {
} else if (deltaTime > 1000 / this.iIncrease.optimalFps) {
this.i += increaseBy[this.iIncrease.belowOptimal];
} else {
this.i += increaseBy[this.iIncrease.aboveOptimal];
this.i += increaseBy[this.iIncrease.aboveOptimal];
}

this._last = vars;
Expand Down
164 changes: 107 additions & 57 deletions test/transformer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,28 @@ describe('Transformer', function () {
}, 20);
});

// This test doesn't work in phantomjs
it.skip('should support setting CSS variables', function (done) {
transformer = new Transformer([
{
el: mock,
properties: [
['--foo', function (i) {
return 'blue';
}]
]
}
]);

interval = setInterval(function () {
const property = mock.style.getPropertyValue('--foo');
if (property === 'blue') {
clearInterval(interval);
done();
}
}, 20);
});

describe('actions and triggers', function () {
it('should support triggering actions', function (done) {
var lastActions;
Expand Down Expand Up @@ -884,6 +906,34 @@ describe('Transformer', function () {
}, 5);
});

it('should not write property changes to DOM if they haven\'t changed', function (done) {
var called = 0;

var transformPart = {
el: mock,
properties: [
['--foo', function (i) {
called++;
return 'blue';
}]
]
};

transformer = new Transformer([ transformPart ]);

interval = setInterval(function () {
if (called === 1) {
transformPart._stagedData.props['--foo'].should.equal('blue');
}

if (called > 1) {
transformPart._stagedData.props['--foo'].toString().should.startWith('Symbol(unchanged)');
clearInterval(interval);
done();
}
}, 5);
});

it('should handle partial transform UNCHANGEDs', function (done) {
var called = 0;

Expand Down Expand Up @@ -1004,62 +1054,62 @@ describe('Transformer', function () {
});

describe('i increase rate', function () {
it('should increase by 1 when fps is less than optimal and mode is "count"', function (done) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignore everything below here, this file had mixed spaces and tabs! 🙈

var called = 0;
var lastI;

transformer = new Transformer([
{
el: mock,
styles: [
['opacity', function (i) {
called++;
lastI = i;
}]
]
}
]);

transformer.iIncrease.optimalFps = 120;

interval = setInterval(function () {
if (called === 3) {
clearInterval(interval);

lastI.should.equal(2);

done();
}
}, 5);
});

it('should increase by < 1 when fps is more than optimal and mode is "time"', function (done) {
var called = 0;
var lastI;

transformer = new Transformer([
{
el: mock,
styles: [
['opacity', function (i) {
called++;
lastI = i;
}]
]
}
]);

transformer.iIncrease.optimalFps = 20;

interval = setInterval(function () {
if (called === 3) {
clearInterval(interval);

lastI.should.be.within(1.2, 1.6);

done();
}
}, 5);
});
it('should increase by 1 when fps is less than optimal and mode is "count"', function (done) {
var called = 0;
var lastI;

transformer = new Transformer([
{
el: mock,
styles: [
['opacity', function (i) {
called++;
lastI = i;
}]
]
}
]);

transformer.iIncrease.optimalFps = 120;

interval = setInterval(function () {
if (called === 3) {
clearInterval(interval);

lastI.should.equal(2);

done();
}
}, 5);
});

it('should increase by < 1 when fps is more than optimal and mode is "time"', function (done) {
var called = 0;
var lastI;

transformer = new Transformer([
{
el: mock,
styles: [
['opacity', function (i) {
called++;
lastI = i;
}]
]
}
]);

transformer.iIncrease.optimalFps = 20;

interval = setInterval(function () {
if (called === 3) {
clearInterval(interval);

lastI.should.be.within(1.2, 1.6);

done();
}
}, 5);
});
});
});