Skip to content
This repository was archived by the owner on Apr 20, 2023. It is now read-only.

Commit 59a89ed

Browse files
committed
Beginning of CSS3 transitions implementation. Added parser for transition property, implementations for cubic bezier and step timing functions, and basic color interpolation.
1 parent e7d3630 commit 59a89ed

File tree

6 files changed

+242
-4
lines changed

6 files changed

+242
-4
lines changed

build.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<fileset file="${src_dir}/BgSize.js" />
3333
<fileset file="${src_dir}/Angle.js" />
3434
<fileset file="${src_dir}/Color.js" />
35+
<fileset file="${src_dir}/TransitionTimingFunction.js" />
3536
<fileset file="${src_dir}/Tokenizer.js" />
3637
<fileset file="${src_dir}/BoundsInfo.js" />
3738
<fileset file="${src_dir}/StyleInfoBase.js" />
@@ -41,6 +42,7 @@
4142
<fileset file="${src_dir}/BorderImageStyleInfo.js" />
4243
<fileset file="${src_dir}/BoxShadowStyleInfo.js" />
4344
<fileset file="${src_dir}/PaddingStyleInfo.js" />
45+
<fileset file="${src_dir}/TransitionStyleInfo.js" />
4446
<fileset file="${src_dir}/VisibilityStyleInfo.js" />
4547
<fileset file="${src_dir}/VmlShape.js" />
4648
<fileset file="${src_dir}/RendererBase.js" />
@@ -79,13 +81,15 @@
7981
<fileset file="${src_dir}/BgSize.js" />
8082
<fileset file="${src_dir}/Angle.js" />
8183
<fileset file="${src_dir}/Color.js" />
84+
<fileset file="${src_dir}/TransitionTimingFunction.js" />
8285
<fileset file="${src_dir}/Tokenizer.js" />
8386
<fileset file="${src_dir}/BoundsInfo.js" />
8487
<fileset file="${src_dir}/StyleInfoBase.js" />
8588
<fileset file="${src_dir}/BackgroundStyleInfo.js" />
8689
<fileset file="${src_dir}/BorderStyleInfo.js" />
8790
<fileset file="${src_dir}/BorderImageStyleInfo.js" />
8891
<fileset file="${src_dir}/PaddingStyleInfo.js" />
92+
<fileset file="${src_dir}/TransitionStyleInfo.js" />
8993
<fileset file="${src_dir}/RendererBase.js" />
9094
<fileset file="${src_dir}/IE9RootRenderer.js" />
9195
<fileset file="${src_dir}/IE9BackgroundRenderer.js" />

