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

Commit

Permalink
Beginning of CSS3 transitions implementation. Added parser for transi…
Browse files Browse the repository at this point in the history
…tion property, implementations for cubic bezier and step timing functions, and basic color interpolation.
  • Loading branch information
lojjic committed May 7, 2012
1 parent e7d3630 commit 59a89ed
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 4 deletions.
4 changes: 4 additions & 0 deletions build.xml
Expand Up @@ -32,6 +32,7 @@
<fileset file="${src_dir}/BgSize.js" />
<fileset file="${src_dir}/Angle.js" />
<fileset file="${src_dir}/Color.js" />
<fileset file="${src_dir}/TransitionTimingFunction.js" />
<fileset file="${src_dir}/Tokenizer.js" />
<fileset file="${src_dir}/BoundsInfo.js" />
<fileset file="${src_dir}/StyleInfoBase.js" />
Expand All @@ -41,6 +42,7 @@
<fileset file="${src_dir}/BorderImageStyleInfo.js" />
<fileset file="${src_dir}/BoxShadowStyleInfo.js" />
<fileset file="${src_dir}/PaddingStyleInfo.js" />
<fileset file="${src_dir}/TransitionStyleInfo.js" />
<fileset file="${src_dir}/VisibilityStyleInfo.js" />
<fileset file="${src_dir}/VmlShape.js" />
<fileset file="${src_dir}/RendererBase.js" />
Expand Down Expand Up @@ -79,13 +81,15 @@
<fileset file="${src_dir}/BgSize.js" />
<fileset file="${src_dir}/Angle.js" />
<fileset file="${src_dir}/Color.js" />
<fileset file="${src_dir}/TransitionTimingFunction.js" />
<fileset file="${src_dir}/Tokenizer.js" />
<fileset file="${src_dir}/BoundsInfo.js" />
<fileset file="${src_dir}/StyleInfoBase.js" />
<fileset file="${src_dir}/BackgroundStyleInfo.js" />
<fileset file="${src_dir}/BorderStyleInfo.js" />
<fileset file="${src_dir}/BorderImageStyleInfo.js" />
<fileset file="${src_dir}/PaddingStyleInfo.js" />
<fileset file="${src_dir}/TransitionStyleInfo.js" />
<fileset file="${src_dir}/RendererBase.js" />
<fileset file="${src_dir}/IE9RootRenderer.js" />
<fileset file="${src_dir}/IE9BackgroundRenderer.js" />
Expand Down
31 changes: 31 additions & 0 deletions sources/Color.js
Expand Up @@ -189,6 +189,37 @@ PIE.Color = (function() {
alpha: function() {
this.parse();
return this._alpha;
},


/**
* Given another target color, calculate and return an intermediate color at a given
* percentage between the two.
* @param {PIE.Color} toColor The other color being transitioned to
* @param {number} percent The percent (number from 0 to 1) between the two colors to calculate
* @param {Element} el
* @return {PIE.Color} The interpolated color
*/
interpolate: function( toColor, percent, el ) {
var hex1 = this.hexValue( el ),
hex2 = toColor.hexValue( el ),
parse = parseInt,
r1 = parse( hex1.substr(1, 2), 16 ),
g1 = parse( hex1.substr(3, 2), 16 ),
b1 = parse( hex1.substr(5, 2), 16 ),
r2 = parse( hex2.substr(1, 2), 16 ),
g2 = parse( hex2.substr(3, 2), 16 ),
b2 = parse( hex2.substr(5, 2), 16 ),
a1 = this.alpha(),
a2 = toColor.alpha(),
newColor = new Color();

// Set the internal color/alpha cached values directly so it doesn't have to re-parse
newColor._alpha = ( a2 - a1 ) * percent + a1;
newColor._color = '#' + ( ( r2 - r1 ) * percent + r1 ).toString( 16 ) +
( ( g2 - g1 ) * percent + g1 ).toString( 16 ) +
( ( b2 - b1 ) * percent + b1 ).toString( 16 );
return newColor;
}
};

Expand Down
8 changes: 6 additions & 2 deletions sources/Element.js
Expand Up @@ -112,13 +112,15 @@ PIE.Element = (function() {
backgroundInfo: new PIE.BackgroundStyleInfo( el ),
borderImageInfo: new PIE.BorderImageStyleInfo( el ),
borderInfo: new PIE.BorderStyleInfo( el ),
paddingInfo: new PIE.PaddingStyleInfo( el )
paddingInfo: new PIE.PaddingStyleInfo( el ),
transitionInfo: new PIE.TransitionStyleInfo( el )
};
styleInfosArr = [
styleInfos.backgroundInfo,
styleInfos.borderInfo,
styleInfos.borderImageInfo,
styleInfos.paddingInfo
styleInfos.paddingInfo,
styleInfos.transitionInfo
];
rootRenderer = new PIE.IE9RootRenderer( el, boundsInfo, styleInfos );
childRenderers = [
Expand All @@ -134,6 +136,7 @@ PIE.Element = (function() {
borderRadiusInfo: new PIE.BorderRadiusStyleInfo( el ),
boxShadowInfo: new PIE.BoxShadowStyleInfo( el ),
paddingInfo: new PIE.PaddingStyleInfo( el ),
transitionInfo: new PIE.TransitionStyleInfo( el ),
visibilityInfo: new PIE.VisibilityStyleInfo( el )
};
styleInfosArr = [
Expand All @@ -143,6 +146,7 @@ PIE.Element = (function() {
styleInfos.borderRadiusInfo,
styleInfos.boxShadowInfo,
styleInfos.paddingInfo,
styleInfos.transitionInfo,
styleInfos.visibilityInfo
];
rootRenderer = new PIE.RootRenderer( el, boundsInfo, styleInfos );
Expand Down
6 changes: 4 additions & 2 deletions sources/Tokenizer.js
Expand Up @@ -27,7 +27,8 @@ PIE.Tokenizer = (function() {
OPERATOR: 256,
PERCENT: 512,
STRING: 1024,
URL: 2048
URL: 2048,
TIME: 4096
};

/**
Expand Down Expand Up @@ -63,7 +64,8 @@ PIE.Tokenizer = (function() {
'px': Type.LENGTH, 'em': Type.LENGTH, 'ex': Type.LENGTH,
'mm': Type.LENGTH, 'cm': Type.LENGTH, 'in': Type.LENGTH,
'pt': Type.LENGTH, 'pc': Type.LENGTH,
'deg': Type.ANGLE, 'rad': Type.ANGLE, 'grad': Type.ANGLE
'deg': Type.ANGLE, 'rad': Type.ANGLE, 'grad': Type.ANGLE,
's': Type.TIME, 'ms': Type.TIME
},

colorFunctions: {
Expand Down
119 changes: 119 additions & 0 deletions sources/TransitionStyleInfo.js
@@ -0,0 +1,119 @@
/**
* Handles parsing, caching, and detecting changes to the 'transition' CSS property
* @constructor
* @param {Element} el the target element
*/
PIE.TransitionStyleInfo = PIE.StyleInfoBase.newStyleInfo( {

cssProperty: 'transition',
styleProperty: 'transition',

parseCss: function( css ) {
var props,
Type = PIE.Tokenizer.Type,
tokenizer, token, tokenType, tokenValue, currentTransition,
a, b, c, d;

function tokenIsChar( token, ch ) {
return token && token.tokenValue === ch;
}

function tokenToNumber( token ) {
return ( token && ( token.tokenType & Type.NUMBER ) ) ? parseFloat( token.tokenValue, 10 ) : NaN;
}

function flushCurrentTransition() {
// Fill in defaults
if (!currentTransition.property) {
currentTransition.property = 'all';
}
if (!currentTransition.timingFunction) {
currentTransition.timingFunction = 'ease';
}
if (!currentTransition.delay) {
currentTransition.delay = '0';
}
props.push(currentTransition);
currentTransition = 0;
}

if( css ) {
tokenizer = new PIE.Tokenizer( css );
props = [];

while( token = tokenizer.next() ) {
tokenType = token.tokenType;
tokenValue = token.tokenValue;
if (!currentTransition) {
currentTransition = {
// property: 'all',
// duration: '0ms',
// timingFunction: <PIE.TransitionTimingFunction.*>,
// delay: '0ms'
};
}

// Time values: first one is duration and second one is delay
if (tokenType & Type.TIME && !currentTransition.duration) {
currentTransition.duration = tokenValue;
}
else if (tokenType & Type.TIME && !currentTransition.delay) {
currentTransition.delay = tokenValue;
}

// Timing function
else if (tokenType & Type.IDENT && !currentTransition.timingFunction && tokenValue in PIE.TransitionTimingFunction.namedEasings) {
currentTransition.timingFunction = PIE.TransitionTimingFunction.namedEasings[tokenValue];
}
else if (tokenType & Type.FUNCTION && !currentTransition.timingFunction) {
if ( tokenValue === 'step' &&
!isNaN( a = tokenToNumber( tokenizer.next() ) ) && a > 0 && Math.floor( a ) === a &&
tokenIsChar( tokenizer.next(), ',' ) &&
( b = tokenizer.next() ) && ( b.tokenType & Type.IDENT ) && ( b.tokenValue === 'start' || b.tokenValue === 'end' ) &&
tokenIsChar( tokenizer.next(), ')' )
) {
currentTransition.timingFunction = PIE.TransitionTimingFunction.getStepsTimingFunction( a, b );
}
else if (tokenValue === 'cubic-bezier' &&
!isNaN( a = tokenToNumber( tokenizer.next() ) ) && a >= 0 && a <= 1 &&
tokenIsChar( tokenizer.next(), ',' ) &&
!isNaN( b = tokenToNumber( tokenizer.next() ) ) &&
tokenIsChar( tokenizer.next(), ',' ) &&
!isNaN( c = tokenToNumber( tokenizer.next() ) ) && c >= 0 && c <= 1 &&
tokenIsChar( tokenizer.next(), ',' ) &&
!isNaN( d = tokenToNumber( tokenizer.next() ) ) &&
tokenIsChar( tokenizer.next(), ')' )
) {
currentTransition.timingFunction = PIE.TransitionTimingFunction.getCubicBezierTimingFunction( a, b, c, d );
}
else {
return null;
}
}

// Property name
else if (tokenType & Type.IDENT && !currentTransition.property) {
currentTransition.property = tokenValue;
}

// Comma: start a new value. Only duration is required so fail if it wasn't found.
else if (tokenType & Type.OPERATOR && tokenValue === ',' && currentTransition.duration) {
flushCurrentTransition();
}

// Something unrecognized - fail!
else {
return null;
}
}

// leftovers
if( currentTransition && currentTransition.duration ) {
flushCurrentTransition();
}

}

return props && props.length ? props : null;
}
} );
78 changes: 78 additions & 0 deletions sources/TransitionTimingFunction.js
@@ -0,0 +1,78 @@
PIE.TransitionTimingFunction = {

/**
* Cubic bezier easing utility by Gaëtan Renaudeau, originally "KeySpline.js", used with
* permission of the author. From the writeup at:
* http://blog.greweb.fr/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/
*
* Modified to be a factory that returns a Function, and to make the internal utility
* functions shared across instances.
*
* @param {Number} x1
* @param {Number} y1
* @param {Number} x2
* @param {Number} y2
* @return Function
*/
getCubicBezierTimingFunction: (function() {
function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
function C(aA1) { return 3.0 * aA1; }

// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
function CalcBezier(aT, aA1, aA2) {
return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
}

// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
function GetSlope(aT, aA1, aA2) {
return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
}

function GetTForX(aX, mX1, mX2) {
// Newton raphson iteration
var aGuessT = aX,
i = 0,
currentSlope, currentX;
for (; i < 4; ++i) {
currentSlope = GetSlope(aGuessT, mX1, mX2);
if (currentSlope == 0.0) return aGuessT;
currentX = CalcBezier(aGuessT, mX1, mX2) - aX;
aGuessT -= currentX / currentSlope;
}
return aGuessT;
}

return function(mX1, mY1, mX2, mY2) {
return function(time) {
if (mX1 == mY1 && mX2 == mY2) return time; // linear
return CalcBezier(GetTForX(time, mX1, mX2), mY1, mY2);
};
}
})(),

/**
* Stepping function - see http://www.w3.org/TR/css3-transitions/#transition-timing-function-property
* @param numSteps The number of steps in the transition
* @param changeAt Whether the step up occurs at the 'start' or 'end' of each step interval. Defaults to 'end'.
* @return Function
*/
getStepsTimingFunction: function(numSteps, changeAt) {
return function(time) {
return Math[ changeAt === 'start' ? 'ceil' : 'floor' ]( time * numSteps ) / numSteps;
};
}
};


PIE.TransitionTimingFunction.namedEasings = {
'ease': PIE.TransitionTimingFunction.getCubicBezierTimingFunction( 0.25, 0.1, 0.25, 1 ),
'linear': PIE.TransitionTimingFunction.getCubicBezierTimingFunction( 0, 0, 1, 1 ),
'ease-in': PIE.TransitionTimingFunction.getCubicBezierTimingFunction( 0.42, 0, 1, 1 ),
'ease-out': PIE.TransitionTimingFunction.getCubicBezierTimingFunction( 0, 0, 0.58, 1 ),
'ease-in-out': PIE.TransitionTimingFunction.getCubicBezierTimingFunction( 0.42, 0, 0.58, 1 ),
'step-start': PIE.TransitionTimingFunction.getStepsTimingFunction( 1, 'start' ),
'step-end': PIE.TransitionTimingFunction.getStepsTimingFunction( 1, 'end' )
};


0 comments on commit 59a89ed

Please sign in to comment.