Skip to content

Commit 127da60

Browse files
committed
rewrote timing engine to use TransitionEnd events to manage frame
synchronisation. This requires passing up 'frame redrawn' flags from the Layer and Viewport code in order to switch between transitions and timeouts on the fly, since non-movement will not cause transition callbacks to fire. animation callbacks now return CSS attributes as an object to apply to the layer element. This allows frame changes to be checked. restructured class internals to move timer responsibility to the TransitionInterval class, drawing functionality to the Layer class
1 parent 725ebc4 commit 127da60

File tree

4 files changed

+194
-94
lines changed

4 files changed

+194
-94
lines changed

jcp-layer.js

+36-25
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ jcparallax.Layer = function(viewport, el, options)
1919
this.element = el;
2020
this.options = options;
2121

22-
jcparallax.Viewport._handleOptions(this.options);
23-
24-
this.setFramerate(jcparallax.Viewport._checkFramerate(this.options, this.options.framerateCheckCb));
22+
jcparallax.Viewport._calcMovementRanges(this.options);
2523

2624
// store animation event handler to be called by our Viewport
2725
if ($.isFunction(this.options.animHandler)) {
@@ -40,11 +38,8 @@ $.extend(jcparallax.Layer.prototype, {
4038
minMaxX : null,
4139
minMaxY : null,
4240

43-
setFramerate : function(ms)
44-
{
45-
this.framerate = ms;
46-
this._addCss(ms);
47-
},
41+
// previous CSS attributes of the layer
42+
prevFrameCss : {},
4843

4944
/**
5045
* Refreshes the cached coordinates of the layer after some external DOM manipulation
@@ -56,6 +51,23 @@ $.extend(jcparallax.Layer.prototype, {
5651
}
5752
},
5853

54+
/**
55+
* Redraw the layer, given new input values
56+
* @return true if the layer needed re-rendering
57+
*/
58+
redraw : function(xVal, yVal)
59+
{
60+
var newCss = this.animHandler.call(this, xVal, yVal);
61+
62+
if (this._cssChanged(newCss)) {
63+
this.prevFrameCss = newCss;
64+
this.element.css(newCss);
65+
return true;
66+
}
67+
68+
return false;
69+
},
70+
5971
_updateMovementRange : function(xRange, yRange)
6072
{
6173
this.minMaxX = $.isArray(xRange) ? xRange : this._calculateMovementRange(xRange);
@@ -71,15 +83,14 @@ $.extend(jcparallax.Layer.prototype, {
7183
return rangeCallback.call(this, this.element, this.viewport);
7284
},
7385

74-
_addCss : function(framerate)
86+
_cssChanged : function(newCss)
7587
{
76-
var that = this;
77-
framerate = (framerate / 1000) + 's';
78-
79-
$.each(jcparallax.cssDomPrefixes, function(i, prefix) {
80-
that.element.css(prefix + 'transition-duration', framerate);
81-
});
82-
that.element.css('transition-duration', framerate);
88+
for (var i in newCss) {
89+
if (this.prevFrameCss[i] === undefined || this.prevFrameCss[i] != newCss[i]) {
90+
return true;
91+
}
92+
}
93+
return false;
8394
}
8495
});
8596

@@ -89,40 +100,40 @@ $.extend(jcparallax.Layer.prototype, {
89100

90101
jcparallax.Layer.animHandlers = {
91102

92-
position : function(el, xVal, yVal)
103+
position : function(xVal, yVal)
93104
{
94-
el.css({
105+
return {
95106
left : this.minMaxX[0] + (xVal * (this.minMaxX[1] - this.minMaxX[0])),
96107
top : this.minMaxY[0] + (yVal * (this.minMaxY[1] - this.minMaxY[0])),
97-
});
108+
};
98109
},
99110

100-
padding : function(el, xVal, yVal)
111+
padding : function(xVal, yVal)
101112
{
102113

103114
},
104115

105-
margins : function(el, xVal, yVal)
116+
margins : function(xVal, yVal)
106117
{
107118

108119
},
109120

110-
background : function(el, xVal, yVal)
121+
background : function(xVal, yVal)
111122
{
112123

113124
},
114125

115-
stretch : function(el, xVal, yVal)
126+
stretch : function(xVal, yVal)
116127
{
117128

118129
},
119130

120-
textShadow : function(el, xVal, yVal)
131+
textShadow : function(xVal, yVal)
121132
{
122133

123134
},
124135

125-
opacity : function(el, xVal, yVal)
136+
opacity : function(xVal, yVal)
126137
{
127138

128139
}

jcp-transitioninterval.js

+94-16
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
* - In half-enabled mode, transitions are still used to smooth between frames but no events are
1111
* available to signify the end of transitions. In this case we just use an interval to try to
1212
* sync the javascript CSS updates with the end of the CSS transitions - but this *can* get out
13-
* of sync slightly which leads to
13+
* of sync slightly which leads to tearing in the animation. Fortunately this mode is unlikely
14+
* to be executed.
1415
* - In fallback mode, we simply sample at a higher framerate to compensate for the lack of browser
1516
* transition support.
1617
*
@@ -20,45 +21,122 @@
2021
* - a sampling rate for the animation when running in CSS-enabled browsers
2122
* - a fallback sampling rate when transition smoothing is not available
2223
*
24+
* After construction, call addElements() to append DOM elements under the control of the TransitionInterval's
25+
* animation timer. These need to be registered in order to apply the correct CSS attributes necessary for
26+
* transition timing on the elements.
27+
*
2328
* @requires jcparallax.js
2429
*/
2530
(function($) {
2631

27-
jcparallax.TransitionInterval = function(cb, framerate, doNotStart)
32+
jcparallax.TransitionInterval = function(cb, framerate, fbFramerate, extraCssAnimCheckCb)
2833
{
29-
this.framerate = framerate;
30-
this.callback = cb;
31-
this.frameCount = -1;
34+
this.elements = jQuery([]);
35+
this.setFramerates(framerate, fbFramerate, extraCssAnimCheckCb);
3236

33-
if (!doNotStart) this.start();
37+
this.callback = cb;
38+
this.frameCount = 0;
3439
};
3540

3641
$.extend(jcparallax.TransitionInterval.prototype, {
3742

38-
interval : null,
43+
_running : false,
44+
45+
timeout : null, // frame timeout reference (fallback mode)
46+
update : null, // underlying registered timer update callback
3947

4048
start : function()
4149
{
4250
// ignore if already running
43-
if (this.interval) {
51+
if (this._running) {
4452
return;
4553
}
4654

47-
var that = this,
48-
update = function() {
49-
++that.frameCount;
50-
that.callback.call(that);
55+
var that = this;
56+
57+
if (jcparallax.support.transitionEndEvent) {
58+
this.update = function() {
59+
// if callback returns to flag no movement, check again at our frame interval
60+
if (!that.callback.call(that)) {
61+
clearTimeout( that.timeout );
62+
that.timeout = setTimeout(function() {
63+
that.timeout = null;
64+
that.update();
65+
}, that.framerate);
66+
} else {
67+
68+
// this check is placed here to failsafe in the event that browser rendering stutters,
69+
// resulting in the transition end event never being called and the animation failing
70+
// to restart.
71+
clearTimeout( that.timeout );
72+
that.timeout = setTimeout(function() {
73+
that.timeout = null;
74+
that.update();
75+
}, that.framerate * 2);
76+
77+
++that.frameCount;
78+
}
5179
};
5280

53-
update(); // run to first frame
81+
// bind to the first element, we only need it to run once since all framerates are the same
82+
this.elements.on(jcparallax.support.transitionEndEvent, that.update);
83+
} else {
84+
var supported = jcparallax.support.transitions && (!this.useFallbackCheckCb || (this.useFallbackCheckCb && this.useFallbackCheckCb()));
85+
86+
this.update = function() {
87+
that.callback.call(that);
88+
that.timeout = setTimeout(that.update, supported ? that.framerate : that.fbFramerate);
5489

55-
this.interval = setInterval(update, this.framerate);
90+
++that.frameCount;
91+
};
92+
this.timeout = setTimeout(this.update, supported ? this.framerate : this.fbFramerate);
93+
}
94+
this._running = true;
95+
96+
this.update(); // run to first frame
5697
},
5798

5899
stop : function()
59100
{
60-
clearInterval( this.interval );
61-
this.interval = null;
101+
if (this._running) {
102+
if (jcparallax.support.transitionEndEvent) {
103+
this.elements.off(jcparallax.support.transitionEndEvent, this.update);
104+
} else {
105+
clearTimeout( this.timeout );
106+
this.timeout = null;
107+
}
108+
this._running = false;
109+
}
110+
},
111+
112+
setFramerates : function(cssFramerate, fallbackFramerate, extraCssAnimCheckCb)
113+
{
114+
this.framerate = cssFramerate;
115+
this.fbFramerate = fallbackFramerate;
116+
this.useFallbackCheckCb = extraCssAnimCheckCb;
117+
this._applyCss();
118+
},
119+
120+
addElements : function(els)
121+
{
122+
if ($.isArray(els)) {
123+
this.elements.pushStack(els);
124+
} else {
125+
this.elements = this.elements.add(els);
126+
}
127+
this._applyCss();
128+
},
129+
130+
_applyCss : function()
131+
{
132+
var that = this,
133+
cssFramerate = (this.framerate / 1000) + 's';
134+
135+
// set transition-duration for CSS sample tweening
136+
$.each(jcparallax.cssDomPrefixes, function(i, prefix) {
137+
that.elements.css(prefix + 'transition-duration', cssFramerate);
138+
});
139+
this.elements.css('transition-duration', cssFramerate);
62140
}
63141
});
64142

0 commit comments

Comments
 (0)