sources/Color.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,37 @@ PIE.Color = (function() {
189189
alpha: function() {
190190
this.parse();
191191
return this._alpha;
192+
},
193+
194+
195+
/**
196+
* Given another target color, calculate and return an intermediate color at a given
197+
* percentage between the two.
198+
* @param {PIE.Color} toColor The other color being transitioned to
199+
* @param {number} percent The percent (number from 0 to 1) between the two colors to calculate
200+
* @param {Element} el
201+
* @return {PIE.Color} The interpolated color
202+
*/
203+
interpolate: function( toColor, percent, el ) {
204+
var hex1 = this.hexValue( el ),
205+
hex2 = toColor.hexValue( el ),
206+
parse = parseInt,
207+
r1 = parse( hex1.substr(1, 2), 16 ),
208+
g1 = parse( hex1.substr(3, 2), 16 ),
209+
b1 = parse( hex1.substr(5, 2), 16 ),
210+
r2 = parse( hex2.substr(1, 2), 16 ),
211+
g2 = parse( hex2.substr(3, 2), 16 ),
212+
b2 = parse( hex2.substr(5, 2), 16 ),
213+
a1 = this.alpha(),
214+
a2 = toColor.alpha(),
215+
newColor = new Color();
216+
217+
// Set the internal color/alpha cached values directly so it doesn't have to re-parse
218+
newColor._alpha = ( a2 - a1 ) * percent + a1;
219+
newColor._color = '#' + ( ( r2 - r1 ) * percent + r1 ).toString( 16 ) +
220+
( ( g2 - g1 ) * percent + g1 ).toString( 16 ) +
221+
( ( b2 - b1 ) * percent + b1 ).toString( 16 );
222+
return newColor;
192223
}
193224
};
194225

sources/Element.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,15 @@ PIE.Element = (function() {
112112
backgroundInfo: new PIE.BackgroundStyleInfo( el ),
113113
borderImageInfo: new PIE.BorderImageStyleInfo( el ),
114114
borderInfo: new PIE.BorderStyleInfo( el ),
115-
paddingInfo: new PIE.PaddingStyleInfo( el )
115+
paddingInfo: new PIE.PaddingStyleInfo( el ),
116+
transitionInfo: new PIE.TransitionStyleInfo( el )
116117
};
117118
styleInfosArr = [
118119
styleInfos.backgroundInfo,
119120
styleInfos.borderInfo,
120121
styleInfos.borderImageInfo,
121-
styleInfos.paddingInfo
122+
styleInfos.paddingInfo,
123+
styleInfos.transitionInfo
122124
];
123125
rootRenderer = new PIE.IE9RootRenderer( el, boundsInfo, styleInfos );
124126
childRenderers = [
@@ -134,6 +136,7 @@ PIE.Element = (function() {
134136
borderRadiusInfo: new PIE.BorderRadiusStyleInfo( el ),
135137
boxShadowInfo: new PIE.BoxShadowStyleInfo( el ),
136138
paddingInfo: new PIE.PaddingStyleInfo( el ),
139+
transitionInfo: new PIE.TransitionStyleInfo( el ),
137140
visibilityInfo: new PIE.VisibilityStyleInfo( el )
138141
};
139142
styleInfosArr = [
@@ -143,6 +146,7 @@ PIE.Element = (function() {
143146
styleInfos.borderRadiusInfo,
144147
styleInfos.boxShadowInfo,
145148
styleInfos.paddingInfo,
149+
styleInfos.transitionInfo,
146150
styleInfos.visibilityInfo
147151
];
148152
rootRenderer = new PIE.RootRenderer( el, boundsInfo, styleInfos );

sources/Tokenizer.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ PIE.Tokenizer = (function() {
2727
OPERATOR: 256,
2828
PERCENT: 512,
2929
STRING: 1024,
30-
URL: 2048
30+
URL: 2048,
31+
TIME: 4096
3132
};
3233

3334
/**
@@ -63,7 +64,8 @@ PIE.Tokenizer = (function() {
6364
'px': Type.LENGTH, 'em': Type.LENGTH, 'ex': Type.LENGTH,
6465
'mm': Type.LENGTH, 'cm': Type.LENGTH, 'in': Type.LENGTH,
6566
'pt': Type.LENGTH, 'pc': Type.LENGTH,
66-
'deg': Type.ANGLE, 'rad': Type.ANGLE, 'grad': Type.ANGLE
67+
'deg': Type.ANGLE, 'rad': Type.ANGLE, 'grad': Type.ANGLE,
68+
's': Type.TIME, 'ms': Type.TIME
6769
},
6870

6971
colorFunctions: {

sources/TransitionStyleInfo.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* Handles parsing, caching, and detecting changes to the 'transition' CSS property
3+
* @constructor
4+
* @param {Element} el the target element
5+
*/
6+
PIE.TransitionStyleInfo = PIE.StyleInfoBase.newStyleInfo( {
7+
8+
cssProperty: 'transition',
9+
styleProperty: 'transition',
10+
11+
parseCss: function( css ) {
12+
var props,
13+
Type = PIE.Tokenizer.Type,
14+
tokenizer, token, tokenType, tokenValue, currentTransition,
15+
a, b, c, d;
16+
17+
function tokenIsChar( token, ch ) {
18+
return token && token.tokenValue === ch;
19+
}
20+
21+
function tokenToNumber( token ) {
22+
return ( token && ( token.tokenType & Type.NUMBER ) ) ? parseFloat( token.tokenValue, 10 ) : NaN;
23+
}
24+
25+
function flushCurrentTransition() {
26+
// Fill in defaults
27+
if (!currentTransition.property) {
28+
currentTransition.property = 'all';
29+
}
30+
if (!currentTransition.timingFunction) {
31+
currentTransition.timingFunction = 'ease';
32+
}
33+
if (!currentTransition.delay) {
34+
currentTransition.delay = '0';
35+
}
36+
props.push(currentTransition);
37+
currentTransition = 0;
38+
}
39+
40+
if( css ) {
41+
tokenizer = new PIE.Tokenizer( css );
42+
props = [];
43+
44+
while( token = tokenizer.next() ) {
45+
tokenType = token.tokenType;
46+
tokenValue = token.tokenValue;
47+
if (!currentTransition) {
48+
currentTransition = {
49+
// property: 'all',
50+
// duration: '0ms',
51+
// timingFunction: <PIE.TransitionTimingFunction.*>,
52+
// delay: '0ms'
53+
};
54+
}
55+
56+
// Time values: first one is duration and second one is delay
57+
if (tokenType & Type.TIME && !currentTransition.duration) {
58+
currentTransition.duration = tokenValue;
59+
}
60+
else if (tokenType & Type.TIME && !currentTransition.delay) {
61+
currentTransition.delay = tokenValue;
62+
}
63+
64+
// Timing function
65+
else if (tokenType & Type.IDENT && !currentTransition.timingFunction && tokenValue in PIE.TransitionTimingFunction.namedEasings) {
66+
currentTransition.timingFunction = PIE.TransitionTimingFunction.namedEasings[tokenValue];
67+
}
68+
else if (tokenType & Type.FUNCTION && !currentTransition.timingFunction) {
69+
if ( tokenValue === 'step' &&
70+
!isNaN( a = tokenToNumber( tokenizer.next() ) ) && a > 0 && Math.floor( a ) === a &&
71+
tokenIsChar( tokenizer.next(), ',' ) &&
72+
( b = tokenizer.next() ) && ( b.tokenType & Type.IDENT ) && ( b.tokenValue === 'start' || b.tokenValue === 'end' ) &&
73+
tokenIsChar( tokenizer.next(), ')' )
74+
) {
75+
currentTransition.timingFunction = PIE.TransitionTimingFunction.getStepsTimingFunction( a, b );
76+
}
77+
else if (tokenValue === 'cubic-bezier' &&
78+
!isNaN( a = tokenToNumber( tokenizer.next() ) ) && a >= 0 && a <= 1 &&
79+
tokenIsChar( tokenizer.next(), ',' ) &&
80+
!isNaN( b = tokenToNumber( tokenizer.next() ) ) &&
81+
tokenIsChar( tokenizer.next(), ',' ) &&
82+
!isNaN( c = tokenToNumber( tokenizer.next() ) ) && c >= 0 && c <= 1 &&
83+
tokenIsChar( tokenizer.next(), ',' ) &&
84+
!isNaN( d = tokenToNumber( tokenizer.next() ) ) &&
85+
tokenIsChar( tokenizer.next(), ')' )
86+
) {
87+
currentTransition.timingFunction = PIE.TransitionTimingFunction.getCubicBezierTimingFunction( a, b, c, d );
88+
}
89+
else {
90+
return null;
91+
}
92+
}
93+
94+
// Property name
95+
else if (tokenType & Type.IDENT && !currentTransition.property) {
96+
currentTransition.property = tokenValue;
97+
}
98+
99+
// Comma: start a new value. Only duration is required so fail if it wasn't found.
100+
else if (tokenType & Type.OPERATOR && tokenValue === ',' && currentTransition.duration) {
101+
flushCurrentTransition();
102+
}
103+
104+
// Something unrecognized - fail!
105+
else {
106+
return null;
107+
}
108+
}
109+
110+
// leftovers
111+
if( currentTransition && currentTransition.duration ) {
112+
flushCurrentTransition();
113+
}
114+
115+
}
116+
117+
return props && props.length ? props : null;
118+
}
119+
} );

sources/TransitionTimingFunction.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
PIE.TransitionTimingFunction = {
2+
3+
/**
4+
* Cubic bezier easing utility by Gaëtan Renaudeau, originally "KeySpline.js", used with
5+
* permission of the author. From the writeup at:
6+
* http://blog.greweb.fr/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/
7+
*
8+
* Modified to be a factory that returns a Function, and to make the internal utility
9+
* functions shared across instances.
10+
*
11+
* @param {Number} x1
12+
* @param {Number} y1
13+
* @param {Number} x2
14+
* @param {Number} y2
15+
* @return Function
16+
*/
17+
getCubicBezierTimingFunction: (function() {
18+
function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
19+
function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
20+
function C(aA1) { return 3.0 * aA1; }
21+
22+
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
23+
function CalcBezier(aT, aA1, aA2) {
24+
return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
25+
}
26+
27+
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
28+
function GetSlope(aT, aA1, aA2) {
29+
return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
30+
}
31+
32+
function GetTForX(aX, mX1, mX2) {
33+
// Newton raphson iteration
34+
var aGuessT = aX,
35+
i = 0,
36+
currentSlope, currentX;
37+
for (; i < 4; ++i) {
38+
currentSlope = GetSlope(aGuessT, mX1, mX2);
39+
if (currentSlope == 0.0) return aGuessT;
40+
currentX = CalcBezier(aGuessT, mX1, mX2) - aX;
41+
aGuessT -= currentX / currentSlope;
42+
}
43+
return aGuessT;
44+
}
45+
46+
return function(mX1, mY1, mX2, mY2) {
47+
return function(time) {
48+
if (mX1 == mY1 && mX2 == mY2) return time; // linear
49+
return CalcBezier(GetTForX(time, mX1, mX2), mY1, mY2);
50+
};
51+
}
52+
})(),
53+
54+
/**
55+
* Stepping function - see http://www.w3.org/TR/css3-transitions/#transition-timing-function-property
56+
* @param numSteps The number of steps in the transition
57+
* @param changeAt Whether the step up occurs at the 'start' or 'end' of each step interval. Defaults to 'end'.
58+
* @return Function
59+
*/
60+
getStepsTimingFunction: function(numSteps, changeAt) {
61+
return function(time) {
62+
return Math[ changeAt === 'start' ? 'ceil' : 'floor' ]( time * numSteps ) / numSteps;
63+
};
64+
}
65+
};
66+
67+
68+
PIE.TransitionTimingFunction.namedEasings = {
69+
'ease': PIE.TransitionTimingFunction.getCubicBezierTimingFunction( 0.25, 0.1, 0.25, 1 ),
70+
'linear': PIE.TransitionTimingFunction.getCubicBezierTimingFunction( 0, 0, 1, 1 ),
71+
'ease-in': PIE.TransitionTimingFunction.getCubicBezierTimingFunction( 0.42, 0, 1, 1 ),
72+
'ease-out': PIE.TransitionTimingFunction.getCubicBezierTimingFunction( 0, 0, 0.58, 1 ),
73+
'ease-in-out': PIE.TransitionTimingFunction.getCubicBezierTimingFunction( 0.42, 0, 0.58, 1 ),
74+
'step-start': PIE.TransitionTimingFunction.getStepsTimingFunction( 1, 'start' ),
75+
'step-end': PIE.TransitionTimingFunction.getStepsTimingFunction( 1, 'end' )
76+
};
77+
78+

0 commit comments

Comments
 (0)